commit f520c66e85f3156cd310cb40973ff49c940437b3 Author: Igor Date: Thu Sep 3 19:04:54 2026 +0200 Vanilla L2J Mobius CT_0 Interlude @ 9be7adbe5a (2026-08-25): java tree, build.xml, libs, and the datapack files SPP later modifies diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..fa1385d --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* -text diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..86e1471 --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +# --- deployment / binaries (not source) --- +server/ +build/ +*.zip +*.rar +*.jar.pre-* +ziAuQ8He +ziVsvreY +# obsolete copies (superseded by src_mobius) +patches/ +src/ +l2j-lisvus/ +# downloaded crest pack (result lives in spp_crest_seed.sql) +crests/ +# local client config +l2.ini +l2.ini.decrypted.txt +# tooling +/.tmp/ +*.log +__pycache__/ diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/build.xml b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/build.xml new file mode 100644 index 0000000..44f4638 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/build.xml @@ -0,0 +1,136 @@ + + + + + This file is part of the L2J Mobius project. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see [http://www.gnu.org/licenses/]. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/dist/game/config/Custom/FakePlayers.ini b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/dist/game/config/Custom/FakePlayers.ini new file mode 100644 index 0000000..31ebaf8 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/dist/game/config/Custom/FakePlayers.ini @@ -0,0 +1,38 @@ +# --------------------------------------------------------------------------- +# Fake players +# --------------------------------------------------------------------------- +# Our fake player system uses the existing NPC system, allowing fake players +# to function seamlessly like NPCs with minimal impact on server performance. + +# Enable fake players. +EnableFakePlayers = False + +# Enable chatting with fake players. +FakePlayerChat = True + +# Enable shots usage for fake players. +FakePlayerUseShots = True + +# Reward PvP kills by killing fake players. +FakePlayerKillsRewardPvP = True + +# Fake player kills apply karma rules. +FakePlayerUnflaggedKillsKarma = True + +# Fake players can be attacked without PvP flagging. +FakePlayerAutoAttackable = False + +# Aggressive AI fake players attack nearby monsters. +FakePlayerAggroMonsters = True + +# Aggressive AI fake players attack nearby players. +FakePlayerAggroPlayers = False + +# Aggressive AI fake players attack nearby fake players. +FakePlayerAggroFPC = False + +# Fake players can drop items when killing monsters. +FakePlayerCanDropItems = True + +# Fake players can pickup dropped items. +FakePlayerCanPickup = True diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/dist/game/config/General.ini b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/dist/game/config/General.ini new file mode 100644 index 0000000..fb73efb --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/dist/game/config/General.ini @@ -0,0 +1,666 @@ +# --------------------------------------------------------------------------- +# General Server Settings +# --------------------------------------------------------------------------- +# The defaults are set to be retail-like. If you modify any of these settings your server will deviate from being retail-like. +# Warning: +# Please take extreme caution when changing anything. Also please understand what you are changing before you do so on a live server. + +# --------------------------------------------------------------------------- +# Administrator +# --------------------------------------------------------------------------- + +# If this option is set to True every newly created character will have access level 127. This means that every character created will have Administrator Privileges. +# Default: False +EverybodyHasAdminRights = False + + +# If True, only accounts with GM access can enter the server. +# Default: False +ServerGMOnly = False + +# Enable GMs to have the glowing aura of a Hero character on login. +# Notes: +# GMs can do "///hero" on themselves and get this aura voluntarily. +# It's advised to keep this off due to graphic lag. +# Default: False +GMHeroAura = False + +# Whether GM logins in builder hide mode by default. +# Default: True +GMStartupBuilderHide = True + +# Auto set invulnerable status to a GM on login. +# Default: False +GMStartupInvulnerable = True + +# Auto set invisible status to a GM on login. +# Default: False +GMStartupInvisible = True + +# Auto block private messages to a GM on login. +# Default: False +GMStartupSilence = False + +# Auto list GMs in GM list (/gmlist) on login. +# Default: False +GMStartupAutoList = False + +# Auto set diet mode on to a GM on login (affects your weight penalty). +# Default: False +GMStartupDietMode = False + +# Item restrictions apply to GMs as well? (True = restricted usage) +# Default: True +GMItemRestriction = True + +# Skill restrictions apply to GMs as well? (True = restricted usage) +# Default: True +GMSkillRestriction = True + +# Allow GMs to drop/trade non-tradable and quest(drop only) items +# Default: False +GMTradeRestrictedItems = False + +# Allow GMs to restart/exit while is fighting stance +# Default: True +GMRestartFighting = True + +# Show the GM's name behind an announcement made by him +# example: "Announce: hi (HanWik)" +GMShowAnnouncerName = False + +# Show the GM's name before an announcement made by him +# example: "Nyaran: hi" +GMShowCritAnnouncerName = False + +# Give special skills for every GM +# 7029,7041-7064,7088-7096,23238-23249 (Master's Blessing) +# Default: False +GMGiveSpecialSkills = False + +# Give special aura skills for every GM +# 7029,23238-23249,23253-23296 (Master's Blessing) +# Default: False +GMGiveSpecialAuraSkills = False + +# Debug html paths for GM characters. +# Default: True +GMDebugHtmlPaths = True + +# In case you are not satisfied with the retail-like implementation of //gmspeed", +# with this config you can rollback it to the old custom L2J version of the GM Speed. +# Default: False +UseSuperHasteAsGMSpeed = False + + +# --------------------------------------------------------------------------- +# Server Security +# --------------------------------------------------------------------------- + +# Logging settings. Enabling these settings will significantly increase the amount of log data written to disk. +# This can lead to increased disk usage and depending on server load and player activity, may impact performance. +# Use these options carefully based on your logging needs and server capacity. + +# Enable logging of player chat messages. Set to True if you need a record of all chat interactions. +# Default: False +LogChat = False + +# Enable logging of item transactions (e.g., pickups, trades, sales). +# This setting can be useful for tracking item movement but may lead to extensive logging on busy servers. +# Default: False +LogItems = False + +# If LogItems is enabled, set this to True to only log important items, specifically Adena (in-game currency) and equippable items. +# This helps to reduce log volume by excluding common, low-value items. +# Default: False +LogItemsSmallLog = False + +# If LogItems is enabled, set this to True to log only specific item IDs rather than all items. +# This is helpful if you only need logs for certain items, such as rare or high-value items. +# Default: False +LogItemsIdsOnly = False + +# Specifies the item IDs to log when LogItemsIdsOnly is enabled. +# Enter item IDs separated by commas to track specific items, for example, rare items or in-game currency. +# Default: 4356 (Gold Einhasad) +LogItemsIdsList = 4356 + +# Enable logging for all actions involving item enchantments, such as success or failure of upgrades. +# This can help with tracking suspicious behavior but may create large log files if enchantment is frequently used. +# Default: False +LogItemEnchants = False + +# Enable logging for all actions related to skill enchantments, including upgrades and modifications. +# Useful for monitoring skill progression but can lead to extensive logging on active servers. +# Default: False +LogSkillEnchants = False + +# Enable audit logging for actions performed by Game Masters (GMs). +# This helps in tracking GM activities to ensure administrative actions are recorded for accountability. +# Default: False +GMAudit = False + +# Check players for non-allowed skills +# Default: False +SkillCheckEnable = True + +# If true, remove invalid skills from player and database. +# Report only, if false. +# Default: False +SkillCheckRemove = True + +# Check also GM characters (only if SkillCheckEnable = True) +# Default: True +SkillCheckGM = False + + +# --------------------------------------------------------------------------- +# Optimization +# --------------------------------------------------------------------------- + +# Items on ground management. +# Allow players to drop items on the ground. +# Default: True +AllowDiscardItem = True + +# Delete dropped reward items from world after a specified amount of seconds. Disabled = 0. +# Default: 600 +AutoDestroyDroppedItemAfter = 600 + +# Time in seconds after which dropped herb will be auto-destroyed +# Default: 60 +AutoDestroyHerbTime = 60 + +# List of item id that will not be destroyed (separated by "," like 57,5575,6673). +# Notes: +# Make sure the lists do NOT CONTAIN trailing spaces or spaces between the numbers! +# Items on this list will be protected regardless of the following options. +# Default: 0 +ListOfProtectedItems = 0 + +# This is the interval (in minutes), that the gameserver will update a players information such as location. +# The higher you set this number, there will be less character information saving so you will have less accessing of the database and your hard drive(s). +# The lower you set this number, there will be more frequent character information saving so you will have more access to the database and your hard drive(s). +# A value of 0 disables periodic saving. +# Independent of this setting the character is always saved after leaving the world. +# Default: 15 +CharacterDataStoreInterval = 15 + +# This enables the server to only update items when saving the character. +# Enabling this greatly reduces DB usage and improves performance. +# WARNING: This option causes item loss during crashes. +# Default: False +LazyItemsUpdate = False + +# When enabled, this forces (even if using lazy item updates) the items owned by the character to be updated into DB when saving its character. +# Default: True +UpdateItemsOnCharStore = True + +# Also delete from world misc. items dropped by players (all except equip-able items). +# Notes: +# Works only if AutoDestroyDroppedItemAfter is greater than 0. +# Default: False +DestroyPlayerDroppedItem = False + +# Destroy dropped equippable items (armor, weapon, jewelry). +# Notes: +# Works only if DestroyPlayerDroppedItem = True +# Default: False +DestroyEquipableItem = False + +# Make all items destroyable. +# If enabled players can destroy all items!!! +DestroyAllItems = False + +# Save dropped items into the database for restoring after restart. +# Default: False +SaveDroppedItem = False + +# Enable/Disable the emptying of the stored dropped items table after items are loaded into memory (safety setting). +# If the server crashed before saving items, on next start old items will be restored and players may already have picked up some of them so this will prevent duplicates. +# Default: False +EmptyDroppedItemTableAfterLoad = False + +# Time interval in minutes to save in DB items on ground. Disabled = 0. +# Notes: +# If SaveDroppedItemInterval is disabled, items will be saved into the database only at server shutdown. +# Default: 60 +SaveDroppedItemInterval = 60 + +# Delete all saved items from the database on next restart? +# Notes: +# Works only if SaveDroppedItem = False. +# Default: False +ClearDroppedItemTable = False + +# Delete invalid quest from players. +# Default: False +AutoDeleteInvalidQuestData = False + +# Allow creating multiple non-stackable items at one time? +# Default: True +MultipleItemDrop = True + +# Enable/Disable html caching. +# True = Load all html's into cache on server startup. +# False = Load html's into cache only on first time html is requested. +# Recommended for live servers: True +# Recommended for development: False +HtmCache = False + +# Check if html files contain non ASCII characters. +# Default = True +CheckHtmlEncoding = True + +# Minimum and maximum variables in seconds for NPC animation delay. +# You must keep MinNpcAnimation lower or equal to MaxNpcAnimation. +# Set values to 0 for disabling random animations. +# Default: 5 +MinNpcAnimation = 5 +# Default: 60 +MaxNpcAnimation = 60 +# Default: 5 +MinMonsterAnimation = 5 +# Default: 60 +MaxMonsterAnimation = 60 + +# Grid options: Grids can turn themselves on and off. This also affects the loading and processing of all AI tasks and (in the future) geodata within this grid. +# Turn on for a grid with a person in it is immediate, but it then turns on the 8 neighboring grids based on the specified number of seconds. +# Turn off for a grid and neighbors occurs after the specified number of seconds have passed during which a grid has had no players in or in any of its neighbors. +# The always on option allows to ignore all this and let all grids be active at all times (not suggested). +# Default: False +GridsAlwaysOn = False + +# Default: 1 +GridNeighborTurnOnTime = 1 + +# Default: 90 +GridNeighborTurnOffTime = 90 + +# Correct buylist and multisell prices when lower than sell price. +# Default: True +CorrectPrices = True + +# Item limit on multisell transaction. +# Max client allowed 999999 +MultisellAmountLimit = 10000 + + +# --------------------------------------------------------------------------- +# Falling Damage +# --------------------------------------------------------------------------- + +# Allow characters to receive damage from falling. +# Default: True +EnableFallingDamage = True + + +# --------------------------------------------------------------------------- +# Skills & Effects +# --------------------------------------------------------------------------- + +# Affect debuff time by the character resistances. +# If the option is false, the debuff time will be the one set in the skill xml. +DebuffDurationUsesResists = False + + +# --------------------------------------------------------------------------- +# Features +# --------------------------------------------------------------------------- + +# Peace Zone Modes: +# 0 = Peace All the Time +# 1 = PVP During Siege for siege participants +# 2 = PVP All the Time +# Default: 0 +PeaceZoneMode = 0 + +# Global Chat. +# Available Options: ON, OFF, GM, GLOBAL +# Default: ON +GlobalChat = ON + +# Trade Chat. +# Available Options: ON, OFF, GM, GLOBAL +# Default: ON +TradeChat = ON + +# Minimum level for chat, 0 = disable. +MinimumChatLevel = 0 + +# If you are experiencing problems with Warehouse transactions, feel free to disable them here. +# Default: True +AllowWarehouse = True + +# Default: True +AllowRefund = True + +# If True player can try on weapon and armor in shop. +# Default: True +AllowWear = True + +# Default: 5 +WearDelay = 5 + +#Adena cost to try on an item. +# Default: 10 +WearPrice = 10 + +# Disable additional adena rewards for starter villages repeatable quests based on turning in items +# True = additional reward for 10+ items not given on quest turn in, False = get additional reward for 10+ items on quest turn in +# ATTENTION: enabling this option greatly decrease adena income capabilities on low levels. +# Default: False +AltVillagesRepQuestReward = False + + +# --------------------------------------------------------------------------- +# Instances +# --------------------------------------------------------------------------- +# Restores the player to their previous instance (ie. an instanced area/dungeon) on EnterWorld. +# Default: False +RestorePlayerInstance = True + +# Set whether summon skills can be used to summon players inside an instance. +# When enabled individual instances can have summoning disabled in instance xml's. +# DEFAULT NEEDS TO BE VERIFIED, MUST BE CHANGED HERE AND IN CONFIG.JAVA IF NOT CORRECT +# Default: False +AllowSummonInInstance = False + +# When a player dies, is removed from instance after a fixed period of time. +# Time in seconds. +# Default: 60 +EjectDeadPlayerTime = 60 + +# When is instance finished, is set time to destruction currency instance. +# Time in seconds. +# Default: 300 +DefaultFinishTime = 300 + + +# --------------------------------------------------------------------------- +# Misc Settings +# --------------------------------------------------------------------------- + +# Default: True +AllowRace = True + +# Default: True +AllowWater = True + +# Default: True +AllowFishing = True + +# Default: True +AllowBoat = True + +# Boat broadcast radius. +# If players getting annoyed by boat shouts then radius can be decreased. +# Default: 20000 +BoatBroadcastRadius = 20000 + +# Default: True +AllowCursedWeapons = True + +# If false, always block party on event. +# If true, allows party if both are in the same event. +AllowPartyInSameEvent = True + +# Show "data/html/servnews.htm" when a character enters world. +# Default: False +ShowServerNews = False + +# Enable the Community Board. +# Default: True +EnableCommunityBoard = True + +# Default Community Board page. +# Default: _bbshome +BBSDefault = _bbshome + +# Enable chat filter +# Default = False +UseChatFilter = False + +# Replace filter words with following chars +ChatFilterChars = ^_^ + +# Banchat for channels, split ";" +# GENERAL (white) +# SHOUT (!) +# WHISPER (") +# PARTY (#) +# CLAN (@) +# GM (//gmchat) +# PETITION_PLAYER (*) +# PETITION_GM (*) +# TRADE (+) +# ALLIANCE ($) +# ANNOUNCEMENT +# BOAT +# FRIEND +# MSNCHAT +# PARTYMATCH_ROOM +# PARTYROOM_COMMANDER (Yellow) +# PARTYROOM_ALL (Red) +# HERO_VOICE (%) +# CRITICAL_ANNOUNCE +# SCREEN_ANNOUNCE +# BATTLEFIELD +# MPCC_ROOM +# NPC_GENERAL +# NPC_SHOUT +# Default: GENERAL;SHOUT;TRADE;HERO_VOICE;WHISPER +BanChatChannels = GENERAL;SHOUT;TRADE;HERO_VOICE;WHISPER + + +# --------------------------------------------------------------------------- +# Manor +# --------------------------------------------------------------------------- + +# Default: True +AllowManor = True + +# Manor refresh time in military hours. +# Default: 20 (8pm) +AltManorRefreshTime = 20 + +# Manor refresh time (minutes). +# Default: 00 (start of the hour) +AltManorRefreshMin = 00 + +# Manor period approve time in military hours. +# Default: 6 (6am) +AltManorApproveTime = 6 + +# Manor period approve time (minutes). +# Default: 0 +AltManorApproveMin = 0 + +# Manor maintenance time (minutes). +# Default: 6 +AltManorMaintenanceMin = 6 + +# Manor Save Type. +# True = Save data into the database after every action +# Default: False +AltManorSaveAllActions = False + +# Manor Save Period (used only if AltManorSaveAllActions = False) +# Default: 2 (hour) +AltManorSavePeriodRate = 2 + + +# --------------------------------------------------------------------------- +# Lottery +# --------------------------------------------------------------------------- + +# Default: True +AllowLottery = True + +# Initial Lottery prize. +# Default: 50000 +AltLotteryPrize = 50000 + +# Lottery Ticket Price +# Default: 2000 +AltLotteryTicketPrice = 2000 + +# What part of jackpot amount should receive characters who pick 5 wining numbers +# Default: 0.6 +AltLottery5NumberRate = 0.6 + +# What part of jackpot amount should receive characters who pick 4 wining numbers +# Default: 0.2 +AltLottery4NumberRate = 0.2 + +# What part of jackpot amount should receive characters who pick 3 wining numbers +# Default: 0.2 +AltLottery3NumberRate = 0.2 + +# How much Adena receive characters who pick two or less of the winning number +# Default: 200 +AltLottery2and1NumberPrize = 200 + + +# --------------------------------------------------------------------------- +# Fishing Tournament +# --------------------------------------------------------------------------- + +# Enable or disable the Fishing Tournament system +AltFishChampionshipEnabled = True + +# Item Id used as reward +AltFishChampionshipRewardItemId = 57 + +# Item count used as reward (for the 5 first winners) +AltFishChampionshipReward1 = 800000 +AltFishChampionshipReward2 = 500000 +AltFishChampionshipReward3 = 300000 +AltFishChampionshipReward4 = 200000 +AltFishChampionshipReward5 = 100000 + + +# --------------------------------------------------------------------------- +# Item Auction +# --------------------------------------------------------------------------- + +# +AltItemAuctionEnabled = True + +# Number of days before auction cleared from database with all bids. +# Default: 14 +AltItemAuctionExpiredAfter = 14 + +# Auction extends to specified amount of seconds if one or more new bids added. +# By default auction extends only two times, by 5 and 3 minutes, this custom value used after it. +# Values higher than 60s is not recommended. +# Default: 0 +AltItemAuctionTimeExtendsOnBid = 0 + + +# --------------------------------------------------------------------------- +# Dimension Rift +# --------------------------------------------------------------------------- + +# Minimum party size to enter rift. Min = 2, Max = 9. +# If while inside the rift, the party becomes smaller, all members will be teleported back. +# Default: 2 +RiftMinPartySize = 2 + +# Number of maximum jumps between rooms allowed, after this time party will be teleported back +# Default: 4 +MaxRiftJumps = 4 + +# Time in ms the party has to wait until the mobs spawn when entering a room. C4 retail: 10s +# Default: 10000 +RiftSpawnDelay = 10000 + +# Time between automatic jumps in seconds +# Default: 480 +AutoJumpsDelayMin = 480 + +# Default: 600 +AutoJumpsDelayMax = 600 + +# Time Multiplier for stay in the boss room +# Default: 1.5 +BossRoomTimeMultiply = 1.5 + +# Cost in dimension fragments to enter the rift, each party member must own this amount +# Default: 18 +RecruitCost = 18 + +# Default: 21 +SoldierCost = 21 + +# Default: 24 +OfficerCost = 24 + +# Default: 27 +CaptainCost = 27 + +# Default: 30 +CommanderCost = 30 + +# Default: 33 +HeroCost = 33 + + +# --------------------------------------------------------------------------- +# Punishment +# --------------------------------------------------------------------------- + +# Player punishment for illegal actions: +# BROADCAST - broadcast warning to GMs only +# KICK - kick player (default) +# KICKBAN - kick and ban player +# JAIL - jail player +DefaultPunish = KICK + +# This setting typically specifies the duration of the above punishment. +# Default: 0 (automatically sets to 100 years) +DefaultPunishParam = 0 + +# Apply default punish if player buy items for zero Adena. +# Default: True +OnlyGMItemsFree = True + +# Jail is a PvP zone. +# Default: False +JailIsPvp = False + +# Disable all chat in jail (except normal one) +# Default: True +JailDisableChat = True + +# Disable all transaction in jail +# Trade/Store/Drop +# Default: False +JailDisableTransaction = False + +# Enchant Skill Details Settings +# Default: 1,5 +NormalEnchantCostMultipiler = 1 +SafeEnchantCostMultipiler = 5 + +# --------------------------------------------------------------------------- +# Custom Components +# --------------------------------------------------------------------------- + +# Default: False +CustomNpcData = True + +# Default: False +CustomTeleportTable = True + +# Default: False +CustomSkillsLoad = True + +# Default: False +CustomItemsLoad = True + +# Default: False +CustomMultisellLoad = True + +# Default: False +CustomBuyListLoad = True diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/dist/game/data/FakePlayerChatData.xml b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/dist/game/data/FakePlayerChatData.xml new file mode 100644 index 0000000..f5da5bf --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/dist/game/data/FakePlayerChatData.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/dist/game/data/scripts/handlers/chat/channels/ChatGeneral.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/dist/game/data/scripts/handlers/chat/channels/ChatGeneral.java new file mode 100644 index 0000000..57b1a0a --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/dist/game/data/scripts/handlers/chat/channels/ChatGeneral.java @@ -0,0 +1,130 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package handlers.chat.channels; + +import java.util.StringTokenizer; + +import org.l2jmobius.gameserver.config.GeneralConfig; +import org.l2jmobius.gameserver.config.custom.FactionSystemConfig; +import org.l2jmobius.gameserver.entity.World; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.holders.player.BlockList; +import org.l2jmobius.gameserver.handler.IChatHandler; +import org.l2jmobius.gameserver.handler.IVoicedCommandHandler; +import org.l2jmobius.gameserver.handler.VoicedCommandHandler; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.enums.ChatType; +import org.l2jmobius.gameserver.network.serverpackets.CreatureSay; + +/** + * General Chat Handler. + * @author durgus + */ +public class ChatGeneral implements IChatHandler +{ + private static final ChatType[] CHAT_TYPES = + { + ChatType.GENERAL, + }; + + @Override + public void onChat(ChatType type, Player activeChar, String paramsValue, String text) + { + boolean vcdUsed = false; + if (text.startsWith(".")) + { + final StringTokenizer st = new StringTokenizer(text); + final IVoicedCommandHandler vch; + String command = ""; + String params = paramsValue; + if (st.countTokens() > 1) + { + command = st.nextToken().substring(1); + params = text.substring(command.length() + 2); + } + else + { + command = text.substring(1); + } + + vch = VoicedCommandHandler.getInstance().getHandler(command); + if (vch != null) + { + vch.onCommand(command, activeChar, params); + vcdUsed = true; + } + else + { + vcdUsed = false; + } + } + + if (!vcdUsed) + { + if (activeChar.isChatBanned() && GeneralConfig.BAN_CHAT_CHANNELS.contains(type)) + { + activeChar.sendPacket(SystemMessageId.CHATTING_IS_CURRENTLY_PROHIBITED_IF_YOU_TRY_TO_CHAT_BEFORE_THE_PROHIBITION_IS_REMOVED_THE_PROHIBITION_TIME_WILL_BECOME_EVEN_LONGER); + return; + } + + if ((activeChar.getLevel() < GeneralConfig.MINIMUM_CHAT_LEVEL) && !activeChar.isGM()) + { + activeChar.sendMessage("Players can use general chat after Lv. " + GeneralConfig.MINIMUM_CHAT_LEVEL + "."); + return; + } + + final CreatureSay cs = new CreatureSay(activeChar, type, activeChar.getAppearance().getVisibleName(), text); + final CreatureSay csRandom = new CreatureSay(activeChar, type, activeChar.getAppearance().getVisibleName(), ChatRandomizer.randomize(text)); + World.forEachVisibleObjectInRange(activeChar, Player.class, 1250, player -> + { + if ((player != null) && !BlockList.isBlocked(player, activeChar)) + { + if (FactionSystemConfig.FACTION_SYSTEM_ENABLED) + { + if (FactionSystemConfig.FACTION_SPECIFIC_CHAT) + { + if ((activeChar.isGood() && player.isEvil()) || (activeChar.isEvil() && player.isGood())) + { + player.sendPacket(csRandom); + } + else + { + player.sendPacket(cs); + } + } + else + { + player.sendPacket(cs); + } + } + else + { + player.sendPacket(cs); + } + } + }); + + activeChar.sendPacket(cs); + } + } + + @Override + public ChatType[] getChatTypeList() + { + return CHAT_TYPES; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/dist/game/data/scripts/handlers/chat/channels/ChatWhisper.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/dist/game/data/scripts/handlers/chat/channels/ChatWhisper.java new file mode 100644 index 0000000..7567f39 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/dist/game/data/scripts/handlers/chat/channels/ChatWhisper.java @@ -0,0 +1,141 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package handlers.chat.channels; + +import org.l2jmobius.gameserver.config.GeneralConfig; +import org.l2jmobius.gameserver.config.PlayerConfig; +import org.l2jmobius.gameserver.config.custom.FactionSystemConfig; +import org.l2jmobius.gameserver.config.custom.FakePlayersConfig; +import org.l2jmobius.gameserver.data.xml.FakePlayerData; +import org.l2jmobius.gameserver.entity.World; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.holders.player.BlockList; +import org.l2jmobius.gameserver.handler.IChatHandler; +import org.l2jmobius.gameserver.managers.FakePlayerChatManager; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.enums.ChatType; +import org.l2jmobius.gameserver.network.serverpackets.CreatureSay; + +/** + * Tell Chat Handler. + * @author durgus + */ +public class ChatWhisper implements IChatHandler +{ + private static final ChatType[] CHAT_TYPES = + { + ChatType.WHISPER + }; + + @Override + public void onChat(ChatType type, Player activeChar, String target, String text) + { + if (activeChar.isChatBanned() && GeneralConfig.BAN_CHAT_CHANNELS.contains(type)) + { + activeChar.sendPacket(SystemMessageId.CHATTING_IS_CURRENTLY_PROHIBITED_IF_YOU_TRY_TO_CHAT_BEFORE_THE_PROHIBITION_IS_REMOVED_THE_PROHIBITION_TIME_WILL_BECOME_EVEN_LONGER); + return; + } + + if (GeneralConfig.JAIL_DISABLE_CHAT && activeChar.isJailed() && !activeChar.isGM()) + { + activeChar.sendPacket(SystemMessageId.CHATTING_IS_CURRENTLY_PROHIBITED); + return; + } + + // Return if no target is set. + if (target == null) + { + return; + } + + if (FakePlayersConfig.FAKE_PLAYERS_ENABLED && (FakePlayerData.getInstance().getProperName(target) != null)) + { + if (FakePlayerData.getInstance().isTalkable(target)) + { + if (FakePlayersConfig.FAKE_PLAYER_CHAT) + { + final String name = FakePlayerData.getInstance().getProperName(target); + activeChar.sendPacket(new CreatureSay(activeChar, type, "->" + name, text)); + FakePlayerChatManager.getInstance().manageChat(activeChar, name, text); + } + else + { + activeChar.sendPacket(SystemMessageId.THAT_PERSON_IS_IN_MESSAGE_REFUSAL_MODE); + } + } + else + { + activeChar.sendPacket(SystemMessageId.THAT_PLAYER_IS_NOT_ONLINE); + } + return; + } + + final Player receiver = World.getPlayer(target); + if ((receiver != null) && !receiver.isSilenceMode(activeChar.getObjectId())) + { + if (GeneralConfig.JAIL_DISABLE_CHAT && receiver.isJailed() && !activeChar.isGM()) + { + activeChar.sendMessage("Player is in jail."); + return; + } + + if (receiver.isChatBanned()) + { + activeChar.sendPacket(SystemMessageId.THAT_PERSON_IS_IN_MESSAGE_REFUSAL_MODE); + return; + } + + if ((receiver.getClient() == null) || receiver.getClient().isDetached()) + { + activeChar.sendMessage("Player is in offline mode."); + return; + } + + if (FactionSystemConfig.FACTION_SYSTEM_ENABLED && FactionSystemConfig.FACTION_SPECIFIC_CHAT && ((activeChar.isGood() && receiver.isEvil()) || (activeChar.isEvil() && receiver.isGood()))) + { + activeChar.sendMessage("Player belongs to the opposing faction."); + return; + } + + if (!BlockList.isBlocked(receiver, activeChar)) + { + // Allow receiver to send PMs to this char, which is in silence mode. + if (PlayerConfig.SILENCE_MODE_EXCLUDE && activeChar.isSilenceMode()) + { + activeChar.addSilenceModeExcluded(receiver.getObjectId()); + } + + receiver.sendPacket(new CreatureSay(activeChar, type, activeChar.getName(), text)); + activeChar.sendPacket(new CreatureSay(activeChar, type, "->" + receiver.getName(), text)); + } + else + { + activeChar.sendPacket(SystemMessageId.THAT_PERSON_IS_IN_MESSAGE_REFUSAL_MODE); + } + } + else + { + activeChar.sendPacket(SystemMessageId.THAT_PLAYER_IS_NOT_ONLINE); + } + } + + @Override + public ChatType[] getChatTypeList() + { + return CHAT_TYPES; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/dist/game/data/spawns/Others/FakePlayers.xml b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/dist/game/data/spawns/Others/FakePlayers.xml new file mode 100644 index 0000000..61bbdc2 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/dist/game/data/spawns/Others/FakePlayers.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/dist/libs/HikariCP-7.0.2.jar b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/dist/libs/HikariCP-7.0.2.jar new file mode 100644 index 0000000..d165106 Binary files /dev/null and b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/dist/libs/HikariCP-7.0.2.jar differ diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/dist/libs/mysql-connector-j-9.5.0.jar b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/dist/libs/mysql-connector-j-9.5.0.jar new file mode 100644 index 0000000..4b90fff Binary files /dev/null and b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/dist/libs/mysql-connector-j-9.5.0.jar differ diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/dist/libs/slf4j-api-2.0.17.jar b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/dist/libs/slf4j-api-2.0.17.jar new file mode 100644 index 0000000..26b1545 Binary files /dev/null and b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/dist/libs/slf4j-api-2.0.17.jar differ diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/dist/libs/slf4j-simple-2.0.17.jar b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/dist/libs/slf4j-simple-2.0.17.jar new file mode 100644 index 0000000..9a7348e Binary files /dev/null and b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/dist/libs/slf4j-simple-2.0.17.jar differ diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/config/DatabaseConfig.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/config/DatabaseConfig.java new file mode 100644 index 0000000..c11cd83 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/config/DatabaseConfig.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.commons.config; + +import org.l2jmobius.commons.util.ConfigReader; + +/** + * This class loads all the database related configurations. + * @author Mobius + */ +public class DatabaseConfig +{ + // File + private static final String DATABASE_CONFIG_FILE = "./config/Database.ini"; + + // Constants + public static String DATABASE_DRIVER; + public static String DATABASE_URL; + public static String DATABASE_LOGIN; + public static String DATABASE_PASSWORD; + public static int DATABASE_MAX_CONNECTIONS; + public static boolean DATABASE_TEST_CONNECTIONS; + public static boolean BACKUP_DATABASE; + public static String MYSQL_BIN_PATH; + public static String BACKUP_PATH; + public static int BACKUP_DAYS; + + public static void load() + { + final ConfigReader config = new ConfigReader(DATABASE_CONFIG_FILE); + DATABASE_DRIVER = config.getString("Driver", "com.mysql.cj.jdbc.Driver"); + DATABASE_URL = config.getString("URL", "jdbc:mysql://localhost/l2jmobius"); + DATABASE_LOGIN = config.getString("Login", "root"); + DATABASE_PASSWORD = config.getString("Password", ""); + DATABASE_MAX_CONNECTIONS = config.getInt("MaximumDatabaseConnections", 10); + DATABASE_TEST_CONNECTIONS = config.getBoolean("TestDatabaseConnections", false); + BACKUP_DATABASE = config.getBoolean("BackupDatabase", false); + MYSQL_BIN_PATH = config.getString("MySqlBinLocation", "C:/xampp/mysql/bin/"); + BACKUP_PATH = config.getString("BackupPath", "../backup/"); + BACKUP_DAYS = config.getInt("BackupDays", 30); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/config/InterfaceConfig.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/config/InterfaceConfig.java new file mode 100644 index 0000000..2139878 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/config/InterfaceConfig.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.commons.config; + +import java.awt.GraphicsEnvironment; + +import org.l2jmobius.commons.util.ConfigReader; + +/** + * This class loads all the interface related configurations. + * @author Mobius + */ +public class InterfaceConfig +{ + // File + private static final String INTERFACE_CONFIG_FILE = "./config/Interface.ini"; + + // Constants + public static boolean ENABLE_GUI; + public static boolean DARK_THEME; + + public static void load() + { + final ConfigReader config = new ConfigReader(INTERFACE_CONFIG_FILE); + ENABLE_GUI = config.getBoolean("EnableGUI", true) && !GraphicsEnvironment.isHeadless(); + if (ENABLE_GUI) + { + DARK_THEME = config.getBoolean("DarkTheme", true); + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/config/ThreadConfig.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/config/ThreadConfig.java new file mode 100644 index 0000000..c708d66 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/config/ThreadConfig.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.commons.config; + +import org.l2jmobius.commons.util.ConfigReader; + +/** + * This class loads all the threadpool related configurations. + * @author Mobius + */ +public class ThreadConfig +{ + // File + private static final String THREADS_CONFIG_FILE = "./config/Threads.ini"; + + // Constants + public static int SCHEDULED_THREAD_POOL_SIZE; + public static int HIGH_PRIORITY_SCHEDULED_THREAD_POOL_SIZE; + public static int INSTANT_THREAD_POOL_SIZE; + public static boolean THREADS_FOR_LOADING; + + public static void load() + { + final ConfigReader config = new ConfigReader(THREADS_CONFIG_FILE); + + SCHEDULED_THREAD_POOL_SIZE = config.getInt("ScheduledThreadPoolSize", -1); + if (SCHEDULED_THREAD_POOL_SIZE == -1) + { + SCHEDULED_THREAD_POOL_SIZE = Runtime.getRuntime().availableProcessors() * 4; + } + + INSTANT_THREAD_POOL_SIZE = config.getInt("InstantThreadPoolSize", -1); + if (INSTANT_THREAD_POOL_SIZE == -1) + { + INSTANT_THREAD_POOL_SIZE = Runtime.getRuntime().availableProcessors() * 2; + } + + if ((SCHEDULED_THREAD_POOL_SIZE > 2) && (INSTANT_THREAD_POOL_SIZE > 2)) + { + HIGH_PRIORITY_SCHEDULED_THREAD_POOL_SIZE = Math.max(2, SCHEDULED_THREAD_POOL_SIZE / 4); + } + else + { + HIGH_PRIORITY_SCHEDULED_THREAD_POOL_SIZE = 0; + } + + if (config.containsKey("ThreadsForLoading")) + { + THREADS_FOR_LOADING = config.getBoolean("ThreadsForLoading", false); + } + else + { + THREADS_FOR_LOADING = false; + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/crypt/BlowfishEngine.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/crypt/BlowfishEngine.java new file mode 100644 index 0000000..52f2891 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/crypt/BlowfishEngine.java @@ -0,0 +1,1468 @@ +/* + * This file is based on the Blowfish Engine that is part of the BouncyCastle JCE. + * Copyright (c) 2000 The Legion Of The Bouncy Castle (http://www.bouncycastle.org) + * 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. + */ +package org.l2jmobius.commons.crypt; + +import java.io.IOException; + +public class BlowfishEngine +{ + private static final int[] KP = + { + 0x243F6A88, + 0x85A308D3, + 0x13198A2E, + 0x03707344, + 0xA4093822, + 0x299F31D0, + 0x082EFA98, + 0xEC4E6C89, + 0x452821E6, + 0x38D01377, + 0xBE5466CF, + 0x34E90C6C, + 0xC0AC29B7, + 0xC97C50DD, + 0x3F84D5B5, + 0xB5470917, + 0x9216D5D9, + 0x8979FB1B + }; + private static final int[] KS0 = + { + 0xD1310BA6, + 0x98DFB5AC, + 0x2FFD72DB, + 0xD01ADFB7, + 0xB8E1AFED, + 0x6A267E96, + 0xBA7C9045, + 0xF12C7F99, + 0x24A19947, + 0xB3916CF7, + 0x0801F2E2, + 0x858EFC16, + 0x636920D8, + 0x71574E69, + 0xA458FEA3, + 0xF4933D7E, + 0x0D95748F, + 0x728EB658, + 0x718BCD58, + 0x82154AEE, + 0x7B54A41D, + 0xC25A59B5, + 0x9C30D539, + 0x2AF26013, + 0xC5D1B023, + 0x286085F0, + 0xCA417918, + 0xB8DB38EF, + 0x8E79DCB0, + 0x603A180E, + 0x6C9E0E8B, + 0xB01E8A3E, + 0xD71577C1, + 0xBD314B27, + 0x78AF2FDA, + 0x55605C60, + 0xE65525F3, + 0xAA55AB94, + 0x57489862, + 0x63E81440, + 0x55CA396A, + 0x2AAB10B6, + 0xB4CC5C34, + 0x1141E8CE, + 0xA15486AF, + 0x7C72E993, + 0xB3EE1411, + 0x636FBC2A, + 0x2BA9C55D, + 0x741831F6, + 0xCE5C3E16, + 0x9B87931E, + 0xAFD6BA33, + 0x6C24CF5C, + 0x7A325381, + 0x28958677, + 0x3B8F4898, + 0x6B4BB9AF, + 0xC4BFE81B, + 0x66282193, + 0x61D809CC, + 0xFB21A991, + 0x487CAC60, + 0x5DEC8032, + 0xEF845D5D, + 0xE98575B1, + 0xDC262302, + 0xEB651B88, + 0x23893E81, + 0xD396ACC5, + 0x0F6D6FF3, + 0x83F44239, + 0x2E0B4482, + 0xA4842004, + 0x69C8F04A, + 0x9E1F9B5E, + 0x21C66842, + 0xF6E96C9A, + 0x670C9C61, + 0xABD388F0, + 0x6A51A0D2, + 0xD8542F68, + 0x960FA728, + 0xAB5133A3, + 0x6EEF0B6C, + 0x137A3BE4, + 0xBA3BF050, + 0x7EFB2A98, + 0xA1F1651D, + 0x39AF0176, + 0x66CA593E, + 0x82430E88, + 0x8CEE8619, + 0x456F9FB4, + 0x7D84A5C3, + 0x3B8B5EBE, + 0xE06F75D8, + 0x85C12073, + 0x401A449F, + 0x56C16AA6, + 0x4ED3AA62, + 0x363F7706, + 0x1BFEDF72, + 0x429B023D, + 0x37D0D724, + 0xD00A1248, + 0xDB0FEAD3, + 0x49F1C09B, + 0x075372C9, + 0x80991B7B, + 0x25D479D8, + 0xF6E8DEF7, + 0xE3FE501A, + 0xB6794C3B, + 0x976CE0BD, + 0x04C006BA, + 0xC1A94FB6, + 0x409F60C4, + 0x5E5C9EC2, + 0x196A2463, + 0x68FB6FAF, + 0x3E6C53B5, + 0x1339B2EB, + 0x3B52EC6F, + 0x6DFC511F, + 0x9B30952C, + 0xCC814544, + 0xAF5EBD09, + 0xBEE3D004, + 0xDE334AFD, + 0x660F2807, + 0x192E4BB3, + 0xC0CBA857, + 0x45C8740F, + 0xD20B5F39, + 0xB9D3FBDB, + 0x5579C0BD, + 0x1A60320A, + 0xD6A100C6, + 0x402C7279, + 0x679F25FE, + 0xFB1FA3CC, + 0x8EA5E9F8, + 0xDB3222F8, + 0x3C7516DF, + 0xFD616B15, + 0x2F501EC8, + 0xAD0552AB, + 0x323DB5FA, + 0xFD238760, + 0x53317B48, + 0x3E00DF82, + 0x9E5C57BB, + 0xCA6F8CA0, + 0x1A87562E, + 0xDF1769DB, + 0xD542A8F6, + 0x287EFFC3, + 0xAC6732C6, + 0x8C4F5573, + 0x695B27B0, + 0xBBCA58C8, + 0xE1FFA35D, + 0xB8F011A0, + 0x10FA3D98, + 0xFD2183B8, + 0x4AFCB56C, + 0x2DD1D35B, + 0x9A53E479, + 0xB6F84565, + 0xD28E49BC, + 0x4BFB9790, + 0xE1DDF2DA, + 0xA4CB7E33, + 0x62FB1341, + 0xCEE4C6E8, + 0xEF20CADA, + 0x36774C01, + 0xD07E9EFE, + 0x2BF11FB4, + 0x95DBDA4D, + 0xAE909198, + 0xEAAD8E71, + 0x6B93D5A0, + 0xD08ED1D0, + 0xAFC725E0, + 0x8E3C5B2F, + 0x8E7594B7, + 0x8FF6E2FB, + 0xF2122B64, + 0x8888B812, + 0x900DF01C, + 0x4FAD5EA0, + 0x688FC31C, + 0xD1CFF191, + 0xB3A8C1AD, + 0x2F2F2218, + 0xBE0E1777, + 0xEA752DFE, + 0x8B021FA1, + 0xE5A0CC0F, + 0xB56F74E8, + 0x18ACF3D6, + 0xCE89E299, + 0xB4A84FE0, + 0xFD13E0B7, + 0x7CC43B81, + 0xD2ADA8D9, + 0x165FA266, + 0x80957705, + 0x93CC7314, + 0x211A1477, + 0xE6AD2065, + 0x77B5FA86, + 0xC75442F5, + 0xFB9D35CF, + 0xEBCDAF0C, + 0x7B3E89A0, + 0xD6411BD3, + 0xAE1E7E49, + 0x00250E2D, + 0x2071B35E, + 0x226800BB, + 0x57B8E0AF, + 0x2464369B, + 0xF009B91E, + 0x5563911D, + 0x59DFA6AA, + 0x78C14389, + 0xD95A537F, + 0x207D5BA2, + 0x02E5B9C5, + 0x83260376, + 0x6295CFA9, + 0x11C81968, + 0x4E734A41, + 0xB3472DCA, + 0x7B14A94A, + 0x1B510052, + 0x9A532915, + 0xD60F573F, + 0xBC9BC6E4, + 0x2B60A476, + 0x81E67400, + 0x08BA6FB5, + 0x571BE91F, + 0xF296EC6B, + 0x2A0DD915, + 0xB6636521, + 0xE7B9F9B6, + 0xFF34052E, + 0xC5855664, + 0x53B02D5D, + 0xA99F8FA1, + 0x08BA4799, + 0x6E85076A + }; + private static final int[] KS1 = + { + 0x4B7A70E9, + 0xB5B32944, + 0xDB75092E, + 0xC4192623, + 0xAD6EA6B0, + 0x49A7DF7D, + 0x9CEE60B8, + 0x8FEDB266, + 0xECAA8C71, + 0x699A17FF, + 0x5664526C, + 0xC2B19EE1, + 0x193602A5, + 0x75094C29, + 0xA0591340, + 0xE4183A3E, + 0x3F54989A, + 0x5B429D65, + 0x6B8FE4D6, + 0x99F73FD6, + 0xA1D29C07, + 0xEFE830F5, + 0x4D2D38E6, + 0xF0255DC1, + 0x4CDD2086, + 0x8470EB26, + 0x6382E9C6, + 0x021ECC5E, + 0x09686B3F, + 0x3EBAEFC9, + 0x3C971814, + 0x6B6A70A1, + 0x687F3584, + 0x52A0E286, + 0xB79C5305, + 0xAA500737, + 0x3E07841C, + 0x7FDEAE5C, + 0x8E7D44EC, + 0x5716F2B8, + 0xB03ADA37, + 0xF0500C0D, + 0xF01C1F04, + 0x0200B3FF, + 0xAE0CF51A, + 0x3CB574B2, + 0x25837A58, + 0xDC0921BD, + 0xD19113F9, + 0x7CA92FF6, + 0x94324773, + 0x22F54701, + 0x3AE5E581, + 0x37C2DADC, + 0xC8B57634, + 0x9AF3DDA7, + 0xA9446146, + 0x0FD0030E, + 0xECC8C73E, + 0xA4751E41, + 0xE238CD99, + 0x3BEA0E2F, + 0x3280BBA1, + 0x183EB331, + 0x4E548B38, + 0x4F6DB908, + 0x6F420D03, + 0xF60A04BF, + 0x2CB81290, + 0x24977C79, + 0x5679B072, + 0xBCAF89AF, + 0xDE9A771F, + 0xD9930810, + 0xB38BAE12, + 0xDCCF3F2E, + 0x5512721F, + 0x2E6B7124, + 0x501ADDE6, + 0x9F84CD87, + 0x7A584718, + 0x7408DA17, + 0xBC9F9ABC, + 0xE94B7D8C, + 0xEC7AEC3A, + 0xDB851DFA, + 0x63094366, + 0xC464C3D2, + 0xEF1C1847, + 0x3215D908, + 0xDD433B37, + 0x24C2BA16, + 0x12A14D43, + 0x2A65C451, + 0x50940002, + 0x133AE4DD, + 0x71DFF89E, + 0x10314E55, + 0x81AC77D6, + 0x5F11199B, + 0x043556F1, + 0xD7A3C76B, + 0x3C11183B, + 0x5924A509, + 0xF28FE6ED, + 0x97F1FBFA, + 0x9EBABF2C, + 0x1E153C6E, + 0x86E34570, + 0xEAE96FB1, + 0x860E5E0A, + 0x5A3E2AB3, + 0x771FE71C, + 0x4E3D06FA, + 0x2965DCB9, + 0x99E71D0F, + 0x803E89D6, + 0x5266C825, + 0x2E4CC978, + 0x9C10B36A, + 0xC6150EBA, + 0x94E2EA78, + 0xA5FC3C53, + 0x1E0A2DF4, + 0xF2F74EA7, + 0x361D2B3D, + 0x1939260F, + 0x19C27960, + 0x5223A708, + 0xF71312B6, + 0xEBADFE6E, + 0xEAC31F66, + 0xE3BC4595, + 0xA67BC883, + 0xB17F37D1, + 0x018CFF28, + 0xC332DDEF, + 0xBE6C5AA5, + 0x65582185, + 0x68AB9802, + 0xEECEA50F, + 0xDB2F953B, + 0x2AEF7DAD, + 0x5B6E2F84, + 0x1521B628, + 0x29076170, + 0xECDD4775, + 0x619F1510, + 0x13CCA830, + 0xEB61BD96, + 0x0334FE1E, + 0xAA0363CF, + 0xB5735C90, + 0x4C70A239, + 0xD59E9E0B, + 0xCBAADE14, + 0xEECC86BC, + 0x60622CA7, + 0x9CAB5CAB, + 0xB2F3846E, + 0x648B1EAF, + 0x19BDF0CA, + 0xA02369B9, + 0x655ABB50, + 0x40685A32, + 0x3C2AB4B3, + 0x319EE9D5, + 0xC021B8F7, + 0x9B540B19, + 0x875FA099, + 0x95F7997E, + 0x623D7DA8, + 0xF837889A, + 0x97E32D77, + 0x11ED935F, + 0x16681281, + 0x0E358829, + 0xC7E61FD6, + 0x96DEDFA1, + 0x7858BA99, + 0x57F584A5, + 0x1B227263, + 0x9B83C3FF, + 0x1AC24696, + 0xCDB30AEB, + 0x532E3054, + 0x8FD948E4, + 0x6DBC3128, + 0x58EBF2EF, + 0x34C6FFEA, + 0xFE28ED61, + 0xEE7C3C73, + 0x5D4A14D9, + 0xE864B7E3, + 0x42105D14, + 0x203E13E0, + 0x45EEE2B6, + 0xA3AAABEA, + 0xDB6C4F15, + 0xFACB4FD0, + 0xC742F442, + 0xEF6ABBB5, + 0x654F3B1D, + 0x41CD2105, + 0xD81E799E, + 0x86854DC7, + 0xE44B476A, + 0x3D816250, + 0xCF62A1F2, + 0x5B8D2646, + 0xFC8883A0, + 0xC1C7B6A3, + 0x7F1524C3, + 0x69CB7492, + 0x47848A0B, + 0x5692B285, + 0x095BBF00, + 0xAD19489D, + 0x1462B174, + 0x23820E00, + 0x58428D2A, + 0x0C55F5EA, + 0x1DADF43E, + 0x233F7061, + 0x3372F092, + 0x8D937E41, + 0xD65FECF1, + 0x6C223BDB, + 0x7CDE3759, + 0xCBEE7460, + 0x4085F2A7, + 0xCE77326E, + 0xA6078084, + 0x19F8509E, + 0xE8EFD855, + 0x61D99735, + 0xA969A7AA, + 0xC50C06C2, + 0x5A04ABFC, + 0x800BCADC, + 0x9E447A2E, + 0xC3453484, + 0xFDD56705, + 0x0E1E9EC9, + 0xDB73DBD3, + 0x105588CD, + 0x675FDA79, + 0xE3674340, + 0xC5C43465, + 0x713E38D8, + 0x3D28F89E, + 0xF16DFF20, + 0x153E21E7, + 0x8FB03D4A, + 0xE6E39F2B, + 0xDB83ADF7 + }; + private static final int[] KS2 = + { + 0xE93D5A68, + 0x948140F7, + 0xF64C261C, + 0x94692934, + 0x411520F7, + 0x7602D4F7, + 0xBCF46B2E, + 0xD4A20068, + 0xD4082471, + 0x3320F46A, + 0x43B7D4B7, + 0x500061AF, + 0x1E39F62E, + 0x97244546, + 0x14214F74, + 0xBF8B8840, + 0x4D95FC1D, + 0x96B591AF, + 0x70F4DDD3, + 0x66A02F45, + 0xBFBC09EC, + 0x03BD9785, + 0x7FAC6DD0, + 0x31CB8504, + 0x96EB27B3, + 0x55FD3941, + 0xDA2547E6, + 0xABCA0A9A, + 0x28507825, + 0x530429F4, + 0x0A2C86DA, + 0xE9B66DFB, + 0x68DC1462, + 0xD7486900, + 0x680EC0A4, + 0x27A18DEE, + 0x4F3FFEA2, + 0xE887AD8C, + 0xB58CE006, + 0x7AF4D6B6, + 0xAACE1E7C, + 0xD3375FEC, + 0xCE78A399, + 0x406B2A42, + 0x20FE9E35, + 0xD9F385B9, + 0xEE39D7AB, + 0x3B124E8B, + 0x1DC9FAF7, + 0x4B6D1856, + 0x26A36631, + 0xEAE397B2, + 0x3A6EFA74, + 0xDD5B4332, + 0x6841E7F7, + 0xCA7820FB, + 0xFB0AF54E, + 0xD8FEB397, + 0x454056AC, + 0xBA489527, + 0x55533A3A, + 0x20838D87, + 0xFE6BA9B7, + 0xD096954B, + 0x55A867BC, + 0xA1159A58, + 0xCCA92963, + 0x99E1DB33, + 0xA62A4A56, + 0x3F3125F9, + 0x5EF47E1C, + 0x9029317C, + 0xFDF8E802, + 0x04272F70, + 0x80BB155C, + 0x05282CE3, + 0x95C11548, + 0xE4C66D22, + 0x48C1133F, + 0xC70F86DC, + 0x07F9C9EE, + 0x41041F0F, + 0x404779A4, + 0x5D886E17, + 0x325F51EB, + 0xD59BC0D1, + 0xF2BCC18F, + 0x41113564, + 0x257B7834, + 0x602A9C60, + 0xDFF8E8A3, + 0x1F636C1B, + 0x0E12B4C2, + 0x02E1329E, + 0xAF664FD1, + 0xCAD18115, + 0x6B2395E0, + 0x333E92E1, + 0x3B240B62, + 0xEEBEB922, + 0x85B2A20E, + 0xE6BA0D99, + 0xDE720C8C, + 0x2DA2F728, + 0xD0127845, + 0x95B794FD, + 0x647D0862, + 0xE7CCF5F0, + 0x5449A36F, + 0x877D48FA, + 0xC39DFD27, + 0xF33E8D1E, + 0x0A476341, + 0x992EFF74, + 0x3A6F6EAB, + 0xF4F8FD37, + 0xA812DC60, + 0xA1EBDDF8, + 0x991BE14C, + 0xDB6E6B0D, + 0xC67B5510, + 0x6D672C37, + 0x2765D43B, + 0xDCD0E804, + 0xF1290DC7, + 0xCC00FFA3, + 0xB5390F92, + 0x690FED0B, + 0x667B9FFB, + 0xCEDB7D9C, + 0xA091CF0B, + 0xD9155EA3, + 0xBB132F88, + 0x515BAD24, + 0x7B9479BF, + 0x763BD6EB, + 0x37392EB3, + 0xCC115979, + 0x8026E297, + 0xF42E312D, + 0x6842ADA7, + 0xC66A2B3B, + 0x12754CCC, + 0x782EF11C, + 0x6A124237, + 0xB79251E7, + 0x06A1BBE6, + 0x4BFB6350, + 0x1A6B1018, + 0x11CAEDFA, + 0x3D25BDD8, + 0xE2E1C3C9, + 0x44421659, + 0x0A121386, + 0xD90CEC6E, + 0xD5ABEA2A, + 0x64AF674E, + 0xDA86A85F, + 0xBEBFE988, + 0x64E4C3FE, + 0x9DBC8057, + 0xF0F7C086, + 0x60787BF8, + 0x6003604D, + 0xD1FD8346, + 0xF6381FB0, + 0x7745AE04, + 0xD736FCCC, + 0x83426B33, + 0xF01EAB71, + 0xB0804187, + 0x3C005E5F, + 0x77A057BE, + 0xBDE8AE24, + 0x55464299, + 0xBF582E61, + 0x4E58F48F, + 0xF2DDFDA2, + 0xF474EF38, + 0x8789BDC2, + 0x5366F9C3, + 0xC8B38E74, + 0xB475F255, + 0x46FCD9B9, + 0x7AEB2661, + 0x8B1DDF84, + 0x846A0E79, + 0x915F95E2, + 0x466E598E, + 0x20B45770, + 0x8CD55591, + 0xC902DE4C, + 0xB90BACE1, + 0xBB8205D0, + 0x11A86248, + 0x7574A99E, + 0xB77F19B6, + 0xE0A9DC09, + 0x662D09A1, + 0xC4324633, + 0xE85A1F02, + 0x09F0BE8C, + 0x4A99A025, + 0x1D6EFE10, + 0x1AB93D1D, + 0x0BA5A4DF, + 0xA186F20F, + 0x2868F169, + 0xDCB7DA83, + 0x573906FE, + 0xA1E2CE9B, + 0x4FCD7F52, + 0x50115E01, + 0xA70683FA, + 0xA002B5C4, + 0x0DE6D027, + 0x9AF88C27, + 0x773F8641, + 0xC3604C06, + 0x61A806B5, + 0xF0177A28, + 0xC0F586E0, + 0x006058AA, + 0x30DC7D62, + 0x11E69ED7, + 0x2338EA63, + 0x53C2DD94, + 0xC2C21634, + 0xBBCBEE56, + 0x90BCB6DE, + 0xEBFC7DA1, + 0xCE591D76, + 0x6F05E409, + 0x4B7C0188, + 0x39720A3D, + 0x7C927C24, + 0x86E3725F, + 0x724D9DB9, + 0x1AC15BB4, + 0xD39EB8FC, + 0xED545578, + 0x08FCA5B5, + 0xD83D7CD3, + 0x4DAD0FC4, + 0x1E50EF5E, + 0xB161E6F8, + 0xA28514D9, + 0x6C51133C, + 0x6FD5C7E7, + 0x56E14EC4, + 0x362ABFCE, + 0xDDC6C837, + 0xD79A3234, + 0x92638212, + 0x670EFA8E, + 0x406000E0 + }; + private static final int[] KS3 = + { + 0x3A39CE37, + 0xD3FAF5CF, + 0xABC27737, + 0x5AC52D1B, + 0x5CB0679E, + 0x4FA33742, + 0xD3822740, + 0x99BC9BBE, + 0xD5118E9D, + 0xBF0F7315, + 0xD62D1C7E, + 0xC700C47B, + 0xB78C1B6B, + 0x21A19045, + 0xB26EB1BE, + 0x6A366EB4, + 0x5748AB2F, + 0xBC946E79, + 0xC6A376D2, + 0x6549C2C8, + 0x530FF8EE, + 0x468DDE7D, + 0xD5730A1D, + 0x4CD04DC6, + 0x2939BBDB, + 0xA9BA4650, + 0xAC9526E8, + 0xBE5EE304, + 0xA1FAD5F0, + 0x6A2D519A, + 0x63EF8CE2, + 0x9A86EE22, + 0xC089C2B8, + 0x43242EF6, + 0xA51E03AA, + 0x9CF2D0A4, + 0x83C061BA, + 0x9BE96A4D, + 0x8FE51550, + 0xBA645BD6, + 0x2826A2F9, + 0xA73A3AE1, + 0x4BA99586, + 0xEF5562E9, + 0xC72FEFD3, + 0xF752F7DA, + 0x3F046F69, + 0x77FA0A59, + 0x80E4A915, + 0x87B08601, + 0x9B09E6AD, + 0x3B3EE593, + 0xE990FD5A, + 0x9E34D797, + 0x2CF0B7D9, + 0x022B8B51, + 0x96D5AC3A, + 0x017DA67D, + 0xD1CF3ED6, + 0x7C7D2D28, + 0x1F9F25CF, + 0xADF2B89B, + 0x5AD6B472, + 0x5A88F54C, + 0xE029AC71, + 0xE019A5E6, + 0x47B0ACFD, + 0xED93FA9B, + 0xE8D3C48D, + 0x283B57CC, + 0xF8D56629, + 0x79132E28, + 0x785F0191, + 0xED756055, + 0xF7960E44, + 0xE3D35E8C, + 0x15056DD4, + 0x88F46DBA, + 0x03A16125, + 0x0564F0BD, + 0xC3EB9E15, + 0x3C9057A2, + 0x97271AEC, + 0xA93A072A, + 0x1B3F6D9B, + 0x1E6321F5, + 0xF59C66FB, + 0x26DCF319, + 0x7533D928, + 0xB155FDF5, + 0x03563482, + 0x8ABA3CBB, + 0x28517711, + 0xC20AD9F8, + 0xABCC5167, + 0xCCAD925F, + 0x4DE81751, + 0x3830DC8E, + 0x379D5862, + 0x9320F991, + 0xEA7A90C2, + 0xFB3E7BCE, + 0x5121CE64, + 0x774FBE32, + 0xA8B6E37E, + 0xC3293D46, + 0x48DE5369, + 0x6413E680, + 0xA2AE0810, + 0xDD6DB224, + 0x69852DFD, + 0x09072166, + 0xB39A460A, + 0x6445C0DD, + 0x586CDECF, + 0x1C20C8AE, + 0x5BBEF7DD, + 0x1B588D40, + 0xCCD2017F, + 0x6BB4E3BB, + 0xDDA26A7E, + 0x3A59FF45, + 0x3E350A44, + 0xBCB4CDD5, + 0x72EACEA8, + 0xFA6484BB, + 0x8D6612AE, + 0xBF3C6F47, + 0xD29BE463, + 0x542F5D9E, + 0xAEC2771B, + 0xF64E6370, + 0x740E0D8D, + 0xE75B1357, + 0xF8721671, + 0xAF537D5D, + 0x4040CB08, + 0x4EB4E2CC, + 0x34D2466A, + 0x0115AF84, + 0xE1B00428, + 0x95983A1D, + 0x06B89FB4, + 0xCE6EA048, + 0x6F3F3B82, + 0x3520AB82, + 0x011A1D4B, + 0x277227F8, + 0x611560B1, + 0xE7933FDC, + 0xBB3A792B, + 0x344525BD, + 0xA08839E1, + 0x51CE794B, + 0x2F32C9B7, + 0xA01FBAC9, + 0xE01CC87E, + 0xBCC7D1F6, + 0xCF0111C3, + 0xA1E8AAC7, + 0x1A908749, + 0xD44FBD9A, + 0xD0DADECB, + 0xD50ADA38, + 0x0339C32A, + 0xC6913667, + 0x8DF9317C, + 0xE0B12B4F, + 0xF79E59B7, + 0x43F5BB3A, + 0xF2D519FF, + 0x27D9459C, + 0xBF97222C, + 0x15E6FC2A, + 0x0F91FC71, + 0x9B941525, + 0xFAE59361, + 0xCEB69CEB, + 0xC2A86459, + 0x12BAA8D1, + 0xB6C1075E, + 0xE3056A0C, + 0x10D25065, + 0xCB03A442, + 0xE0EC6E0E, + 0x1698DB3B, + 0x4C98A0BE, + 0x3278E964, + 0x9F1F9532, + 0xE0D392DF, + 0xD3A0342B, + 0x8971F21E, + 0x1B0A7441, + 0x4BA3348C, + 0xC5BE7120, + 0xC37632D8, + 0xDF359F8D, + 0x9B992F2E, + 0xE60B6F47, + 0x0FE3F11D, + 0xE54CDA54, + 0x1EDAD891, + 0xCE6279CF, + 0xCD3E7E6F, + 0x1618B166, + 0xFD2C1D05, + 0x848FD2C5, + 0xF6FB2299, + 0xF523F357, + 0xA6327623, + 0x93A83531, + 0x56CCCD02, + 0xACF08162, + 0x5A75EBB5, + 0x6E163697, + 0x88D273CC, + 0xDE966292, + 0x81B949D0, + 0x4C50901B, + 0x71C65614, + 0xE6C6C7BD, + 0x327A140A, + 0x45E1D006, + 0xC3F27B9A, + 0xC9AA53FD, + 0x62A80F00, + 0xBB25BFE2, + 0x35BDD2F6, + 0x71126905, + 0xB2040222, + 0xB6CBCF7C, + 0xCD769C2B, + 0x53113EC0, + 0x1640E3D3, + 0x38ABBD60, + 0x2547ADF0, + 0xBA38209C, + 0xF746CE76, + 0x77AFA1C5, + 0x20756060, + 0x85CBFE4E, + 0x8AE88DD8, + 0x7AAAF9B0, + 0x4CF9AA7E, + 0x1948C25C, + 0x02FB8A8C, + 0x01C36AE4, + 0xD6EBE1F9, + 0x90D4F869, + 0xA65CDEA0, + 0x3F09252D, + 0xC208E69F, + 0xB74E6132, + 0xCE77E25B, + 0x578FDFE3, + 0x3AC372E6 + }; + + private static final int ROUNDS = 16; + private static final int BLOCK_SIZE = 8; // bytes = 64 bits + private static final int SBOX_SK = 256; + private static final int P_SZ = ROUNDS + 2; + private final int[] S0; // the s-boxes + private final int[] S1; // the s-boxes + private final int[] S2; // the s-boxes + private final int[] S3; // the s-boxes + private final int[] P; // the p-array + private byte[] workingKey = null; + + public BlowfishEngine() + { + S0 = new int[SBOX_SK]; + S1 = new int[SBOX_SK]; + S2 = new int[SBOX_SK]; + S3 = new int[SBOX_SK]; + P = new int[P_SZ]; + } + + /** + * Initialize a Blowfish cipher. + * @param key the key used to set up the cipher + */ + public void init(byte[] key) + { + workingKey = key; + setKey(workingKey); + } + + public String getAlgorithmName() + { + return "Blowfish"; + } + + public int getBlockSize() + { + return BLOCK_SIZE; + } + + private int func(int x) + { + return (((S0[(x >>> 24)] + S1[(x >>> 16) & 0xff]) ^ S2[(x >>> 8) & 0xff]) + S3[x & 0xff]); + } + + /** + * apply the encryption cycle to each value pair in the table. + * @param xl + * @param xr + * @param table + */ + @SuppressWarnings("all") + private void processTable(int xl, int xr, int[] table) + { + final int size = table.length; + for (int s = 0; s < size; s += 2) + { + xl ^= P[0]; + xr ^= func(xl) ^ P[1]; + xl ^= func(xr) ^ P[2]; + xr ^= func(xl) ^ P[3]; + xl ^= func(xr) ^ P[4]; + xr ^= func(xl) ^ P[5]; + xl ^= func(xr) ^ P[6]; + xr ^= func(xl) ^ P[7]; + xl ^= func(xr) ^ P[8]; + xr ^= func(xl) ^ P[9]; + xl ^= func(xr) ^ P[10]; + xr ^= func(xl) ^ P[11]; + xl ^= func(xr) ^ P[12]; + xr ^= func(xl) ^ P[13]; + xl ^= func(xr) ^ P[14]; + xr ^= func(xl) ^ P[15]; + xl ^= func(xr) ^ P[16]; + xr ^= P[17]; + table[s] = xr; + table[s + 1] = xl; + xr = xl; // end of cycle swap + xl = table[s]; + } + } + + private void setKey(byte[] key) + { + /** + * - comments are from _Applied Crypto_, Schneier, p338.
+ * Please be careful comparing the two, AC numbers the arrays from 1, the enclosed code from 0.
+ * (1) Initialize the S-boxes and the P-array, with a fixed string This string contains the hexadecimal digits of pi (3.141...) + */ + System.arraycopy(KS0, 0, S0, 0, SBOX_SK); + System.arraycopy(KS1, 0, S1, 0, SBOX_SK); + System.arraycopy(KS2, 0, S2, 0, SBOX_SK); + System.arraycopy(KS3, 0, S3, 0, SBOX_SK); + System.arraycopy(KP, 0, P, 0, P_SZ); + /** + * (2) Now, XOR P[0] with the first 32 bits of the key, XOR P[1] with the second 32-bits of the key, and so on for all bits of the key (up to P[17]).
+ * Repeatedly cycle through the key bits until the entire P-array has been XOR-ed with the key bits + */ + final int keyLength = key.length; + int keyIndex = 0; + for (int i = 0; i < P_SZ; i++) + { + // get the 32 bits of the key, in 4 * 8 bit chunks + int data = 0x0000000; + for (int j = 0; j < 4; j++) + { + // create a 32 bit block + data = (data << 8) | (key[keyIndex++] & 0xff); + + // wrap when we get to the end of the key + if (keyIndex >= keyLength) + { + keyIndex = 0; + } + } + + // XOR the newly created 32 bit chunk onto the P-array. + P[i] ^= data; + } + /** + * (3) Encrypt the all-zero string with the Blowfish algorithm, using the subkeys described in (1) and (2)
+ * (4) Replace P1 and P2 with the output of step (3)
+ * (5) Encrypt the output of step(3) using the Blowfish algorithm, with the modified subkeys.
+ * (6) Replace P3 and P4 with the output of step (5)
+ * (7) Continue the process, replacing all elements of the P-array and then all four S-boxes in order, with the output of the continuously changing Blowfish algorithm + */ + processTable(0, 0, P); + processTable(P[P_SZ - 2], P[P_SZ - 1], S0); + processTable(S0[SBOX_SK - 2], S0[SBOX_SK - 1], S1); + processTable(S1[SBOX_SK - 2], S1[SBOX_SK - 1], S2); + processTable(S2[SBOX_SK - 2], S2[SBOX_SK - 1], S3); + } + + /** + * Method to encrypt the block at the given index.
+ * The encrypted block goes directly to the source array at the given index. + * @param src source array with the plain data + * @param srcIndex index where the block to encrypt is located + * @throws IllegalStateException The cipher was not yet initialized + * @throws IOException The source array is too small to hold a block at the given index + */ + public void tryEncryptBlock(byte[] src, int srcIndex) throws IOException + { + if (workingKey == null) + { + throw new IllegalStateException("Blowfish not initialized"); + } + + if ((srcIndex + BLOCK_SIZE) > src.length) + { + throw new IOException("input buffer too short"); + } + + encryptBlock(src, srcIndex); + } + + /** + * Method to encrypt the block at the given index.
+ * The encrypted block goes to the destination array at the given index. + * @param src source array with the plain data + * @param srcIndex index where the block to encrypt is located + * @param dst destination array the encryption will go to + * @param dstIndex index where the encrypted block is to be stored + * @throws IllegalStateException The cipher was not yet initialized + * @throws IOException The source or destination array is too small to hold a block at the given index + */ + public void tryEncryptBlock(byte[] src, int srcIndex, byte[] dst, int dstIndex) throws IOException + { + if (workingKey == null) + { + throw new IllegalStateException("Blowfish not initialized"); + } + + if ((srcIndex + BLOCK_SIZE) > src.length) + { + throw new IOException("input buffer too short"); + } + + if ((dstIndex + BLOCK_SIZE) > dst.length) + { + throw new IOException("output buffer too short"); + } + + encryptBlock(src, srcIndex, dst, dstIndex); + } + + /** + * Method to encrypt the block at the given index.
+ * The encrypted block goes directly to the source array at the given
+ * index.
+ *
+ * This method does not perform any error checking. This could be
+ * usefull when code calling this method performs size checks already or
+ * perfroming steps to ensure nothing can go wrong.
+ *
+ * If you want error checking use {@link #tryEncryptBlock(byte[], int)}. + * @param src source array with the plain data + * @param srcIndex index where the block to encrypt is located + */ + public void encryptBlock(byte[] src, int srcIndex) + { + encryptBlock(src, srcIndex, src, srcIndex); + } + + /** + * Method to encrypt the block at the given index.
+ * The encrypted block goes to the destination array at the given index.
+ *
+ * This method does not perform any error checking. This could be
+ * usefull when code calling this method performs size checks already or
+ * perfroming steps to ensure nothing can go wrong.
+ *
+ * If you want error checking use {@link #tryEncryptBlock(byte[], int, byte[], int)}. + * @param src source array with the plain data + * @param srcIndex index where the block to encrypt is located + * @param dst destination array the encryption will go to + * @param dstIndex index where the encrypted block is to be stored + */ + public void encryptBlock(byte[] src, int srcIndex, byte[] dst, int dstIndex) + { + int xl = bytesTo32bits(src, srcIndex); + int xr = bytesTo32bits(src, srcIndex + 4); + xl ^= P[0]; + xr ^= func(xl) ^ P[1]; + xl ^= func(xr) ^ P[2]; + xr ^= func(xl) ^ P[3]; + xl ^= func(xr) ^ P[4]; + xr ^= func(xl) ^ P[5]; + xl ^= func(xr) ^ P[6]; + xr ^= func(xl) ^ P[7]; + xl ^= func(xr) ^ P[8]; + xr ^= func(xl) ^ P[9]; + xl ^= func(xr) ^ P[10]; + xr ^= func(xl) ^ P[11]; + xl ^= func(xr) ^ P[12]; + xr ^= func(xl) ^ P[13]; + xl ^= func(xr) ^ P[14]; + xr ^= func(xl) ^ P[15]; + xl ^= func(xr) ^ P[16]; + xr ^= P[17]; + bits32ToBytes(xr, dst, dstIndex); + bits32ToBytes(xl, dst, dstIndex + 4); + } + + /** + * Method to decrypt the block at the given index.
+ * The decrypted block goes directly to the source array at the given
+ * index. + * @param src source array with the encrypted data + * @param srcIndex index where the block to decrypt is located + * @throws IllegalStateException The cipher was not yet initialized + * @throws IOException The source array is too small to hold a block at the given index + */ + public void tryDecryptBlock(byte[] src, int srcIndex) throws IOException + { + if (workingKey == null) + { + throw new IllegalStateException("Blowfish not initialized"); + } + + if ((srcIndex + BLOCK_SIZE) > src.length) + { + throw new IOException("input buffer too short"); + } + + decryptBlock(src, srcIndex); + } + + /** + * Method to decrypt the block at the given index.
+ * The decrypted block goes to the destination array at the given index.
+ * @param src source array with the plain data + * @param srcIndex index where the block to decrypt is located + * @param dst destination array the decryption will go to + * @param dstIndex index where the decrypted block is to be stored + * @throws IllegalStateException The cipher was not yet initialized + * @throws IOException The source or destination array is too small to hold a block at the given index + */ + public void tryDecryptBlock(byte[] src, int srcIndex, byte[] dst, int dstIndex) throws IOException + { + if (workingKey == null) + { + throw new IllegalStateException("Blowfish not initialized"); + } + + if ((srcIndex + BLOCK_SIZE) > src.length) + { + throw new IOException("input buffer too short"); + } + + if ((dstIndex + BLOCK_SIZE) > src.length) + { + throw new IOException("output buffer too short"); + } + + decryptBlock(src, srcIndex, dst, dstIndex); + } + + /** + * Method to decrypt the block at the given index.
+ * The decrypted block goes directly to the source array at the given
+ * index.
+ *
+ * This method does not perform any error checking. This could be
+ * usefull when code calling this method performs size checks already or
+ * perfroming steps to ensure nothing can go wrong.
+ *
+ * If you want error checking use {@link #tryDecryptBlock(byte[], int)}. + * @param src source array with the encrypted data + * @param srcIndex index where the block to decrypt is located + */ + public void decryptBlock(byte[] src, int srcIndex) + { + decryptBlock(src, srcIndex, src, srcIndex); + } + + /** + * Method to decrypt the block at the given index.
+ * The decrypted block goes to the destination array at the given index.
+ *
+ * This method does not perform any error checking. This could be
+ * usefull when code calling this method performs size checks already or
+ * perfroming steps to ensure nothing can go wrong.
+ *
+ * If you want error checking use {@link #tryDecryptBlock(byte[], int, byte[], int)}. + * @param src source array with the plain data + * @param srcIndex index where the block to decrypt is located + * @param dst destination array the decryption will go to + * @param dstIndex index where the decrypted block is to be stored + * @throws IllegalStateException The cipher was not yet initialized + */ + public void decryptBlock(byte[] src, int srcIndex, byte[] dst, int dstIndex) + { + int xl = bytesTo32bits(src, srcIndex); + int xr = bytesTo32bits(src, srcIndex + 4); + xl ^= P[17]; + xr ^= func(xl) ^ P[16]; + xl ^= func(xr) ^ P[15]; + xr ^= func(xl) ^ P[14]; + xl ^= func(xr) ^ P[13]; + xr ^= func(xl) ^ P[12]; + xl ^= func(xr) ^ P[11]; + xr ^= func(xl) ^ P[10]; + xl ^= func(xr) ^ P[9]; + xr ^= func(xl) ^ P[8]; + xl ^= func(xr) ^ P[7]; + xr ^= func(xl) ^ P[6]; + xl ^= func(xr) ^ P[5]; + xr ^= func(xl) ^ P[4]; + xl ^= func(xr) ^ P[3]; + xr ^= func(xl) ^ P[2]; + xl ^= func(xr) ^ P[1]; + xr ^= P[0]; + bits32ToBytes(xr, dst, dstIndex); + bits32ToBytes(xl, dst, dstIndex + 4); + } + + /** + * Method to construct an int from the source array.
+ * 4 bytes are used from the given index.
+ *
+ * This method does not do any error checking. + * @param src source array with the bytes + * @param srcIndex the index to extract the int from + * @return the extracted integer + */ + private int bytesTo32bits(byte[] src, int srcIndex) + { + return ((src[srcIndex + 3] & 0xff) << 24) | ((src[srcIndex + 2] & 0xff) << 16) | ((src[srcIndex + 1] & 0xff) << 8) | (src[srcIndex] & 0xff); + } + + /** + * Method to decompose an int into a byte array.
+ *
+ * This method does not do any error checking. + * @param in the integer to decompose into bytes + * @param dst the destination array the decomposed int goes to + * @param dstIndex the index in the destination array the decomposed int will be stored at + */ + private void bits32ToBytes(int in, byte[] dst, int dstIndex) + { + dst[dstIndex] = (byte) in; + dst[dstIndex + 1] = (byte) (in >> 8); + dst[dstIndex + 2] = (byte) (in >> 16); + dst[dstIndex + 3] = (byte) (in >> 24); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/crypt/NewCrypt.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/crypt/NewCrypt.java new file mode 100644 index 0000000..006cc50 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/crypt/NewCrypt.java @@ -0,0 +1,242 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.commons.crypt; + +import java.nio.charset.StandardCharsets; + +/** + * Blowfish cipher implementation with ECB processing for L2J packet encryption.
+ * Provides checksum validation and XOR encryption for secure server communication. + *
    + *
  • Packet checksum verification and generation for integrity validation.
  • + *
  • XOR pass encryption for initial login server to game client handshake.
  • + *
  • Blowfish ECB encryption/decryption for ongoing packet security.
  • + *
+ */ +public class NewCrypt +{ + // Constants. + private static final int BYTES_PER_BLOCK = 4; + private static final int CHECKSUM_SIZE = 4; + private static final int BLOWFISH_BLOCK_SIZE = 8; + private static final int XOR_KEY_OFFSET = 4; + private static final int XOR_FINAL_OFFSET = 8; + private static final int BYTE_MASK = 0xFF; + private static final int SHIFT_8_BITS = 8; + private static final int SHIFT_16_BITS = 16; + private static final int SHIFT_24_BITS = 24; + private static final long MASK_16_BITS = 0xFF00; + private static final long MASK_24_BITS = 0xFF0000; + private static final long MASK_32_BITS = 0xFF000000; + + // Encryption Engine. + private final BlowfishEngine _blowfishCipher; + + /** + * Creates new crypt instance with blowfish key bytes. + * @param blowfishKey + */ + public NewCrypt(byte[] blowfishKey) + { + _blowfishCipher = new BlowfishEngine(); + _blowfishCipher.init(blowfishKey); + } + + /** + * Creates new crypt instance with string key converted to bytes. + * @param key + */ + public NewCrypt(String key) + { + this(key.getBytes(StandardCharsets.UTF_8)); + } + + /** + * Verifies packet checksum for entire data array. + * @param rawData data array to be verified + * @return true when the checksum of the data is valid, false otherwise + */ + public static boolean verifyChecksum(byte[] rawData) + { + return verifyChecksum(rawData, 0, rawData.length); + } + + /** + * Verifies packet checksum for data integrity validation.
+ * Used for login server to game client and game server communication. + * @param rawData data array to be verified + * @param offset at which offset to start verifying + * @param size number of bytes to verify + * @return true if the checksum of the data is valid, false otherwise + */ + public static boolean verifyChecksum(byte[] rawData, int offset, int size) + { + // Check if size is multiple of 4 and if there is more than only the checksum. + if (((size & 3) != 0) || (size <= CHECKSUM_SIZE)) + { + return false; + } + + long calculatedChecksum = 0; + final int dataLength = size - CHECKSUM_SIZE; + long currentBlock = -1; + int position; + + for (position = offset; position < dataLength; position += BYTES_PER_BLOCK) + { + currentBlock = rawData[position] & BYTE_MASK; + currentBlock |= (rawData[position + 1] << SHIFT_8_BITS) & MASK_16_BITS; + currentBlock |= (rawData[position + 2] << SHIFT_16_BITS) & MASK_24_BITS; + currentBlock |= (rawData[position + 3] << SHIFT_24_BITS) & MASK_32_BITS; + + calculatedChecksum ^= currentBlock; + } + + currentBlock = rawData[position] & BYTE_MASK; + currentBlock |= (rawData[position + 1] << SHIFT_8_BITS) & MASK_16_BITS; + currentBlock |= (rawData[position + 2] << SHIFT_16_BITS) & MASK_24_BITS; + currentBlock |= (rawData[position + 3] << SHIFT_24_BITS) & MASK_32_BITS; + + return currentBlock == calculatedChecksum; + } + + /** + * Appends packet checksum to entire data array. + * @param rawData data array to compute the checksum from + */ + public static void appendChecksum(byte[] rawData) + { + appendChecksum(rawData, 0, rawData.length); + } + + /** + * Computes and appends packet checksum at the end of the packet. + * @param rawData data array to compute the checksum from + * @param offset offset where to start in the data array + * @param size number of bytes to compute the checksum from + */ + public static void appendChecksum(byte[] rawData, int offset, int size) + { + long calculatedChecksum = 0; + final int dataLength = size - CHECKSUM_SIZE; + long currentBlock; + int position; + + for (position = offset; position < dataLength; position += BYTES_PER_BLOCK) + { + currentBlock = rawData[position] & BYTE_MASK; + currentBlock |= (rawData[position + 1] << SHIFT_8_BITS) & MASK_16_BITS; + currentBlock |= (rawData[position + 2] << SHIFT_16_BITS) & MASK_24_BITS; + currentBlock |= (rawData[position + 3] << SHIFT_24_BITS) & MASK_32_BITS; + + calculatedChecksum ^= currentBlock; + } + + currentBlock = rawData[position] & BYTE_MASK; + currentBlock |= (rawData[position + 1] << SHIFT_8_BITS) & MASK_16_BITS; + currentBlock |= (rawData[position + 2] << SHIFT_16_BITS) & MASK_24_BITS; + currentBlock |= (rawData[position + 3] << SHIFT_24_BITS) & MASK_32_BITS; + + rawData[position] = (byte) (calculatedChecksum & BYTE_MASK); + rawData[position + 1] = (byte) ((calculatedChecksum >> SHIFT_8_BITS) & BYTE_MASK); + rawData[position + 2] = (byte) ((calculatedChecksum >> SHIFT_16_BITS) & BYTE_MASK); + rawData[position + 3] = (byte) ((calculatedChecksum >> SHIFT_24_BITS) & BYTE_MASK); + } + + /** + * Encrypts packet with XOR encoding and appends the XOR key to entire data array.
+ * Assumes sufficient room exists for the key without overwriting data. + * @param rawData The raw bytes to be encrypted + * @param xorKey The 4 bytes (int) XOR key + */ + public static void encXORPass(byte[] rawData, int xorKey) + { + encXORPass(rawData, 0, rawData.length, xorKey); + } + + /** + * Encrypts packet with XOR encoding and appends the XOR key to the data.
+ * Assumes sufficient room exists for the key without overwriting data. + * @param rawData The raw bytes to be encrypted + * @param offset The beginning of the data to be encrypted + * @param size Length of the data to be encrypted + * @param xorKey The 4 bytes (int) XOR key + */ + public static void encXORPass(byte[] rawData, int offset, int size, int xorKey) + { + final int endPosition = size - XOR_FINAL_OFFSET; + int currentPosition = XOR_KEY_OFFSET + offset; + int dataBlock; + int encryptionKey = xorKey; // Initial xor key. + + while (currentPosition < endPosition) + { + dataBlock = rawData[currentPosition] & BYTE_MASK; + dataBlock |= (rawData[currentPosition + 1] & BYTE_MASK) << SHIFT_8_BITS; + dataBlock |= (rawData[currentPosition + 2] & BYTE_MASK) << SHIFT_16_BITS; + dataBlock |= (rawData[currentPosition + 3] & BYTE_MASK) << SHIFT_24_BITS; + + encryptionKey += dataBlock; + + dataBlock ^= encryptionKey; + + rawData[currentPosition++] = (byte) (dataBlock & BYTE_MASK); + rawData[currentPosition++] = (byte) ((dataBlock >> SHIFT_8_BITS) & BYTE_MASK); + rawData[currentPosition++] = (byte) ((dataBlock >> SHIFT_16_BITS) & BYTE_MASK); + rawData[currentPosition++] = (byte) ((dataBlock >> SHIFT_24_BITS) & BYTE_MASK); + } + + rawData[currentPosition++] = (byte) (encryptionKey & BYTE_MASK); + rawData[currentPosition++] = (byte) ((encryptionKey >> SHIFT_8_BITS) & BYTE_MASK); + rawData[currentPosition++] = (byte) ((encryptionKey >> SHIFT_16_BITS) & BYTE_MASK); + rawData[currentPosition++] = (byte) ((encryptionKey >> SHIFT_24_BITS) & BYTE_MASK); + } + + /** + * Decrypts data using Blowfish cipher in ECB mode.
+ * Results are placed directly inside the raw array without error checking. + * @param rawData the data array to be decrypted + * @param offset the offset at which to start decrypting + * @param size the number of bytes to be decrypted + */ + public void decrypt(byte[] rawData, int offset, int size) + { + for (int i = offset; i < (offset + size); i += BLOWFISH_BLOCK_SIZE) + { + _blowfishCipher.decryptBlock(rawData, i); + } + } + + /** + * Encrypts data using Blowfish cipher in ECB mode.
+ * Results are placed directly inside the raw array without error checking. + * @param rawData the data array to be encrypted + * @param offset the offset at which to start encrypting + * @param size the number of bytes to be encrypted + */ + public void crypt(byte[] rawData, int offset, int size) + { + for (int i = offset; i < (offset + size); i += BLOWFISH_BLOCK_SIZE) + { + _blowfishCipher.encryptBlock(rawData, i); + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/database/DatabaseBackup.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/database/DatabaseBackup.java new file mode 100644 index 0000000..8bab73c --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/database/DatabaseBackup.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.commons.database; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.util.concurrent.TimeUnit; + +import org.l2jmobius.commons.config.DatabaseConfig; + +/** + * @author Mobius + */ +public class DatabaseBackup +{ + private static final DateTimeFormatter BACKUP_FORMAT = DateTimeFormatter.ofPattern("_yyyy_MM_dd_HH_mm'.sql'"); + + public static void performBackup(String description) + { + // Delete old files. + if (DatabaseConfig.BACKUP_DAYS > 0) + { + final long cut = LocalDateTime.now().minusDays(DatabaseConfig.BACKUP_DAYS).toEpochSecond(ZoneOffset.UTC); + final Path path = Paths.get(DatabaseConfig.BACKUP_PATH); + try + { + Files.list(path).filter(n -> + { + try + { + return Files.getLastModifiedTime(n).to(TimeUnit.SECONDS) < cut; + } + catch (Exception ex) + { + return false; + } + }).forEach(n -> + { + try + { + Files.delete(n); + } + catch (Exception ex) + { + // Ignore. + } + }); + } + catch (Exception e) + { + // Ignore. + } + } + + // Dump to file. + final String mysqldumpPath = System.getProperty("os.name").toLowerCase().contains("win") ? DatabaseConfig.MYSQL_BIN_PATH : ""; + try + { + // Java 17 + // final Process process = Runtime.getRuntime().exec(mysqldumpPath + "mysqldump -u " + Config.DATABASE_LOGIN + (Config.DATABASE_PASSWORD.trim().isEmpty() ? "" : " -p" + Config.DATABASE_PASSWORD) + " " + // + Config.DATABASE_URL.replace("jdbc:mysql://", "").replaceAll(".*\\/|\\?.*", "") + " -r " + Config.BACKUP_PATH + description + BACKUP_FORMAT.format(LocalDateTime.now())); + // Java 18 + final String backupFileName = DatabaseConfig.BACKUP_PATH + description + BACKUP_FORMAT.format(LocalDateTime.now()); + final String databaseName = DatabaseConfig.DATABASE_URL.replace("jdbc:mysql://", "").replaceAll(".*\\/|\\?.*", ""); + final String[] command = + { + mysqldumpPath + "mysqldump", + "-u", + DatabaseConfig.DATABASE_LOGIN, + DatabaseConfig.DATABASE_PASSWORD.trim().isEmpty() ? "" : "-p" + DatabaseConfig.DATABASE_PASSWORD, + databaseName, + "-r", + backupFileName + }; + + final Process process = Runtime.getRuntime().exec(command); + process.waitFor(); + } + catch (Exception e) + { + // Ignore. + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/database/DatabaseFactory.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/database/DatabaseFactory.java new file mode 100644 index 0000000..be36288 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/database/DatabaseFactory.java @@ -0,0 +1,290 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.commons.database; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; + +import org.l2jmobius.commons.config.DatabaseConfig; + +/** + * DatabaseFactory class using HikariCP for connection pooling.
+ * Configured for high-load environments with 2000-3000 players.
+ * Singleton implementation to ensure a single pool instance. + * @author Mobius + * @since November 10th 2018 + * @version October 16th 2024 + */ +public class DatabaseFactory +{ + private static final Logger LOGGER = Logger.getLogger(DatabaseFactory.class.getName()); + + private static HikariDataSource DATABASE_POOL; + + private DatabaseFactory() + { + } + + /** + * Initializes the HikariCP connection pool with optimized settings.
+ * Ensures that the pool is initialized only once. + */ + public static synchronized void init() + { + if ((DATABASE_POOL != null) && !DATABASE_POOL.isClosed()) + { + LOGGER.warning("Database: Connection pool is already initialized."); + return; + } + + // Load configurations. + DatabaseConfig.load(); + + try + { + final HikariConfig config = new HikariConfig(); + config.setDriverClassName(DatabaseConfig.DATABASE_DRIVER); + config.setJdbcUrl(DatabaseConfig.DATABASE_URL); + config.setUsername(DatabaseConfig.DATABASE_LOGIN); + config.setPassword(DatabaseConfig.DATABASE_PASSWORD); + + // Pool Size Configuration. + config.setMaximumPoolSize(determineMaxPoolSize(DatabaseConfig.DATABASE_MAX_CONNECTIONS)); // 100 + config.setMinimumIdle(determineMinimumIdle(DatabaseConfig.DATABASE_MAX_CONNECTIONS)); // e.g., 20 + + // Timeout Settings. + config.setConnectionTimeout(60000); // 1 minute. + config.setIdleTimeout(300000); // 5 minutes. + config.setMaxLifetime(600000); // 10 minutes. + + // Leak Detection. + config.setLeakDetectionThreshold(600000); // 10 minutes. + + // Pool Name for Identification. + config.setPoolName("L2JMobiusPool"); + + // Register MBeans for Monitoring. + config.setRegisterMbeans(true); + + // Additional Optimizations. + config.setInitializationFailTimeout(-1); + config.setValidationTimeout(5000); // 5 seconds. + + // Initialize HikariDataSource. + DATABASE_POOL = new HikariDataSource(config); + + LOGGER.info("Database: HikariCP pool initialized successfully."); + + if (DatabaseConfig.DATABASE_TEST_CONNECTIONS) + { + testDatabaseConnections(); + } + else + { + testSingleConnection(); + } + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "Database: Failed to initialize HikariCP pool.", e); + } + } + + /** + * Determines the appropriate maximum pool size based on configuration and server capacity. + * @param configuredMax The configured maximum pool size from Config. + * @return Adjusted maximum pool size. + */ + private static int determineMaxPoolSize(int configuredMax) + { + return Math.clamp(configuredMax, 4, 1000); + } + + /** + * Determines the appropriate minimum idle connections based on configuration. + * @param configuredMax The configured maximum pool size from Config. + * @return Adjusted minimum idle connections. + */ + private static int determineMinimumIdle(int configuredMax) + { + return Math.max(determineMaxPoolSize(configuredMax) / 10, 2); + } + + /** + * Tests the database connections by attempting to open the maximum number of connections.
+ * Adjusts the pool size if necessary based on successful connections. + */ + private static void testDatabaseConnections() + { + final List connections = new ArrayList<>(); + int successfulConnections = 0; + + try + { + LOGGER.info("Database: Testing database connections..."); + + for (int i = 0; i < DATABASE_POOL.getMaximumPoolSize(); i++) + { + Connection connection = null; + try + { + connection = DATABASE_POOL.getConnection(); + connections.add(connection); + successfulConnections++; + LOGGER.info("Database: Successfully opened connection " + connection.toString() + "."); + } + catch (SQLException e) + { + LOGGER.log(Level.SEVERE, "Database: Failed to open connection " + (i + 1) + "!", e); + break; + } + } + + if (successfulConnections == DATABASE_POOL.getMaximumPoolSize()) + { + LOGGER.info("Database: Initialized with a total of " + successfulConnections + " connections."); + } + else + { + LOGGER.warning("Database: Only " + successfulConnections + " out of " + DATABASE_POOL.getMaximumPoolSize() + " connections were successful."); + adjustPoolSize(successfulConnections); + } + } + finally // Close all opened connections. + { + for (Connection connection : connections) + { + if (connection != null) + { + try + { + connection.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.SEVERE, "Database: Error closing connection.", e); + } + } + } + } + } + + /** + * Adjusts the pool size based on the number of successful connections. + * @param successfulConnections Number of connections that were successfully opened. + */ + private static void adjustPoolSize(int successfulConnections) + { + LOGGER.warning("Database: Adjusting pool size based on successful connections."); + + // Calculate new pool size, reducing in steps to find a stable number. + int newConnectionCount = successfulConnections; + + if (successfulConnections > 100) + { + newConnectionCount = (successfulConnections / 100) * 100; + } + else if (successfulConnections > 50) + { + newConnectionCount = (successfulConnections / 50) * 50; + } + + // Ensure a minimum pool size of 20. + newConnectionCount = Math.max(newConnectionCount, 20); + + // Update pool configuration. + try + { + DATABASE_POOL.setMaximumPoolSize(newConnectionCount); + DATABASE_POOL.setMinimumIdle(determineMinimumIdle(newConnectionCount)); + LOGGER.info("Database: Reinitialized pool size to " + newConnectionCount + "."); + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "Database: Failed to adjust pool size.", e); + } + } + + /** + * Tests a single connection to verify database connectivity. + */ + private static void testSingleConnection() + { + try (Connection connection = DATABASE_POOL.getConnection()) + { + if (connection.isValid(5)) + { + LOGGER.info("Database: Initialized with a valid connection."); + } + else + { + LOGGER.warning("Database: Connection is not valid."); + } + } + catch (SQLException e) + { + LOGGER.log(Level.SEVERE, "Database: Problem initializing connection pool.", e); + } + } + + /** + * Retrieves a connection from the pool. + * @return A valid database connection. + */ + public static Connection getConnection() + { + try + { + return DATABASE_POOL.getConnection(); + } + catch (SQLException e) + { + LOGGER.log(Level.SEVERE, "Database: Could not get a connection.", e); + throw new RuntimeException("Unable to obtain a database connection.", e); + } + } + + /** + * Closes the HikariCP connection pool gracefully. + */ + public static synchronized void close() + { + if ((DATABASE_POOL != null) && !DATABASE_POOL.isClosed()) + { + try + { + DATABASE_POOL.close(); + LOGGER.info("Database: HikariCP pool closed successfully."); + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "Database: There was a problem closing the data source.", e); + } + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/network/Client.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/network/Client.java new file mode 100644 index 0000000..778db05 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/network/Client.java @@ -0,0 +1,465 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.commons.network; + +import java.util.Collection; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +import org.l2jmobius.commons.network.buffer.ReadBuffer; +import org.l2jmobius.commons.network.buffer.WriteBuffer; +import org.l2jmobius.commons.network.handler.ReadHandler; +import org.l2jmobius.commons.network.handler.WriteHandler; +import org.l2jmobius.commons.network.packet.WritablePacket; +import org.l2jmobius.commons.network.pool.ResourcePool; + +/** + * Abstract client entity that owns a {@link Connection} and manages packet I/O.
+ *
+ * Fair-send mechanism: a single global {@link ConcurrentLinkedQueue} ({@code PENDING_CLIENTS}) is used across all active clients. Whenever a client wants to send it atomically acquires the {@code _writing} flag, then adds itself to the pending queue.
+ * The client polled from the front of the queue sends its next packet. This round-robin approach ensures no single client can monopolise the I/O threads when many clients are active simultaneously.
+ *
+ * Packet dropping: when {@link ConnectionConfig#dropPackets} is enabled, packets that return {@code true} from {@link WritablePacket#canBeDropped} are silently discarded once the estimated outbound queue exceeds {@link ConnectionConfig#dropPacketThreshold}. + * @param the concrete {@link Connection} subtype bound to this client + * @author JoeAlisson, Mobius + */ +public abstract class Client> +{ + /** + * Global fair-send queue shared across all client instances.
+ * Unbounded; clients add themselves when they start sending and are polled by the completing write to find the next sender. + */ + private static final ConcurrentLinkedQueue> PENDING_CLIENTS = new ConcurrentLinkedQueue<>(); + + private final T _connection; + private final Queue>> _packetsToWrite = new ConcurrentLinkedQueue<>(); + private final AtomicBoolean _writing = new AtomicBoolean(); + private final AtomicBoolean _disconnecting = new AtomicBoolean(); + private final AtomicBoolean _closing = new AtomicBoolean(); + private final AtomicInteger _estimateQueueSize = new AtomicInteger(); + private final AtomicInteger _dataSentSize = new AtomicInteger(); + + // Read-side state (accessed only on the single async-read thread for this client). + private boolean _readingPayload; + private int _expectedReadSize; + + /** + * Constructs a client bound to the given connection. + * @param connection the open connection; must not be {@code null} or closed + * @throws IllegalArgumentException if {@code connection} is {@code null} or already closed + */ + protected Client(T connection) + { + if ((connection == null) || !connection.isOpen()) + { + throw new IllegalArgumentException("The connection is null or closed."); + } + + _connection = connection; + } + + /** + * Queues {@code packet} for transmission and triggers the fair-send loop if needed. + * @param packet the packet to send; ignored if {@code null} + */ + protected void writePacket(WritablePacket> packet) + { + if (!isConnected() || (packet == null) || packetCanBeDropped(packet)) + { + return; + } + + _estimateQueueSize.incrementAndGet(); + _packetsToWrite.add(packet); + writeFairPacket(); + } + + /** + * Queues multiple packets and triggers the fair-send loop if needed. + * @param packets the collection of packets to send; ignored if {@code null} or empty + */ + protected void writePackets(Collection>> packets) + { + if (!isConnected() || (packets == null) || packets.isEmpty()) + { + return; + } + + _estimateQueueSize.addAndGet(packets.size()); + _packetsToWrite.addAll(packets); + writeFairPacket(); + } + + /** + * Determines if a packet can be dropped based on the connection's drop packet settings. + * @param packet The packet to check. + * @return True if the packet can be dropped, false otherwise. + */ + @SuppressWarnings( + { + "unchecked", + "rawtypes" + }) + private boolean packetCanBeDropped(WritablePacket packet) + { + return _connection.dropPackets() && (_estimateQueueSize.get() > _connection.dropPacketThreshold()) && packet.canBeDropped(this); + } + + /** + * Enters the send loop for this client if it is not already active. + */ + private void writeFairPacket() + { + if (_writing.compareAndSet(false, true)) + { + sendFairPacket(); + } + } + + /** + * Adds this client to the global pending queue, then polls one client to send its next packet.
+ * The polled client may be {@code this} or any other client waiting to send. + */ + private void sendFairPacket() + { + PENDING_CLIENTS.offer(this); + final Client nextClient = PENDING_CLIENTS.poll(); + if (nextClient != null) + { + nextClient.writeNextPacket(); + } + } + + /** + * Sends the next queued packet, or releases the write lock if the queue is empty. + */ + private void writeNextPacket() + { + final WritablePacket> packet = _packetsToWrite.poll(); + if (packet == null) + { + releaseWritingResource(); + + // A packet may have been queued by another thread between the empty poll above and the release of the writing flag; that thread's CAS failed, so this thread must restart the send loop. + if (!_packetsToWrite.isEmpty()) + { + writeFairPacket(); + return; + } + + if (_closing.get()) + { + // A close(packet) may have queued its final packet concurrently, or another thread may be writing it right now; that thread's completion path will come back here and disconnect. + if (!_packetsToWrite.isEmpty() || _writing.get()) + { + return; + } + + disconnect(); + } + } + else + { + _estimateQueueSize.decrementAndGet(); + write(packet); + } + } + + /** + * Writes a specified packet to the connection. Encrypts the data, writes headers and manages the buffer.
+ * If the packet cannot be written, it handles resource release and retries. + * @param packet The packet to be written. + */ + @SuppressWarnings( + { + "unchecked", + "rawtypes" + }) + private void write(WritablePacket packet) + { + boolean written = false; + WriteBuffer buffer = null; + try + { + buffer = packet.writeData(this); + + final int payloadSize = buffer.limit() - ConnectionConfig.HEADER_SIZE; + if (payloadSize <= 0) + { + return; + } + + if (encrypt(buffer, ConnectionConfig.HEADER_SIZE, payloadSize)) + { + final int bufferLimit = buffer.limit(); + _dataSentSize.set(bufferLimit); + if (bufferLimit <= ConnectionConfig.HEADER_SIZE) + { + return; + } + + packet.writeHeader(buffer, bufferLimit); + written = _connection.write(buffer.toByteBuffers()); + } + } + catch (Exception e) + { + // Intentionally silent - a broken packet must not crash the I/O thread. + } + finally + { + if (!written) + { + handleNotWritten(buffer); + } + } + } + + /** + * Handles scenarios where a packet could not be written successfully.
+ * Releases any associated buffer resources and re-attempts the packet send if the client is still connected. + * @param buffer The buffer containing packet data, which may need resource release. + */ + private void handleNotWritten(WriteBuffer buffer) + { + if (!releaseWritingResource() && (buffer != null)) + { + buffer.releaseResources(); + } + + if (isConnected()) + { + writeFairPacket(); + } + } + + /** + * Starts reading the next packet header from the connection. + */ + public void read() + { + _expectedReadSize = ConnectionConfig.HEADER_SIZE; + _readingPayload = false; + _connection.readHeader(); + } + + /** + * Transitions to payload-reading mode for the given data size. + * @param dataSize the number of payload bytes to read + */ + public void readPayload(int dataSize) + { + _expectedReadSize = dataSize; + _readingPayload = true; + _connection.read(dataSize); + } + + /** + * Closes the connection immediately, discarding all pending outbound packets. + */ + public void close() + { + close(null); + } + + /** + * Sends {@code packet} (if non-null) and then closes the connection.
+ * All other queued packets are discarded. + * @param packet a final packet to transmit before closing, or {@code null} + */ + public void close(WritablePacket> packet) + { + if (!isConnected()) + { + return; + } + + _packetsToWrite.clear(); + if (packet != null) + { + _packetsToWrite.add(packet); + } + + _closing.set(true); + + writeFairPacket(); + } + + /** + * Called by {@link WriteHandler} after a partial write; adjusts the remaining-bytes counter and retries the write with the remainder. + * @param result the number of bytes that were successfully sent + */ + public void resumeSend(int result) + { + _dataSentSize.addAndGet(-result); + _connection.write(); + } + + /** + * Called by {@link WriteHandler} after a complete write; releases buffers and sends the next packet. + */ + public void finishWriting() + { + _connection.releaseWritingBuffer(); + sendFairPacket(); + } + + private boolean releaseWritingResource() + { + final boolean released = _connection.releaseWritingBuffer(); + _writing.set(false); + return released; + } + + /** + * Disconnects the client: calls {@link #onDisconnection()}, clears all pending packets, and closes the underlying channel. Guaranteed to execute at most once. + */ + public void disconnect() + { + if (_disconnecting.compareAndSet(false, true)) + { + try + { + onDisconnection(); + } + finally + { + _packetsToWrite.clear(); + _connection.close(); + } + } + } + + /** + * Returns the connection bound to this client. + * @return the connection + */ + public T getConnection() + { + return _connection; + } + + /** + * Returns the total size of the data being sent in the current write operation. + * @return bytes currently in flight + */ + public int getDataSentSize() + { + return _dataSentSize.get(); + } + + /** + * Returns the remote peer's IP address. + * @return IP string, or an empty string if unavailable + */ + public String getHostAddress() + { + return _connection == null ? "" : _connection.getRemoteAddress(); + } + + /** + * Returns {@code true} if the connection is open and a close has not been requested. + * @return {@code true} if connected + */ + public boolean isConnected() + { + return _connection.isOpen() && !_closing.get(); + } + + /** + * Returns the estimated number of packets waiting in the outbound queue. + * @return estimated queue depth + */ + public int getEstimateQueueSize() + { + return _estimateQueueSize.get(); + } + + /** + * Returns the {@link ResourcePool} used by the underlying connection. + * @return the resource pool + */ + public ResourcePool getResourcePool() + { + return _connection.getResourcePool(); + } + + /** + * Returns {@code true} when the client is in the middle of reading a packet payload (as opposed to reading a header). + * @return {@code true} if reading payload + */ + public boolean isReadingPayload() + { + return _readingPayload; + } + + /** + * Called by {@link ReadHandler} after a partial read; subtracts the bytes already received and issues another read for the remainder. + * @param bytesRead the number of bytes received in the partial read + */ + public void resumeRead(int bytesRead) + { + _expectedReadSize -= bytesRead; + _connection.read(); + } + + /** + * Returns the number of bytes still expected from the current read operation. + * @return remaining expected bytes + */ + public int getExpectedReadSize() + { + return _expectedReadSize; + } + + /** + * Encrypts the packet data in-place within {@code data}. + * @param data the outbound buffer containing the data to encrypt + * @param offset byte offset at which the encryptable region starts + * @param size number of bytes to encrypt + * @return {@code true} if encryption succeeded; {@code false} to abort sending + */ + public abstract boolean encrypt(WriteBuffer data, int offset, int size); + + /** + * Decrypts the packet data in-place within {@code data}. + * @param data the inbound buffer containing the data to decrypt + * @param offset byte offset at which the decryptable region starts + * @param size number of bytes to decrypt + * @return {@code true} if decryption succeeded; {@code false} to abort processing + */ + public abstract boolean decrypt(ReadBuffer data, int offset, int size); + + /** + * Called once when the client disconnects.
+ * Implementations must persist state and release all application-level resources.
+ * No further packets can be sent after this method returns. + */ + protected abstract void onDisconnection(); + + /** + * Called once immediately after the connection is accepted.
+ * Implementations should not block; outbound packets may be sent from this method onward. + */ + public abstract void onConnected(); +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/network/Connection.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/network/Connection.java new file mode 100644 index 0000000..919bbe8 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/network/Connection.java @@ -0,0 +1,302 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.commons.network; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.nio.ByteBuffer; +import java.nio.channels.AsynchronousSocketChannel; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.l2jmobius.commons.network.handler.ReadHandler; +import org.l2jmobius.commons.network.handler.WriteHandler; +import org.l2jmobius.commons.network.pool.ResourcePool; + +/** + * Manages a single network connection backed by an {@link AsynchronousSocketChannel}.
+ *
+ * Owns the lifecycle of both the read buffer (single {@link ByteBuffer} obtained from the {@link ResourcePool}) and the write buffer array (supplied per-packet by the {@link Client}).
+ * All asynchronous I/O is dispatched from here; completion logic lives in {@link ReadHandler} and {@link WriteHandler}.
+ *
+ * Thread-safety: the {@code _client} reference is declared {@code volatile} because it is read by NIO completion-handler threads.
+ * An {@link AtomicBoolean} guards {@link #close()} against double-close races that can occur when a read failure and a write failure fire concurrently. + * @param the client type associated with this connection + * @author JoeAlisson, Mobius + */ +public class Connection>> +{ + private final AsynchronousSocketChannel _channel; + private final ReadHandler _readHandler; + private final WriteHandler _writeHandler; + private final ConnectionConfig _config; + + /** Volatile because NIO completion-handler threads read this field. */ + private volatile T _client; + + /** Ensures {@link #close()} body executes at most once. */ + private final AtomicBoolean _closed = new AtomicBoolean(); + + private ByteBuffer _readingBuffer; + private ByteBuffer[] _writingBuffers; + + /** + * Creates a new connection around an already-accepted channel. + * @param channel the open async socket channel + * @param readHandler completion handler for read operations + * @param writeHandler completion handler for write operations + * @param config shared configuration (resource pool, thresholds, etc.) + */ + public Connection(AsynchronousSocketChannel channel, ReadHandler readHandler, WriteHandler writeHandler, ConnectionConfig config) + { + _channel = channel; + _readHandler = readHandler; + _writeHandler = writeHandler; + _config = config; + } + + /** + * Binds a client to this connection. Must be called exactly once, immediately after construction. + * @param client the owning client + */ + public void setClient(T client) + { + _client = client; + } + + // ------------------------------------------------------------------ + // Read operations. + // ------------------------------------------------------------------ + + /** + * Submits an async read into the current reading buffer.
+ * No-op if the channel has already been closed. + */ + public void read() + { + if (_channel.isOpen()) + { + _channel.read(_readingBuffer, _client, _readHandler); + } + } + + /** + * Prepares for the next packet by recycling the old reading buffer, obtaining a fresh header-sized buffer from the pool and starting a read. + */ + public void readHeader() + { + if (_channel.isOpen()) + { + recycleReadBuffer(); + _readingBuffer = _config.resourcePool.getHeaderBuffer(); + read(); + } + } + + /** + * Swaps the reading buffer for one that can hold {@code size} bytes and starts a read. + * @param size required capacity in bytes + */ + public void read(int size) + { + if (_channel.isOpen()) + { + _readingBuffer = _config.resourcePool.recycleAndGetNew(_readingBuffer, size); + read(); + } + } + + /** + * Returns the buffer that is currently receiving inbound data. + * @return the reading buffer, or {@code null} between packets + */ + public ByteBuffer getReadingBuffer() + { + return _readingBuffer; + } + + // ------------------------------------------------------------------ + // Write operations. + // ------------------------------------------------------------------ + + /** + * Begins a gather-write of the supplied buffers to the channel. + * @param buffers one or more buffers containing the outgoing packet data + * @return {@code true} if the write was submitted; {@code false} if the channel is closed + */ + public boolean write(ByteBuffer[] buffers) + { + if (!_channel.isOpen()) + { + return false; + } + + _writingBuffers = buffers; + write(); + return true; + } + + /** + * Continues (or completes) a previously started gather-write.
+ * If the channel is closed or no buffers remain, signals the client via {@link Client#finishWriting()} so it can dequeue the next packet. + */ + public void write() + { + if (_channel.isOpen() && (_writingBuffers != null)) + { + _channel.write(_writingBuffers, 0, _writingBuffers.length, -1, TimeUnit.MILLISECONDS, _client, _writeHandler); + } + else if (_client != null) + { + _client.finishWriting(); + } + } + + /** + * Returns every write buffer to the resource pool. + * @return {@code true} if at least one buffer was recycled + */ + public boolean releaseWritingBuffer() + { + final ByteBuffer[] buffers = _writingBuffers; + if (buffers == null) + { + return false; + } + + _writingBuffers = null; + for (ByteBuffer buf : buffers) + { + _config.resourcePool.recycleBuffer(buf); + } + + return true; + } + + // ------------------------------------------------------------------ + // Lifecycle. + // ------------------------------------------------------------------ + + /** + * Closes the connection, releasing all pooled buffers and shutting down the channel.
+ * Safe to call from multiple threads - only the first invocation performs cleanup. + */ + public void close() + { + if (!_closed.compareAndSet(false, true)) + { + return; + } + + recycleReadBuffer(); + releaseWritingBuffer(); + + try + { + if (_channel.isOpen()) + { + _channel.close(); + } + } + catch (IOException ignored) + { + // Channel was already broken - nothing useful to do. + } + finally + { + _client = null; + } + } + + /** + * Returns {@code true} if the underlying channel is still open. + * @return channel open state + */ + public boolean isOpen() + { + return _channel.isOpen(); + } + + // ------------------------------------------------------------------ + // Accessors. + // ------------------------------------------------------------------ + + /** + * Resolves the remote peer's IP address. + * @return dotted-quad (or IPv6) address string, or {@code ""} if the channel is closed + */ + public String getRemoteAddress() + { + try + { + final InetSocketAddress remote = (InetSocketAddress) _channel.getRemoteAddress(); + return remote.getAddress().getHostAddress(); + } + catch (IOException e) + { + return ""; + } + } + + /** + * Returns the {@link ResourcePool} shared by all connections on this acceptor. + * @return the resource pool + */ + public ResourcePool getResourcePool() + { + return _config.resourcePool; + } + + /** + * Indicates whether the server is configured to silently drop expendable packets when a client's outbound queue grows too large. + * @return {@code true} if packet dropping is enabled + */ + public boolean dropPackets() + { + return _config.dropPackets; + } + + /** + * Returns the queue depth at which expendable packets begin to be dropped. + * @return the drop threshold + */ + public int dropPacketThreshold() + { + return _config.dropPacketThreshold; + } + + // ------------------------------------------------------------------ + // Internal helpers. + // ------------------------------------------------------------------ + + /** + * Returns the current reading buffer to the pool and nulls the reference. + */ + private void recycleReadBuffer() + { + final ByteBuffer buf = _readingBuffer; + if (buf != null) + { + _readingBuffer = null; + _config.resourcePool.recycleBuffer(buf); + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/network/ConnectionConfig.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/network/ConnectionConfig.java new file mode 100644 index 0000000..4fc5ec1 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/network/ConnectionConfig.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.commons.network; + +import java.net.SocketAddress; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.l2jmobius.commons.network.pool.BufferPool; +import org.l2jmobius.commons.network.pool.ResourcePool; +import org.l2jmobius.commons.util.ConfigReader; + +/** + * Configures and initializes connection parameters for the network layer.
+ * This class handles configuration settings, buffer pools and network properties. + * @author Mobius + */ +public class ConnectionConfig +{ + public static final int HEADER_SIZE = 2; + + private static final Pattern BUFFER_POOL_PROPERTY = Pattern.compile("(BufferPool\\.\\w+?\\.)Size", Pattern.CASE_INSENSITIVE); + private static final int MINIMUM_POOL_GROUPS = 3; + + public ResourcePool resourcePool; + public SocketAddress address; + + public float initBufferPoolFactor; + public long shutdownWaitTime; + public int threadPoolSize; + public boolean useNagle; + public boolean dropPackets; + public int dropPacketThreshold; + public int threadPriority; + public boolean autoExpandPoolCapacity; + + /** + * Initializes the connection configuration with the specified socket address. + * @param socketAddress the address to which this configuration applies + */ + public ConnectionConfig(SocketAddress socketAddress) + { + address = socketAddress; + threadPoolSize = 2; + + // Initialize Resource Pool and default buffer settings. + resourcePool = new ResourcePool(); + resourcePool.addBufferPool(HEADER_SIZE, new BufferPool(100, HEADER_SIZE)); + + // Read configuration properties. + final ConfigReader networkConfig = new ConfigReader("config/Network.ini"); + shutdownWaitTime = networkConfig.getInt("ShutdownWaitTime", 5) * 1000L; + + // Configure thread pool based on processor count. + final int processors = Runtime.getRuntime().availableProcessors(); + threadPoolSize = networkConfig.getInt("ThreadPoolSize", threadPoolSize); + threadPoolSize = threadPoolSize < 1 ? processors * 4 : threadPoolSize; + + // Other network and buffer configurations. + threadPriority = networkConfig.getInt("ThreadPriority", Thread.NORM_PRIORITY); + autoExpandPoolCapacity = networkConfig.getBoolean("BufferPool.AutoExpandCapacity", true); + initBufferPoolFactor = networkConfig.getFloat("BufferPool.InitFactor", 0); + dropPackets = networkConfig.getBoolean("DropPackets", dropPackets); + dropPacketThreshold = networkConfig.getInt("DropPacketThreshold", 250); + resourcePool.setBufferSegmentSize(networkConfig.getInt("BufferSegmentSize", resourcePool.getSegmentSize())); + + // Set up custom buffer pools from properties. + networkConfig.getStringPropertyNames().forEach(property -> + { + final Matcher matcher = BUFFER_POOL_PROPERTY.matcher(property); + if (matcher.matches()) + { + final int size = networkConfig.getInt(property, 10); + final int bufferSize = networkConfig.getInt(matcher.group(1) + "BufferSize", 1024); + resourcePool.addBufferPool(bufferSize, new BufferPool(size, bufferSize)); + } + }); + + // Add additional buffer pool for segment size. + resourcePool.addBufferPool(resourcePool.getSegmentSize(), new BufferPool(100, resourcePool.getSegmentSize())); + + // Ensure minimum pool groups. + final int missingPools = MINIMUM_POOL_GROUPS - resourcePool.bufferPoolSize(); + for (int i = 0; i < missingPools; i++) + { + final int bufferSize = 256 << i; + resourcePool.addBufferPool(bufferSize, new BufferPool(10, bufferSize)); + } + + // Initialize resource pool buffers. + resourcePool.initializeBuffers(autoExpandPoolCapacity, initBufferPoolFactor); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/network/ConnectionManager.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/network/ConnectionManager.java new file mode 100644 index 0000000..752d714 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/network/ConnectionManager.java @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.commons.network; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.StandardSocketOptions; +import java.nio.channels.AsynchronousChannelGroup; +import java.nio.channels.AsynchronousServerSocketChannel; +import java.nio.channels.AsynchronousSocketChannel; +import java.nio.channels.ClosedChannelException; +import java.nio.channels.CompletionHandler; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.function.Function; + +import org.l2jmobius.commons.network.handler.ReadHandler; +import org.l2jmobius.commons.network.handler.WriteHandler; +import org.l2jmobius.commons.network.packet.PacketExecutor; +import org.l2jmobius.commons.network.packet.PacketHandler; +import org.l2jmobius.commons.network.packet.PacketThreadFactory; +import org.l2jmobius.commons.network.packet.ReadablePacket; + +/** + * Binds a server socket, accepts incoming TCP connections and initialises a {@link Client} for each one via a caller-supplied factory.
+ *
+ * The underlying {@link AsynchronousChannelGroup} uses a bounded thread pool sized by {@link ConnectionConfig#threadPoolSize}. A {@link LinkedBlockingQueue} work queue allows the pool to buffer short bursts of connection-accept events without spawning unbounded threads. + * @param the concrete client type created for each accepted connection + * @author Mobius + */ +public class ConnectionManager>> +{ + private final AsynchronousChannelGroup _group; + private final AsynchronousServerSocketChannel _socketChannel; + private final ConnectionConfig _config; + private final WriteHandler _writeHandler; + private final ReadHandler _readHandler; + private final Function, T> _clientFactory; + + /** + * Binds the server to {@code address} and starts accepting connections. + * @param address the local address and port to listen on + * @param clientFactory function that creates a {@link Client} for a given {@link Connection} + * @param packetHandler handler that maps incoming bytes to concrete {@link ReadablePacket}s + * @throws IOException if the server socket cannot be opened or bound + */ + public ConnectionManager(InetSocketAddress address, Function, T> clientFactory, PacketHandler packetHandler) throws IOException + { + _config = new ConnectionConfig(address); + _clientFactory = clientFactory; + _readHandler = new ReadHandler<>(packetHandler, new PacketExecutor<>(_config)); + _writeHandler = new WriteHandler<>(); + + // Bounded thread pool: core == max == threadPoolSize; idle threads kept alive for 1 minute. + // LinkedBlockingQueue buffers short accept bursts without spawning extra threads. + final ThreadPoolExecutor threadPool = new ThreadPoolExecutor(_config.threadPoolSize, _config.threadPoolSize, 1, TimeUnit.MINUTES, new LinkedBlockingQueue<>(), new PacketThreadFactory("Server", _config.threadPriority)); + threadPool.allowCoreThreadTimeOut(true); + + _group = AsynchronousChannelGroup.withThreadPool(threadPool); + _socketChannel = _group.provider().openAsynchronousServerSocketChannel(_group); + _socketChannel.setOption(StandardSocketOptions.SO_REUSEADDR, true); + _socketChannel.bind(_config.address); + + // Begin the accept loop. + _socketChannel.accept(null, new AcceptConnectionHandler()); + } + + /** + * Stops accepting new connections and shuts down the channel group.
+ * Waits up to {@link ConnectionConfig#shutdownWaitTime} ms for in-flight I/O to complete. + */ + public void shutdown() + { + try + { + _socketChannel.close(); + _group.shutdown(); + _group.awaitTermination(_config.shutdownWaitTime, TimeUnit.MILLISECONDS); + _group.shutdownNow(); + } + catch (InterruptedException e) + { + Thread.currentThread().interrupt(); + } + catch (Exception e) + { + // Best-effort shutdown; errors here are not actionable. + } + } + + private class AcceptConnectionHandler implements CompletionHandler + { + @Override + public void completed(AsynchronousSocketChannel clientChannel, Void attachment) + { + // Re-arm the accept loop before processing the new channel so that further connections are not delayed by client initialisation. + if (_socketChannel.isOpen()) + { + _socketChannel.accept(null, this); + } + + processNewConnection(clientChannel); + } + + @Override + public void failed(Throwable t, Void attachment) + { + // Re-arm even on failure so transient errors do not kill the accept loop. + if (_socketChannel.isOpen()) + { + _socketChannel.accept(null, this); + } + } + + private void processNewConnection(AsynchronousSocketChannel channel) + { + if ((channel == null) || !channel.isOpen()) + { + return; + } + + try + { + channel.setOption(StandardSocketOptions.TCP_NODELAY, !_config.useNagle); + + final Connection connection = new Connection<>(channel, _readHandler, _writeHandler, _config); + final T client = _clientFactory.apply(connection); + connection.setClient(client); + + client.onConnected(); + client.read(); + } + catch (ClosedChannelException e) + { + // Channel was closed between accept and setup - nothing to do. + } + catch (Exception e) + { + try + { + channel.close(); + } + catch (IOException ioe) + { + // Ignore; channel is already unusable. + } + } + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/network/buffer/ReadBuffer.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/network/buffer/ReadBuffer.java new file mode 100644 index 0000000..5aa9d63 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/network/buffer/ReadBuffer.java @@ -0,0 +1,216 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.commons.network.buffer; + +import java.nio.BufferUnderflowException; +import java.nio.ByteBuffer; +import java.util.Objects; + +/** + * Inbound network buffer backed by a single NIO {@link ByteBuffer} handed in by the read handler.
+ *
+ * Provides sequential cursor-based reads (advancing the buffer's native position) plus absolute indexed accessors (used during decryption).
+ *
+ * All multi-byte values are read in little-endian order, matching the protocol convention. The wrapped buffer's byte order and position are used as-is; its limit defines the end of the decrypted payload. + * @author Mobius + */ +public class ReadBuffer +{ + private final ByteBuffer _buf; + + /** + * Wraps the supplied buffer. Its byte order and position are used as-is. + * @param buffer the NIO buffer to wrap (must not be {@code null}) + * @throws NullPointerException if {@code buffer} is {@code null} + */ + public ReadBuffer(ByteBuffer buffer) + { + _buf = Objects.requireNonNull(buffer); + } + + /** + * Factory that wraps a standard NIO {@link ByteBuffer} as a {@link ReadBuffer}. + * @param buffer the NIO buffer to adapt + * @return a {@link ReadBuffer} backed by {@code buffer} + */ + public static ReadBuffer of(ByteBuffer buffer) + { + return new ReadBuffer(buffer); + } + + // ------------------------------------------------------------------------- + // Sequential reads (cursor-based). + // ------------------------------------------------------------------------- + + /** + * Reads a little-endian UTF-16 {@code char} (2 bytes) and advances the position by 2. + * @return the char value + */ + public char readChar() + { + return _buf.getChar(); + } + + /** + * Reads a single byte and advances the position by 1. + * @return the byte value + */ + public byte readByte() + { + return _buf.get(); + } + + /** + * Reads a little-endian {@code short} (2 bytes) and advances the position by 2. + * @return the short value + */ + public short readShort() + { + return _buf.getShort(); + } + + /** + * Reads a little-endian {@code int} (4 bytes) and advances the position by 4. + * @return the int value + */ + public int readInt() + { + return _buf.getInt(); + } + + /** + * Reads a little-endian {@code long} (8 bytes) and advances the position by 8. + * @return the long value + */ + public long readLong() + { + return _buf.getLong(); + } + + /** + * Reads a little-endian IEEE 754 {@code float} (4 bytes) and advances the position by 4. + * @return the float value + */ + public float readFloat() + { + return _buf.getFloat(); + } + + /** + * Reads a little-endian IEEE 754 {@code double} (8 bytes) and advances the position by 8. + * @return the double value + */ + public double readDouble() + { + return _buf.getDouble(); + } + + /** + * Allocates a new array of the given length, fills it from the current position, and advances the position by {@code length}. + * @param length number of bytes to read + * @return newly allocated array containing the bytes + */ + public byte[] readBytes(int length) + { + // Validate before allocating, so a forged length cannot exhaust the heap. + if ((length < 0) || (length > _buf.remaining())) + { + throw new BufferUnderflowException(); + } + + final byte[] dst = new byte[length]; + _buf.get(dst); + return dst; + } + + /** + * Reads exactly {@code dst.length} bytes into the supplied array and advances the position. + * @param dst destination array + */ + public void readBytes(byte[] dst) + { + _buf.get(dst); + } + + /** + * Reads {@code length} bytes into {@code dst} starting at {@code offset} and advances the position. + * @param dst destination array + * @param offset start index within {@code dst} + * @param length number of bytes to transfer + */ + public void readBytes(byte[] dst, int offset, int length) + { + _buf.get(dst, offset, length); + } + + /** + * Returns the number of bytes between the current position and the limit. + * @return remaining bytes available for reading + */ + public int remaining() + { + return _buf.remaining(); + } + + // ------------------------------------------------------------------------- + // Indexed (absolute) access - used during decryption. + // ------------------------------------------------------------------------- + + public byte readByte(int index) + { + return _buf.get(index); + } + + public short readShort(int index) + { + return _buf.getShort(index); + } + + public int readInt(int index) + { + return _buf.getInt(index); + } + + public void writeByte(int index, byte value) + { + _buf.put(index, value); + } + + public void writeShort(int index, short value) + { + _buf.putShort(index, value); + } + + public void writeInt(int index, int value) + { + _buf.putInt(index, value); + } + + public int limit() + { + return _buf.limit(); + } + + public void limit(int newLimit) + { + _buf.limit(newLimit); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/network/buffer/WriteBuffer.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/network/buffer/WriteBuffer.java new file mode 100644 index 0000000..449c376 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/network/buffer/WriteBuffer.java @@ -0,0 +1,424 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.commons.network.buffer; + +import java.nio.ByteBuffer; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.l2jmobius.commons.network.pool.ResourcePool; + +/** + * Outbound network buffer backed by a single pooled direct {@link ByteBuffer} from a {@link ResourcePool}.
+ *
+ * Provides sequential cursor-based writes (advancing an internal position) plus absolute indexed writes (used for header and encryption patches).
+ *
+ * All multi-byte values are stored in little-endian order, matching the protocol convention.
+ *
+ * Growth: when a write would exceed the current capacity, a larger buffer is acquired from the pool, the existing bytes are copied into it, and the old buffer is returned to the pool.
+ *
+ * Per-class size hints: the maximum observed {@code limit()} for each packet class is tracked so that the initial allocation for subsequent sends is right-sized on the first try, avoiding realloc cycles.
+ *
+ * Broadcast cache: {@link #toByteArray()} snapshots the written content for reuse; {@link #WriteBuffer(byte[], int, ResourcePool, Class)} creates a per-recipient copy seeded from that snapshot. + * @author Mobius + */ +public class WriteBuffer +{ + /** Per-packet-class largest observed packet size, used to right-size the initial pooled buffer. */ + private static final Map, Integer> MAXIMUM_PACKET_SIZE = new ConcurrentHashMap<>(); + + private final ResourcePool _resourcePool; + private final Class _packetClass; + private final int _initialSize; + + private ByteBuffer _buf; + private int _position; + private int _limit; + + /** + * Creates an empty buffer sized to the historical maximum for the given packet class, falling back to the pool's segment size. + * @param resourcePool pool for acquiring direct {@link ByteBuffer}s + * @param packetClass the packet class; used for size-hint tracking + */ + public WriteBuffer(ResourcePool resourcePool, Class packetClass) + { + _resourcePool = resourcePool; + _packetClass = packetClass; + _initialSize = MAXIMUM_PACKET_SIZE.getOrDefault(packetClass, resourcePool.getSegmentSize()); + _buf = resourcePool.getBuffer(_initialSize); + _limit = _buf.capacity(); + } + + /** + * Creates a buffer seeded from a broadcast-cache snapshot.
+ * The pooled buffer is sized to hold {@code cachedLength} bytes, the snapshot is bulk-copied in, and the logical limit is set to {@code cachedLength}. + * @param cached the byte snapshot produced by {@link #toByteArray()} + * @param cachedLength the number of bytes in {@code cached} that are meaningful + * @param resourcePool pool for acquiring direct {@link ByteBuffer}s + * @param packetClass the packet class; used for size-hint tracking + */ + public WriteBuffer(byte[] cached, int cachedLength, ResourcePool resourcePool, Class packetClass) + { + _resourcePool = resourcePool; + _packetClass = packetClass; + _initialSize = MAXIMUM_PACKET_SIZE.getOrDefault(packetClass, Math.max(cachedLength, resourcePool.getSegmentSize())); + _buf = resourcePool.getBuffer(Math.max(_initialSize, cachedLength)); + _buf.position(0); + _buf.put(cached, 0, cachedLength); + _buf.position(0); + _position = cachedLength; + _limit = cachedLength; + } + + /** + * Grows the underlying buffer if it cannot hold at least {@code required} bytes.
+ * Growth factor is 1.5x, clamped to at least {@code required}. + * @param required the minimum capacity needed + */ + private void ensureSize(int required) + { + final int capacity = _buf.capacity(); + if (capacity < required) + { + final int newSize = Math.max(required, (int) (capacity * 1.5)); + final ByteBuffer newBuf = _resourcePool.getBuffer(newSize); + newBuf.position(0); + _buf.position(0).limit(Math.min(_position, capacity)); + newBuf.put(_buf); + newBuf.position(0); + _resourcePool.recycleBuffer(_buf); + _buf = newBuf; + } + } + + // ------------------------------------------------------------------------- + // Sequential writes (cursor-based). + // ------------------------------------------------------------------------- + + /** + * Writes a single byte at the current position. + * @param value the byte to write + */ + public void writeByte(byte value) + { + ensureSize(_position + 1); + _buf.put(_position++, value); + } + + /** + * Writes the lowest 8 bits of the given {@code int} as a single byte. + * @param value the value whose low byte is written + */ + public void writeByte(int value) + { + writeByte((byte) value); + } + + /** + * Writes a boolean as a single byte - {@code 0x01} for {@code true}, {@code 0x00} for {@code false}. + * @param value the boolean to encode + */ + public void writeByte(boolean value) + { + writeByte((byte) (value ? 1 : 0)); + } + + /** + * Writes a variable-length byte sequence at the current position. + * @param value the bytes to write + */ + public void writeBytes(byte... value) + { + if ((value == null) || (value.length == 0)) + { + return; + } + + ensureSize(_position + value.length); + _buf.put(_position, value, 0, value.length); + _position += value.length; + } + + /** + * Writes a little-endian {@code short} (2 bytes) at the current position. + * @param value the short to write + */ + public void writeShort(short value) + { + ensureSize(_position + 2); + _buf.putShort(_position, value); + _position += 2; + } + + /** + * Writes the lowest 16 bits of the given {@code int} as a little-endian short. + * @param value the value whose low 16 bits are written + */ + public void writeShort(int value) + { + writeShort((short) value); + } + + /** + * Writes a boolean as a little-endian short - {@code 1} for {@code true}, {@code 0} for {@code false}. + * @param value the boolean to encode + */ + public void writeShort(boolean value) + { + writeShort((short) (value ? 1 : 0)); + } + + /** + * Writes a little-endian {@code int} (4 bytes) at the current position. + * @param value the int to write + */ + public void writeInt(int value) + { + ensureSize(_position + 4); + _buf.putInt(_position, value); + _position += 4; + } + + /** + * Writes a boolean as a little-endian int - {@code 1} for {@code true}, {@code 0} for {@code false}. + * @param value the boolean to encode + */ + public void writeInt(boolean value) + { + writeInt(value ? 1 : 0); + } + + /** + * Writes a little-endian {@code long} (8 bytes) at the current position. + * @param value the long to write + */ + public void writeLong(long value) + { + ensureSize(_position + 8); + _buf.putLong(_position, value); + _position += 8; + } + + /** + * Writes a little-endian IEEE 754 {@code float} (4 bytes) at the current position. + * @param value the float to write + */ + public void writeFloat(float value) + { + writeInt(Float.floatToRawIntBits(value)); + } + + /** + * Writes a little-endian IEEE 754 {@code double} (8 bytes) at the current position. + * @param value the double to write + */ + public void writeDouble(double value) + { + writeLong(Double.doubleToRawLongBits(value)); + } + + /** + * Writes a little-endian UTF-16 {@code char} (2 bytes) at the current position. + * @param value the character to write + */ + public void writeChar(char value) + { + writeShort((short) value); + } + + /** + * Encodes a string as null-terminated UTF-16LE.
+ * A {@code null} input produces only the null terminator (2 zero bytes). + * @param text the string to write, or {@code null} + */ + public void writeString(CharSequence text) + { + if (text != null) + { + final int len = text.length(); + ensureSize(_position + (len << 1) + 2); + for (int i = 0; i < len; i++) + { + _buf.putShort(_position, (short) text.charAt(i)); + _position += 2; + } + + // Null terminator - capacity already reserved above. + _buf.putShort(_position, (short) 0); + _position += 2; + return; + } + + writeChar('\000'); + } + + /** + * Encodes a string as a length-prefixed UTF-16LE sequence (no null terminator). + * @param text the string to write, or {@code null} + */ + public void writeSizedString(CharSequence text) + { + if ((text != null) && !text.isEmpty()) + { + final int len = text.length(); + ensureSize(_position + 2 + (len << 1)); + _buf.putShort(_position, (short) len); + _position += 2; + for (int i = 0; i < len; i++) + { + _buf.putShort(_position, (short) text.charAt(i)); + _position += 2; + } + return; + } + + writeShort(0); + } + + // ------------------------------------------------------------------------- + // Indexed (absolute) writes and reads - used for header patching and encryption. + // ------------------------------------------------------------------------- + + public void writeByte(int index, byte value) + { + ensureSize(index + 1); + _buf.put(index, value); + } + + public void writeShort(int index, short value) + { + ensureSize(index + 2); + _buf.putShort(index, value); + } + + public void writeInt(int index, int value) + { + ensureSize(index + 4); + _buf.putInt(index, value); + } + + public byte readByte(int index) + { + return _buf.get(index); + } + + public short readShort(int index) + { + return _buf.getShort(index); + } + + public int readInt(int index) + { + return _buf.getInt(index); + } + + // ------------------------------------------------------------------------- + // Position / limit / mark. + // ------------------------------------------------------------------------- + + /** + * Returns the current sequential write position. + * @return write cursor + */ + public int position() + { + return _position; + } + + /** + * Moves the sequential write cursor. + * @param pos the new position + */ + public void position(int pos) + { + _position = pos; + } + + public int limit() + { + return _limit; + } + + public void limit(int newLimit) + { + ensureSize(newLimit); + _limit = newLimit; + } + + /** + * Marks the current write position as the logical end of the buffer's content, setting the limit to the current position. + */ + public void mark() + { + _limit = _position; + } + + // ------------------------------------------------------------------------- + // Export / recycle. + // ------------------------------------------------------------------------- + + /** + * Prepares the underlying {@link ByteBuffer} for a channel write and exports it as a single-element array.
+ * Updates the per-class size hint if this packet was larger than previously seen. + * @return a single-element array containing the underlying buffer, positioned at 0 with limit set to the logical packet length + */ + public ByteBuffer[] toByteBuffers() + { + if (_limit > _initialSize) + { + MAXIMUM_PACKET_SIZE.put(_packetClass, Math.min(_limit, 65535)); + } + + _buf.position(0); + _buf.limit(_limit); + return new ByteBuffer[] + { + _buf + }; + } + + /** + * Snapshots the written bytes as a fresh heap {@code byte[]} for broadcast caching.
+ * The returned array has length {@link #limit()} and contains exactly the logical packet content. + * @return a new heap byte array holding the packet data + */ + public byte[] toByteArray() + { + final byte[] snapshot = new byte[_limit]; + _buf.get(0, snapshot, 0, _limit); + return snapshot; + } + + /** + * Returns the pooled {@link ByteBuffer} back to the {@link ResourcePool}. + */ + public void releaseResources() + { + if (_buf != null) + { + _resourcePool.recycleBuffer(_buf); + _buf = null; + } + + _position = 0; + _limit = 0; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/network/handler/ReadHandler.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/network/handler/ReadHandler.java new file mode 100644 index 0000000..f1f07c9 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/network/handler/ReadHandler.java @@ -0,0 +1,170 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.commons.network.handler; + +import java.nio.ByteBuffer; +import java.nio.channels.CompletionHandler; + +import org.l2jmobius.commons.network.Client; +import org.l2jmobius.commons.network.Connection; +import org.l2jmobius.commons.network.ConnectionConfig; +import org.l2jmobius.commons.network.buffer.ReadBuffer; +import org.l2jmobius.commons.network.packet.PacketExecutor; +import org.l2jmobius.commons.network.packet.PacketHandler; +import org.l2jmobius.commons.network.packet.ReadablePacket; + +/** + * {@link CompletionHandler} for async read operations.
+ * Processes completed reads, assembles the header/payload pair into a {@link ReadablePacket}, decrypts it and dispatches it to the {@link PacketExecutor}.
+ *
+ * Packet size validation: the 2-byte header encodes the total packet length (header + payload).
+ * Packets whose declared payload size is 0 are silently skipped (keep-alive / heartbeat).
+ * Packets whose declared size exceeds {@link #MAX_PACKET_SIZE} cause immediate disconnection to protect against memory exhaustion from malformed or malicious data. + * @param the client type associated with this handler + * @author JoeAlisson, Mobius + */ +public class ReadHandler>> implements CompletionHandler +{ + /** Maximum accepted payload size (65533 bytes = 0xFFFF total − 2-byte header). */ + private static final int MAX_PACKET_SIZE = 65533; + + private final PacketHandler _packetHandler; + private final PacketExecutor _executor; + + /** + * Creates a read handler backed by the given packet factory and executor. + * @param packetHandler factory that maps opcode bytes to {@link ReadablePacket} instances + * @param executor pool that will run accepted packets off the I/O thread + */ + public ReadHandler(PacketHandler packetHandler, PacketExecutor executor) + { + _packetHandler = packetHandler; + _executor = executor; + } + + @Override + public void completed(Integer bytesRead, T client) + { + if (!client.isConnected()) + { + return; + } + + if (bytesRead < 0) + { + // Clean peer close. + client.disconnect(); + return; + } + + if (bytesRead < client.getExpectedReadSize()) + { + // Partial read - resume until the full segment arrives. + client.resumeRead(bytesRead); + return; + } + + if (client.isReadingPayload()) + { + handlePayload(client); + } + else + { + handleHeader(client); + } + } + + private void handleHeader(T client) + { + final ByteBuffer buffer = client.getConnection().getReadingBuffer(); + if (buffer == null) + { + client.disconnect(); + return; + } + + buffer.flip(); + + // The 2-byte header is the total packet length (header + payload). + final int dataSize = Short.toUnsignedInt(buffer.getShort()) - ConnectionConfig.HEADER_SIZE; + if (dataSize <= 0) + { + // Zero-payload packet (keep-alive / heartbeat) - skip silently. + client.read(); + return; + } + + // Guard against packets large enough to cause OOM or exceed protocol limits. + if (dataSize > MAX_PACKET_SIZE) + { + client.disconnect(); + return; + } + + client.readPayload(dataSize); + } + + private void handlePayload(T client) + { + final ByteBuffer buffer = client.getConnection().getReadingBuffer(); + if (buffer == null) + { + client.disconnect(); + return; + } + + buffer.flip(); + parseAndExecutePacket(client, buffer); + + // Immediately start reading the next packet's header. + client.read(); + } + + private void parseAndExecutePacket(T client, ByteBuffer incomingBuffer) + { + try + { + final ReadBuffer buffer = ReadBuffer.of(incomingBuffer); + if (client.decrypt(buffer, 0, buffer.remaining())) + { + final ReadablePacket packet = _packetHandler.handle(buffer, client); + if (packet != null) + { + packet.init(client, buffer); + if (packet.read()) + { + _executor.execute(packet); + } + } + } + } + catch (Exception e) + { + failed(e, client); + } + } + + @Override + public void failed(Throwable e, T client) + { + client.disconnect(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/network/handler/WriteHandler.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/network/handler/WriteHandler.java new file mode 100644 index 0000000..b8e2d11 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/network/handler/WriteHandler.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.commons.network.handler; + +import java.nio.channels.CompletionHandler; + +import org.l2jmobius.commons.network.Client; +import org.l2jmobius.commons.network.Connection; + +/** + * {@link CompletionHandler} for async scatter-write operations.
+ * Invoked by the NIO framework when a {@link Connection#write()} completes (fully or partially).
+ *
    + *
  • Negative result -> peer disconnected; disconnect the client.
  • + *
  • Partial write -> call {@link Client#resumeSend} so the remaining bytes are retried.
  • + *
  • Full write -> call {@link Client#finishWriting} to release buffers and send the next queued packet.
  • + *
+ * @param the client type associated with this handler + * @author JoeAlisson, Mobius + */ +public class WriteHandler>> implements CompletionHandler +{ + @Override + public void completed(Long result, T client) + { + if (client == null) + { + return; + } + + final int bytesWritten = result.intValue(); + if (bytesWritten < 0) + { + if (client.isConnected()) + { + client.disconnect(); + } + return; + } + + if ((bytesWritten > 0) && (bytesWritten < client.getDataSentSize())) + { + // Partial write - continue sending the remaining data. + client.resumeSend(bytesWritten); + } + else + { + // All bytes sent - release buffers and process the next queued packet. + client.finishWriting(); + } + } + + @Override + public void failed(Throwable e, T client) + { + if (client != null) + { + client.disconnect(); + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/network/packet/PacketExecutor.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/network/packet/PacketExecutor.java new file mode 100644 index 0000000..a295838 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/network/packet/PacketExecutor.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.commons.network.packet; + +import java.lang.Thread.UncaughtExceptionHandler; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.commons.network.Client; +import org.l2jmobius.commons.network.Connection; +import org.l2jmobius.commons.network.ConnectionConfig; + +/** + * Thread-pool-based executor for incoming network packets.
+ * Received packets are submitted here after successful parsing so that their {@link ReadablePacket#run()} logic executes off the I/O thread. + * @param the client type associated with the packets being executed + * @author Mobius + */ +public class PacketExecutor>> +{ + private static final Logger LOGGER = Logger.getLogger(PacketExecutor.class.getName()); + + private final ThreadPoolExecutor _executor; + + /** + * Creates an executor sized according to {@link ConnectionConfig#threadPoolSize}. + * @param config the connection configuration supplying pool size and thread priority + */ + public PacketExecutor(ConnectionConfig config) + { + _executor = new ThreadPoolExecutor(config.threadPoolSize, Integer.MAX_VALUE, 1, TimeUnit.MINUTES, new LinkedBlockingQueue<>(), new PacketThreadFactory("PacketExecutor", config.threadPriority)); + } + + /** + * Submits a packet for execution on a worker thread. + * @param packet the packet to execute + */ + public void execute(ReadablePacket packet) + { + try + { + _executor.execute(new PacketRunnable<>(packet)); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Failed to submit " + packet.getClass().getSimpleName(), e); + } + } + + private static class PacketRunnable>> implements Runnable + { + private final ReadablePacket _packet; + + public PacketRunnable(ReadablePacket packet) + { + _packet = packet; + } + + @Override + public void run() + { + try + { + _packet.run(); + } + catch (Throwable t) + { + final Thread currentThread = Thread.currentThread(); + final UncaughtExceptionHandler handler = currentThread.getUncaughtExceptionHandler(); + if (handler != null) + { + handler.uncaughtException(currentThread, t); + } + else + { + LOGGER.log(Level.SEVERE, "Uncaught exception in " + _packet.getClass().getSimpleName(), t); + } + } + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/network/packet/PacketHandler.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/network/packet/PacketHandler.java new file mode 100644 index 0000000..6aebd74 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/network/packet/PacketHandler.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.commons.network.packet; + +import org.l2jmobius.commons.network.Client; +import org.l2jmobius.commons.network.Connection; +import org.l2jmobius.commons.network.buffer.ReadBuffer; + +/** + * Packet-identification strategy.
+ *
+ * Implementations inspect the leading opcode byte(s) of a decrypted payload buffer and return the {@link ReadablePacket} subclass that knows how to parse the remaining fields, or {@code null} when the opcode is not recognised (the caller will silently discard the data).
+ *
+ * Because exactly one method is required, this interface is marked {@link FunctionalInterface} and can be supplied as a lambda or method reference. + * @param the client type bound to the connection + * @author JoeAlisson, Mobius + */ +@FunctionalInterface +public interface PacketHandler>> +{ + /** + * Maps raw buffer data to a concrete packet instance.
+ * The buffer is positioned at the first opcode byte; the implementation should read only the bytes it needs to identify the packet and leave the rest for
+ * {@link ReadablePacket#read()}. + * @param buffer the decrypted payload, positioned at the first opcode byte + * @param client the client that sent this data (available for per-client branching) + * @return the matching {@link ReadablePacket}, or {@code null} if the opcode is unknown + */ + ReadablePacket handle(ReadBuffer buffer, T client); +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/network/packet/PacketThreadFactory.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/network/packet/PacketThreadFactory.java new file mode 100644 index 0000000..3246b88 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/network/packet/PacketThreadFactory.java @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.commons.network.packet; + +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Thread factory for creating and managing threads for network server tasks.
+ * Provides custom naming conventions and priority management for better thread identification and performance tuning. + *
    + *
  • Generates unique thread names with pool and thread sequence numbers.
  • + *
  • Enforces priority constraints based on thread group limitations.
  • + *
  • Handles integer overflow for thread sequence numbering.
  • + *
+ * @author BazookaRpm + */ +public class PacketThreadFactory implements ThreadFactory +{ + // Constants. + private static final String DEFAULT_BASE_NAME = "Thread"; + private static final int INITIAL_SEQUENCE_VALUE = 1; + private static final int STACK_SIZE = 0; // Use JVM default stack size. + + // Global pool sequence counter. + private static final AtomicInteger POOL_SEQUENCE = new AtomicInteger(INITIAL_SEQUENCE_VALUE); + + // Thread naming and priority. + private final AtomicInteger _threadSequence = new AtomicInteger(INITIAL_SEQUENCE_VALUE); + private final String _threadPrefix; + private final int _threadPriority; + + /** + * Creates a new network thread factory with specified base name and priority. + * @param baseName base name for thread naming + * @param priority thread priority level + */ + public PacketThreadFactory(String baseName, int priority) + { + final String safeBaseName = ((baseName == null) || baseName.isEmpty()) ? DEFAULT_BASE_NAME : baseName; + _threadPrefix = safeBaseName + "-network-pool-" + POOL_SEQUENCE.getAndIncrement() + "-thread-"; + + // Clamp priority to valid range. + if (priority < Thread.MIN_PRIORITY) + { + _threadPriority = Thread.MIN_PRIORITY; + } + else if (priority > Thread.MAX_PRIORITY) + { + _threadPriority = Thread.MAX_PRIORITY; + } + else + { + _threadPriority = priority; + } + } + + /** + * Creates a new thread for the given task with configured naming and priority. + * @param task the runnable task + * @return newly created thread + */ + @Override + public Thread newThread(Runnable task) + { + final int threadIndex = nextIndex(); + final Thread thread = new Thread(null, task, _threadPrefix + threadIndex, STACK_SIZE); + + // Apply priority respecting thread group constraints. + final ThreadGroup threadGroup = thread.getThreadGroup(); + final int groupMaxPriority = (threadGroup != null) ? threadGroup.getMaxPriority() : Thread.MAX_PRIORITY; + final int effectivePriority = (_threadPriority > groupMaxPriority) ? groupMaxPriority : _threadPriority; + + thread.setPriority(effectivePriority); + thread.setDaemon(false); + + return thread; + } + + /** + * Gets the next thread index with overflow protection. + * @return next available thread index + */ + private int nextIndex() + { + final int currentValue = _threadSequence.getAndIncrement(); + return (currentValue == Integer.MIN_VALUE) ? _threadSequence.updateAndGet(x -> (x <= 0) ? INITIAL_SEQUENCE_VALUE : x) : currentValue; // Handle integer overflow by resetting to 1. + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/network/packet/ReadablePacket.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/network/packet/ReadablePacket.java new file mode 100644 index 0000000..fb8f269 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/network/packet/ReadablePacket.java @@ -0,0 +1,247 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.commons.network.packet; + +import java.nio.charset.StandardCharsets; + +import org.l2jmobius.commons.network.Client; +import org.l2jmobius.commons.network.Connection; +import org.l2jmobius.commons.network.buffer.ReadBuffer; + +/** + * Base class for all packets received from a client.
+ * Subclasses implement {@link #read()} to parse fields from the buffer and then override {@link #run()} (inherited from {@link Runnable}) to act on those fields.
+ *
+ * The lifecycle managed by the network layer is: + *
    + *
  1. {@link PacketHandler} creates the concrete subclass instance.
  2. + *
  3. {@link #init(Client, ReadBuffer)} binds the client and buffer.
  4. + *
  5. {@link #read()} is called on the I/O thread to parse the payload.
  6. + *
  7. If {@link #read()} returns {@code true}, the packet is submitted to a {@link PacketExecutor} which calls {@link #run()} on a worker thread.
  8. + *
+ * @param the client type associated with this packet + * @author JoeAlisson, Mobius + */ +public abstract class ReadablePacket>> implements Runnable +{ + private ReadBuffer _buffer; + private T _client; + + protected ReadablePacket() + { + } + + /** + * Binds this packet to the given client and buffer before parsing begins. + * @param client the client that sent this packet + * @param buffer the decrypted payload buffer + */ + public void init(T client, ReadBuffer buffer) + { + _client = client; + _buffer = buffer; + } + + // ------------------------------------------------------------------------- + // Delegating read helpers - all delegate to the bound buffer. + // ------------------------------------------------------------------------- + + /** + * Reads a little-endian {@code char} (2 bytes). + * @return the char value + */ + protected char readChar() + { + return _buffer.readChar(); + } + + /** + * Reads a single byte. + * @return the byte value + */ + protected byte readByte() + { + return _buffer.readByte(); + } + + /** + * Reads a single byte as an unsigned int (0–255). + * @return the unsigned byte value + */ + protected int readUnsignedByte() + { + return Byte.toUnsignedInt(_buffer.readByte()); + } + + /** + * Reads a byte and returns {@code true} if it is non-zero. + * @return {@code true} if the byte is non-zero + */ + protected boolean readBoolean() + { + return _buffer.readByte() != 0; + } + + /** + * Reads {@code length} bytes into a new array. + * @param length the number of bytes to read + * @return the byte array + */ + protected byte[] readBytes(int length) + { + return _buffer.readBytes(length); + } + + /** + * Reads {@code dst.length} bytes into {@code dst}. + * @param dst the destination array + */ + protected void readBytes(byte[] dst) + { + _buffer.readBytes(dst, 0, dst.length); + } + + /** + * Reads {@code length} bytes into {@code dst} starting at {@code offset}. + * @param dst the destination array + * @param offset the starting offset within {@code dst} + * @param length the number of bytes to read + */ + protected void readBytes(byte[] dst, int offset, int length) + { + _buffer.readBytes(dst, offset, length); + } + + /** + * Reads a little-endian {@code short} (2 bytes). + * @return the short value + */ + protected short readShort() + { + return _buffer.readShort(); + } + + /** + * Reads a little-endian {@code int} (4 bytes). + * @return the int value + */ + protected int readInt() + { + return _buffer.readInt(); + } + + /** + * Reads a little-endian {@code long} (8 bytes). + * @return the long value + */ + protected long readLong() + { + return _buffer.readLong(); + } + + /** + * Reads a little-endian {@code float} (4 bytes). + * @return the float value + */ + protected float readFloat() + { + return _buffer.readFloat(); + } + + /** + * Reads a little-endian {@code double} (8 bytes). + * @return the double value + */ + protected double readDouble() + { + return _buffer.readDouble(); + } + + /** + * Reads a null-terminated UTF-16LE string from the buffer.
+ * Reads {@code short} values until a zero short is encountered. + * @return the decoded string (never {@code null}) + */ + protected String readString() + { + final StringBuilder result = new StringBuilder(); + try + { + int charId; + while ((charId = readShort()) != 0) + { + result.append((char) charId); + } + } + catch (Exception ignored) + { + } + + return result.toString(); + } + + /** + * Reads a length-prefixed UTF-16LE string.
+ * The first {@code short} is the character count; the following bytes are the UTF-16LE data. + * @return the decoded string (never {@code null}) + */ + protected String readSizedString() + { + try + { + final int charCount = readShort(); + if (charCount > 0) + { + return new String(readBytes(charCount * 2), StandardCharsets.UTF_16LE); + } + } + catch (Exception ignored) + { + } + + return ""; + } + + /** + * Returns the number of unread bytes remaining in the buffer. + * @return remaining byte count + */ + protected int remaining() + { + return _buffer.remaining(); + } + + /** + * Returns the client that sent this packet. + * @return the client instance + */ + public T getClient() + { + return _client; + } + + /** + * Parses the packet's fields from the bound buffer.
+ * Called on the I/O thread; must not block or perform heavy work. + * @return {@code true} if parsing succeeded and the packet should be executed; {@code false} to discard + */ + public abstract boolean read(); +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/network/packet/SimpleReadablePacket.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/network/packet/SimpleReadablePacket.java new file mode 100644 index 0000000..95a989c --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/network/packet/SimpleReadablePacket.java @@ -0,0 +1,218 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.commons.network.packet; + +import java.nio.charset.StandardCharsets; + +/** + * Represents a simple class for packets that can be read from client data.
+ * Provides methods to read various data types from the packet's byte array. + * @author Mobius + * @since October 29th 2020 + */ +public class SimpleReadablePacket +{ + private final byte[] _bytes; + private int _position = 0; + + public SimpleReadablePacket(byte[] bytes) + { + _bytes = bytes; + } + + /** + * Reads a boolean value from the packet data.
+ * 8-bit integer (00 or 01). + * @return The boolean value read from the packet. + */ + public boolean readBoolean() + { + return readByte() != 0; + } + + /** + * Reads String from the packet data. + * @return + */ + public String readString() + { + final StringBuilder result = new StringBuilder(); + try + { + int charId; + while ((charId = readShort()) != 0) + { + result.append((char) charId); + } + } + catch (Exception ignored) + { + } + + return result.toString(); + } + + /** + * Reads String with fixed size specified as (short size, char[size]) from the packet data. + * @return + */ + public String readSizedString() + { + String result = ""; + try + { + result = new String(readBytes(readShort() * 2), StandardCharsets.UTF_16LE); + } + catch (Exception ignored) + { + } + + return result; + } + + /** + * Reads byte[] from the packet data.
+ * 8bit integer array (00...) + * @param length of the array. + * @return + */ + public byte[] readBytes(int length) + { + // Validate before allocating, so a forged length cannot exhaust the heap. + if ((length < 0) || (length > (_bytes.length - _position))) + { + throw new ArrayIndexOutOfBoundsException(); + } + + final byte[] result = new byte[length]; + for (int i = 0; i < length; i++) + { + result[i] = _bytes[_position++]; + } + + return result; + } + + /** + * Reads byte[] from the packet data.
+ * 8bit integer array (00...) + * @param array used to store data. + * @return + */ + public byte[] readBytes(byte[] array) + { + for (int i = 0; i < array.length; i++) + { + array[i] = _bytes[_position++]; + } + + return array; + } + + /** + * Reads byte from the packet data.
+ * 8bit integer (00) + * @return + */ + public int readByte() + { + return _bytes[_position++] & 0xff; + } + + /** + * Reads short from the packet data.
+ * 16bit integer (00 00) + * @return + */ + public int readShort() + { + return (_bytes[_position++] & 0xff) // + | ((_bytes[_position++] & 0xff) << 8); + } + + /** + * Reads int from the packet data.
+ * 32bit integer (00 00 00 00) + * @return + */ + public int readInt() + { + return (_bytes[_position++] & 0xff) // + | ((_bytes[_position++] & 0xff) << 8) // + | ((_bytes[_position++] & 0xff) << 16) // + | ((_bytes[_position++] & 0xff) << 24); + } + + /** + * Reads long from the packet data.
+ * 64bit integer (00 00 00 00 00 00 00 00) + * @return + */ + public long readLong() + { + return (_bytes[_position++] & 0xff) // + | ((_bytes[_position++] & 0xffL) << 8) // + | ((_bytes[_position++] & 0xffL) << 16) // + | ((_bytes[_position++] & 0xffL) << 24) // + | ((_bytes[_position++] & 0xffL) << 32) // + | ((_bytes[_position++] & 0xffL) << 40) // + | ((_bytes[_position++] & 0xffL) << 48) // + | ((_bytes[_position++] & 0xffL) << 56); + } + + /** + * Reads float from the packet data.
+ * 32bit single precision float (00 00 00 00) + * @return + */ + public float readFloat() + { + return Float.intBitsToFloat(readInt()); + } + + /** + * Reads double from the packet data.
+ * 64bit double precision float (00 00 00 00 00 00 00 00) + * @return + */ + public double readDouble() + { + return Double.longBitsToDouble(readLong()); + } + + /** + * Gets the number of bytes remaining to be read. + * @return The number of unread bytes. + */ + public int getRemainingLength() + { + return _bytes.length - _position; + } + + /** + * Gets the total length of the byte array. + * @return The total byte size. + */ + public int getLength() + { + return _bytes.length; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/network/packet/SimpleWritablePacket.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/network/packet/SimpleWritablePacket.java new file mode 100644 index 0000000..a690c10 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/network/packet/SimpleWritablePacket.java @@ -0,0 +1,289 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.commons.network.packet; + +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Abstract class for writable packets backed by a byte array, with a maximum raw data size of 65533 bytes.
+ * Provides methods to write various types of data to the packet. + * @author Mobius + * @since October 29th 2020 + */ +public abstract class SimpleWritablePacket +{ + private static final Map, Integer> MAXIMUM_PACKET_SIZE = new ConcurrentHashMap<>(); + + private final int _initialSize = MAXIMUM_PACKET_SIZE.getOrDefault(getClass(), 8); + + private byte[] _data; + private byte[] _sendableBytes; + private int _position = 2; // Allocate space for size (max length 65535 - size header). + + protected SimpleWritablePacket() + { + _data = new byte[_initialSize]; + } + + public void write(byte value) + { + // Check current size. + if (_position < 65535) + { + // Check capacity. + if (_position == _data.length) + { + _data = Arrays.copyOf(_data, _data.length * 2); // Double the capacity. + } + + // Set value. + _data[_position++] = value; + return; + } + + throw new IndexOutOfBoundsException("Packet data exceeded the raw data size limit of 65533!"); + } + + /** + * Write boolean to the packet data.
+ * 8bit integer (00) or (01) + * @param value + */ + public void writeBoolean(boolean value) + { + writeByte(value ? 1 : 0); + } + + /** + * Write String to the packet data. + * @param text + */ + public void writeString(String text) + { + if (text != null) + { + writeBytes(text.getBytes(StandardCharsets.UTF_16LE)); + } + + writeShort(0); + } + + /** + * Write String with fixed size specified as (short size, char[size]) to the packet data. + * @param text + */ + public void writeSizedString(String text) + { + if (text != null) + { + writeShort(text.length()); + writeBytes(text.getBytes(StandardCharsets.UTF_16LE)); + } + else + { + writeShort(0); + } + } + + /** + * Write byte[] to the packet data.
+ * 8bit integer array (00...) + * @param array + */ + public void writeBytes(byte[] array) + { + for (int i = 0; i < array.length; i++) + { + write(array[i]); + } + } + + /** + * Write byte to the packet data.
+ * 8bit integer (00) + * @param value + */ + public void writeByte(int value) + { + write((byte) (value & 0xff)); + } + + /** + * Write boolean to the packet data.
+ * 8bit integer (00) or (01) + * @param value + */ + public void writeByte(boolean value) + { + writeByte(value ? 1 : 0); + } + + /** + * Write short to the packet data.
+ * 16bit integer (00 00) + * @param value + */ + public void writeShort(int value) + { + write((byte) (value & 0xff)); + write((byte) ((value >> 8) & 0xff)); + } + + /** + * Write boolean to the packet data.
+ * 16bit integer (00 00) + * @param value + */ + public void writeShort(boolean value) + { + writeShort(value ? 1 : 0); + } + + /** + * Write int to the packet data.
+ * 32bit integer (00 00 00 00) + * @param value + */ + public void writeInt(int value) + { + write((byte) (value & 0xff)); + write((byte) ((value >> 8) & 0xff)); + write((byte) ((value >> 16) & 0xff)); + write((byte) ((value >> 24) & 0xff)); + } + + /** + * Write boolean to the packet data.
+ * 32bit integer (00 00 00 00) + * @param value + */ + public void writeInt(boolean value) + { + writeInt(value ? 1 : 0); + } + + /** + * Write long to the packet data.
+ * 64bit integer (00 00 00 00 00 00 00 00) + * @param value + */ + public void writeLong(long value) + { + write((byte) (value & 0xff)); + write((byte) ((value >> 8) & 0xff)); + write((byte) ((value >> 16) & 0xff)); + write((byte) ((value >> 24) & 0xff)); + write((byte) ((value >> 32) & 0xff)); + write((byte) ((value >> 40) & 0xff)); + write((byte) ((value >> 48) & 0xff)); + write((byte) ((value >> 56) & 0xff)); + } + + /** + * Write boolean to the packet data.
+ * 64bit integer (00 00 00 00 00 00 00 00) + * @param value + */ + public void writeLong(boolean value) + { + writeLong(value ? 1 : 0); + } + + /** + * Write float to the packet data.
+ * 32bit single precision float (00 00 00 00) + * @param value + */ + public void writeFloat(float value) + { + writeInt(Float.floatToRawIntBits(value)); + } + + /** + * Write double to the packet data.
+ * 64bit double precision float (00 00 00 00 00 00 00 00) + * @param value + */ + public void writeDouble(double value) + { + writeLong(Double.doubleToRawLongBits(value)); + } + + /** + * Can be overridden to write data after packet has initialized.
+ * Called when getSendableBytes generates data, ensures that the data are processed only once. + */ + public void write() + { + // Overridden by server implementation. + } + + /** + * Returns the byte array containing the packet's data, including the size header.
+ * This method should be called after all data has been written to the packet. + * @return Byte array of the sendable packet data. + */ + public synchronized byte[] getSendableBytes() + { + // Generate sendable byte array. + if (_sendableBytes == null /* Not processed */) + { + // Write packet implementation (only once). + if (_position == 2) + { + write(); + + // Update maximum packet size if needed. + if (_position > _initialSize) + { + MAXIMUM_PACKET_SIZE.put(getClass(), Math.min(_position, 65535)); + } + } + + // Check if data was written. + if (_position > 2) + { + // Trim array of data. + _sendableBytes = Arrays.copyOf(_data, _position); + + // Add size info at start (unsigned short - max size 65535). + _sendableBytes[0] = (byte) (_position & 0xff); + _sendableBytes[1] = (byte) ((_position >> 8) & 0xffff); + } + } + + // Return the data. + return _sendableBytes; + } + + /** + * Gets the length of the data written to the packet, including the size header.
+ * Note that the data must be written first before calling this method. + * @return The length of the data. + */ + public int getLength() + { + return _position; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/network/packet/WritablePacket.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/network/packet/WritablePacket.java new file mode 100644 index 0000000..2e12f2c --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/network/packet/WritablePacket.java @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.commons.network.packet; + +import org.l2jmobius.commons.network.Client; +import org.l2jmobius.commons.network.Connection; +import org.l2jmobius.commons.network.ConnectionConfig; +import org.l2jmobius.commons.network.buffer.WriteBuffer; + +/** + * Base class for all packets sent to clients.
+ * All packets carry a 2-byte little-endian header that encodes the total packet length.
+ *
+ * Broadcast optimisation: when a packet will be sent to multiple clients, call {@link #sendInBroadcast()} before the first {@link Client#writePacket} call.
+ * The packet data is then written once into a {@link WriteBuffer}, a heap {@code byte[]} snapshot is cached on this packet instance, and every subsequent recipient gets a fresh {@link WriteBuffer} seeded from that snapshot. Each client still encrypts its own copy independently. + * @param the client type associated with this packet + * @author Mobius + */ +public abstract class WritablePacket>> +{ + private volatile boolean _broadcast; + + /** + * Cached snapshot of the serialized packet bytes for broadcast reuse.
+ * Populated on the first send when {@link #_broadcast} is {@code true}; every later send bulk-copies these bytes into a fresh per-client {@link WriteBuffer}. Guarded by {@code this} (synchronized on the WritablePacket instance). + */ + private byte[] _broadcastCacheBytes; + private int _broadcastCacheLength; + + protected WritablePacket() + { + } + + /** + * Produces the buffer containing this packet's encoded data for the given client.
+ * For broadcast packets the first call populates a shared byte-array cache; subsequent calls seed a fresh per-client {@link WriteBuffer} from that cache so each client can encrypt independently. + * @param client the recipient client + * @return a {@link WriteBuffer} whose logical limit is the packet length + * @throws Exception if the {@link #write} implementation signals a failure + */ + public WriteBuffer writeData(T client) throws Exception + { + if (_broadcast) + { + return writeDataWithCache(client); + } + + return writeDataToBuffer(client); + } + + /** + * Broadcast path: returns a fresh per-client buffer seeded from the shared cache, building the cache on the first call. + * @param client the recipient client + * @return per-client buffer populated with the cached bytes + * @throws Exception if the underlying write fails on first cache population + */ + private synchronized WriteBuffer writeDataWithCache(T client) throws Exception + { + if (_broadcastCacheBytes != null) + { + return new WriteBuffer(_broadcastCacheBytes, _broadcastCacheLength, client.getResourcePool(), getClass()); + } + + final WriteBuffer buffer = writeDataToBuffer(client); + _broadcastCacheBytes = buffer.toByteArray(); + _broadcastCacheLength = buffer.limit(); + return buffer; + } + + /** + * Writes packet data into a fresh {@link WriteBuffer} sized by the per-class historical maximum. + * @param client the recipient client + * @return buffer containing the written packet data, positioned at 0 with limit at packet end + * @throws Exception if {@link #write} returns {@code false} + */ + private WriteBuffer writeDataToBuffer(T client) throws Exception + { + final WriteBuffer buffer = new WriteBuffer(client.getResourcePool(), getClass()); + buffer.position(ConnectionConfig.HEADER_SIZE); + if (write(client, buffer)) + { + buffer.mark(); + return buffer; + } + + buffer.releaseResources(); + throw new Exception("WritablePacket.write() returned false for " + getClass().getSimpleName()); + } + + /** + * Writes the 2-byte packet-length header at offset 0 of {@code buffer}. + * @param buffer the buffer whose first 2 bytes receive the total length + * @param header the total packet length (including the 2-byte header itself) + */ + public void writeHeader(WriteBuffer buffer, int header) + { + buffer.writeShort(0, (short) header); + } + + /** + * Marks this packet as a broadcast packet.
+ * Must be called before the first {@link Client#writePacket} invocation.
+ * After this call, packet data is written once and the resulting bytes are cached on this instance for reuse across all recipients. + */ + public void sendInBroadcast() + { + _broadcast = true; + } + + /** + * Returns whether this packet may be silently discarded when the client's send queue exceeds the configured drop threshold.
+ * The default implementation always returns {@code false} (never drop). + * @param client the recipient client + * @return {@code true} if the packet is expendable and may be dropped under pressure + */ + public boolean canBeDropped(T client) + { + return false; + } + + /** + * Writes the packet's payload into {@code buffer}.
+ * The buffer's position is already set to {@link ConnectionConfig#HEADER_SIZE} on entry; the implementation should write all payload bytes and return {@code true} on success. + * @param client the recipient client (available for per-client customisation) + * @param buffer the buffer to write payload data into + * @return {@code true} if the packet was written successfully; {@code false} to cancel sending + */ + protected abstract boolean write(T client, WriteBuffer buffer); + + @Override + public String toString() + { + return getClass().getSimpleName(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/network/pool/BufferPool.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/network/pool/BufferPool.java new file mode 100644 index 0000000..6c6ced5 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/network/pool/BufferPool.java @@ -0,0 +1,218 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.commons.network.pool; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.ArrayDeque; +import java.util.concurrent.locks.ReentrantLock; + +/** + * Represents a pool of {@link ByteBuffer} objects for efficient reuse, reducing allocation overhead.
+ * Uses an {@link ArrayDeque} backed by a {@link ReentrantLock} for correct, thread-safe pool management without the estimate-drift issues of a lock-free approach. + * @author Mobius + */ +public class BufferPool +{ + private final ArrayDeque _buffers; + private final ReentrantLock _lock = new ReentrantLock(); + private final int _bufferSize; + private int _maxSize; + + /** + * Creates a buffer pool with the specified capacity and buffer size. + * @param maxSize the maximum number of pooled buffers + * @param bufferSize the capacity (in bytes) of each pooled buffer + */ + public BufferPool(int maxSize, int bufferSize) + { + _maxSize = maxSize; + _bufferSize = bufferSize; + _buffers = new ArrayDeque<>(maxSize); + } + + /** + * Pre-allocates buffers into the pool based on the given factor.
+ * The number pre-allocated is {@code min(maxSize, maxSize * factor)}. + * @param factor the fraction of the pool to pre-fill (0 = none, 1.0 = full) + */ + public void initialize(float factor) + { + final int amount = (int) Math.min(_maxSize, _maxSize * factor); + _lock.lock(); + try + { + for (int i = 0; i < amount; i++) + { + _buffers.offer(ByteBuffer.allocateDirect(_bufferSize).order(ByteOrder.LITTLE_ENDIAN)); + } + } + finally + { + _lock.unlock(); + } + } + + /** + * Retrieves a pooled {@link ByteBuffer}, or {@code null} if the pool is empty. + * @return a cleared buffer ready for use, or {@code null} + */ + public ByteBuffer get() + { + _lock.lock(); + try + { + return _buffers.poll(); + } + finally + { + _lock.unlock(); + } + } + + /** + * Returns a buffer to the pool if capacity allows.
+ * The buffer is cleared before being stored. + * @param buffer the buffer to recycle + * @return {@code true} if the buffer was accepted; {@code false} if the pool was full + */ + public boolean recycle(ByteBuffer buffer) + { + _lock.lock(); + try + { + if (_buffers.size() < _maxSize) + { + _buffers.offer(buffer.clear()); + return true; + } + + return false; + } + finally + { + _lock.unlock(); + } + } + + /** + * Expands the pool's maximum capacity and optionally allocates additional buffers.
+ * If {@code factor > 0}, new buffers are allocated immediately and the max size is increased.
+ * Otherwise, the max size is simply doubled to allow future recycling. + * @param factor allocation factor; if {@code > 0}, new buffers are created immediately + * @param limit the pool only expands when its current max size does not exceed this value + */ + public void expandCapacity(float factor, int limit) + { + _lock.lock(); + try + { + if (_maxSize > limit) + { + return; + } + + if (factor > 0) + { + final int amount = (int) (_maxSize * factor); + for (int i = 0; i < amount; i++) + { + _buffers.offer(ByteBuffer.allocateDirect(_bufferSize).order(ByteOrder.LITTLE_ENDIAN)); + } + + _maxSize += amount; + } + else + { + _maxSize *= 2; + } + } + finally + { + _lock.unlock(); + } + } + + /** + * Returns the configured maximum capacity of this pool. + * @return the maximum number of buffers that may be held + */ + public int getMaxSize() + { + _lock.lock(); + try + { + return _maxSize; + } + finally + { + _lock.unlock(); + } + } + + /** + * Returns {@code true} when the pool holds at least as many buffers as its current max size. + * @return {@code true} if the pool is at capacity + */ + public boolean isFull() + { + _lock.lock(); + try + { + return _buffers.size() >= _maxSize; + } + finally + { + _lock.unlock(); + } + } + + /** + * Returns {@code true} when the pool holds no buffers. + * @return {@code true} if the pool is empty + */ + public boolean isEmpty() + { + _lock.lock(); + try + { + return _buffers.isEmpty(); + } + finally + { + _lock.unlock(); + } + } + + @Override + public String toString() + { + _lock.lock(); + try + { + return "Pool {maxSize=" + _maxSize + ", bufferSize=" + _bufferSize + ", currentSize=" + _buffers.size() + '}'; + } + finally + { + _lock.unlock(); + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/network/pool/ResourcePool.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/network/pool/ResourcePool.java new file mode 100644 index 0000000..e0d4cf3 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/network/pool/ResourcePool.java @@ -0,0 +1,264 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.commons.network.pool; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentSkipListMap; + +import org.l2jmobius.commons.network.ConnectionConfig; + +/** + * Manages multiple {@link BufferPool}s indexed by buffer size for efficient {@link ByteBuffer} reuse.
+ *
+ * Two concurrent data structures are maintained in parallel: + *
    + *
  • A {@link ConcurrentSkipListMap} keyed by size for {@code ceiling} lookups (finding the smallest pool whose buffer capacity is ≥ the requested size).
  • + *
  • A {@link ConcurrentHashMap} keyed by exact capacity for O(1) recycle path (returning a buffer whose capacity exactly matches a known pool).
  • + *
+ * Both structures are written together in {@link #addBufferPool}, so they are always consistent. + * @author JoeAlisson, Mobius + */ +public class ResourcePool +{ + /** Ordered map for ceiling-entry lookups (find smallest pool >= requested size). */ + private final ConcurrentSkipListMap _sizedPools = new ConcurrentSkipListMap<>(); + + /** Fast O(1) exact-capacity lookup used by the recycle path. */ + private final ConcurrentHashMap _exactPools = new ConcurrentHashMap<>(); + + private volatile boolean _autoExpandCapacity = true; + private volatile boolean _initBufferPools = false; + private volatile float _initBufferPoolFactor = 0; + private volatile int _bufferSegmentSize = 64; + + public ResourcePool() + { + } + + /** + * Returns a {@link ByteBuffer} sized for the packet header (2 bytes). + * @return a header-sized buffer + */ + public ByteBuffer getHeaderBuffer() + { + return getSizedBuffer(ConnectionConfig.HEADER_SIZE); + } + + /** + * Returns a {@link ByteBuffer} with at least {@code size} bytes of capacity. + * @param size the minimum required capacity + * @return a buffer with capacity ≥ {@code size} + */ + public ByteBuffer getBuffer(int size) + { + return getSizedBuffer(determineBufferSize(size)); + } + + /** + * Recycles {@code buffer} (if non-null) and returns a new buffer of {@code newSize}.
+ * If {@code buffer} already has the right capacity, it is cleared and reused directly. + * @param buffer the buffer to recycle, or {@code null} + * @param newSize the required capacity of the returned buffer + * @return a buffer with capacity exactly matching the pool size for {@code newSize} + */ + public ByteBuffer recycleAndGetNew(ByteBuffer buffer, int newSize) + { + final int poolSize = determineBufferSize(newSize); + if (buffer != null) + { + if (buffer.clear().limit() == poolSize) + { + return buffer.limit(newSize); + } + + recycleBuffer(buffer); + } + + return getSizedBuffer(poolSize).limit(newSize); + } + + /** + * Returns a buffer of exactly {@code size} bytes from the appropriate pool, expanding or creating pools as needed. + * @param size the exact capacity to serve (must be a pool key) + * @return a direct {@link ByteBuffer} with the requested capacity + */ + private ByteBuffer getSizedBuffer(int size) + { + final Entry entry = _sizedPools.ceilingEntry(size); + if (entry != null) + { + final BufferPool pool = entry.getValue(); + if (_autoExpandCapacity) + { + if (_initBufferPools) + { + if (pool.isEmpty()) + { + pool.expandCapacity(_initBufferPoolFactor, pool.getMaxSize()); + } + } + else if (pool.isFull()) + { + pool.expandCapacity(_initBufferPoolFactor, pool.getMaxSize()); + } + } + + final ByteBuffer buffer = pool.get(); + if (buffer != null) + { + return buffer; + } + } + + // No pool or pool was empty and no expansion produced a buffer - allocate directly. + if (entry == null) + { + // Unknown size: register a new pool so future requests are served from the pool. + final BufferPool pool = new BufferPool(10, size); + if (_initBufferPools) + { + pool.initialize(_initBufferPoolFactor); + } + + _sizedPools.putIfAbsent(size, pool); + _exactPools.putIfAbsent(size, _sizedPools.get(size)); + } + + return ByteBuffer.allocateDirect(size).order(ByteOrder.LITTLE_ENDIAN); + } + + /** + * Returns the pool key (capacity) to use for the given {@code size}. + * @param size the minimum required capacity + * @return the capacity of the smallest pool that can satisfy {@code size} + */ + private int determineBufferSize(int size) + { + final Entry entry = _sizedPools.ceilingEntry(size); + if (entry != null) + { + return entry.getKey(); + } + + // Unknown size - register a new pool. + final BufferPool pool = new BufferPool(10, size); + if (_initBufferPools) + { + pool.initialize(_initBufferPoolFactor); + } + + _sizedPools.putIfAbsent(size, pool); + _exactPools.putIfAbsent(size, _sizedPools.get(size)); + return size; + } + + /** + * Returns {@code buffer} to its pool using an O(1) exact-capacity lookup.
+ * Silently ignores {@code null} or buffers whose capacity has no matching pool. + * @param buffer the buffer to recycle + */ + public void recycleBuffer(ByteBuffer buffer) + { + if (buffer == null) + { + return; + } + + final BufferPool pool = _exactPools.get(buffer.capacity()); + if (pool != null) + { + pool.recycle(buffer); + } + } + + /** + * Registers a {@link BufferPool} for the given buffer size.
+ * If a pool for that size already exists it is not replaced. + * @param bufferSize the capacity (in bytes) of buffers managed by the pool + * @param bufferPool the pool to register + */ + public void addBufferPool(int bufferSize, BufferPool bufferPool) + { + _sizedPools.putIfAbsent(bufferSize, bufferPool); + _exactPools.putIfAbsent(bufferSize, bufferPool); + } + + /** + * Returns the number of registered buffer pools. + * @return pool count + */ + public int bufferPoolSize() + { + return _sizedPools.size(); + } + + /** + * Finalises pool configuration and optionally pre-allocates buffers. + * @param autoExpandCapacity whether pools should grow automatically when exhausted + * @param initBufferPoolFactor fraction of each pool to pre-allocate (0 = none) + */ + public void initializeBuffers(boolean autoExpandCapacity, float initBufferPoolFactor) + { + _autoExpandCapacity = autoExpandCapacity; + _initBufferPoolFactor = initBufferPoolFactor; + _initBufferPools = initBufferPoolFactor > 0; + if (_initBufferPools) + { + _sizedPools.values().forEach(pool -> pool.initialize(initBufferPoolFactor)); + } + } + + /** + * Returns the configured buffer segment size (default 64). + * @return segment size in bytes + */ + public int getSegmentSize() + { + return _bufferSegmentSize; + } + + /** + * Sets the buffer segment size used as the default initial size for dynamic packet buffers. + * @param size the new segment size in bytes + */ + public void setBufferSegmentSize(int size) + { + _bufferSegmentSize = size; + } + + /** + * Returns a diagnostic string listing all registered pools. + * @return multi-line pool statistics + */ + public String stats() + { + final StringBuilder sb = new StringBuilder(); + for (BufferPool pool : _sizedPools.values()) + { + sb.append(pool.toString()).append(System.lineSeparator()); + } + + return sb.toString(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/threads/ThreadPool.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/threads/ThreadPool.java new file mode 100644 index 0000000..1ae4096 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/threads/ThreadPool.java @@ -0,0 +1,302 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.commons.threads; + +import java.lang.Thread.UncaughtExceptionHandler; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.RejectedExecutionHandler; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.logging.Logger; + +import org.l2jmobius.commons.config.ThreadConfig; +import org.l2jmobius.commons.util.StringUtil; +import org.l2jmobius.commons.util.TraceUtil; + +/** + * Provides methods to schedule tasks with delays, fixed rates and immediate execution.
+ * Manages multiple thread pools including scheduled tasks, instant execution and high priority scheduling. + *
    + *
  • Scheduled thread pool for delayed and recurring tasks.
  • + *
  • Instant thread pool for immediate task execution.
  • + *
  • High priority scheduled thread pool for critical tasks.
  • + *
  • Automatic task purging and cleanup mechanisms.
  • + *
+ * @author Mobius + */ +public class ThreadPool +{ + private static final Logger LOGGER = Logger.getLogger(ThreadPool.class.getName()); + + // Constants. + private static final long ONE_HUNDRED_YEARS_MS = 3155695200000L; + private static final long MIN_DELAY_MS = 0L; + private static final long PURGE_INTERVAL_MS = 60000L; + private static final int INSTANT_POOL_KEEP_ALIVE_MINUTES = 1; + + // Thread Pool Executors. + private static ScheduledThreadPoolExecutor HIGH_PRIORITY_SCHEDULED_POOL; + private static ScheduledThreadPoolExecutor SCHEDULED_POOL; + private static ThreadPoolExecutor INSTANT_POOL; + + /** + * Initializes thread pool executors and starts maintenance tasks. + */ + public static void init() + { + LOGGER.info("ThreadPool: Initializing."); + + // Load configurations. + ThreadConfig.load(); + + // Configure High Priority ScheduledThreadPoolExecutor. + if (ThreadConfig.HIGH_PRIORITY_SCHEDULED_THREAD_POOL_SIZE > 0) + { + HIGH_PRIORITY_SCHEDULED_POOL = new ScheduledThreadPoolExecutor(ThreadConfig.HIGH_PRIORITY_SCHEDULED_THREAD_POOL_SIZE, new ThreadProvider("L2jMobius High Priority ScheduledThread", ThreadPriority.PRIORITY_8), new ThreadPoolExecutor.CallerRunsPolicy()); + LOGGER.info(StringUtil.concat("...scheduled pool executor with ", String.valueOf(ThreadConfig.HIGH_PRIORITY_SCHEDULED_THREAD_POOL_SIZE), " high priority threads.")); + } + + // Configure ScheduledThreadPoolExecutor. + SCHEDULED_POOL = new ScheduledThreadPoolExecutor(ThreadConfig.SCHEDULED_THREAD_POOL_SIZE, new ThreadProvider("L2jMobius ScheduledThread"), new ThreadPoolExecutor.CallerRunsPolicy()); + SCHEDULED_POOL.setRejectedExecutionHandler(new RejectedExecutionHandlerImpl()); + SCHEDULED_POOL.setRemoveOnCancelPolicy(true); + SCHEDULED_POOL.prestartAllCoreThreads(); + + // Configure ThreadPoolExecutor. + INSTANT_POOL = new ThreadPoolExecutor(ThreadConfig.INSTANT_THREAD_POOL_SIZE, Integer.MAX_VALUE, INSTANT_POOL_KEEP_ALIVE_MINUTES, TimeUnit.MINUTES, new LinkedBlockingQueue<>(), new ThreadProvider("L2jMobius Thread")); + INSTANT_POOL.setRejectedExecutionHandler(new RejectedExecutionHandlerImpl()); + INSTANT_POOL.prestartAllCoreThreads(); + + // Schedule the purge task. + scheduleAtFixedRate(ThreadPool::purge, PURGE_INTERVAL_MS, PURGE_INTERVAL_MS); + + // Log thread pool configuration. + LOGGER.info(StringUtil.concat("...scheduled pool executor with ", String.valueOf(ThreadConfig.SCHEDULED_THREAD_POOL_SIZE), " total threads.")); + LOGGER.info(StringUtil.concat("...instant pool executor with ", String.valueOf(ThreadConfig.INSTANT_THREAD_POOL_SIZE), " total threads.")); + } + + /** + * Purges cancelled tasks from all thread pools to free memory. + */ + public static void purge() + { + SCHEDULED_POOL.purge(); + INSTANT_POOL.purge(); + if (HIGH_PRIORITY_SCHEDULED_POOL != null) + { + HIGH_PRIORITY_SCHEDULED_POOL.purge(); + } + } + + /** + * Creates and executes a one-shot action that becomes enabled after the given delay. + * @param runnable the task to execute + * @param delay the time from now to delay execution + * @return a ScheduledFuture representing pending completion of the task and whose get() method will return null upon completion + */ + public static ScheduledFuture schedule(Runnable runnable, long delay) + { + try + { + return SCHEDULED_POOL.schedule(new RunnableWrapper(runnable), validateDelay(delay), TimeUnit.MILLISECONDS); + } + catch (Exception e) + { + LOGGER.warning(StringUtil.concat("ThreadPool: Failed to schedule task ", runnable.getClass().getSimpleName(), " with delay ", String.valueOf(delay), "ms: ", e.getMessage(), System.lineSeparator(), TraceUtil.getStackTrace(e))); + return null; + } + } + + /** + * Creates and executes a periodic action that becomes enabled first after the given initial delay. + * @param runnable the task to execute + * @param initialDelay the time to delay first execution + * @param period the period between successive executions + * @return a ScheduledFuture representing pending completion of the task and whose get() method will throw an exception upon cancellation + */ + public static ScheduledFuture scheduleAtFixedRate(Runnable runnable, long initialDelay, long period) + { + try + { + return SCHEDULED_POOL.scheduleAtFixedRate(new RunnableWrapper(runnable), validateDelay(initialDelay), validateDelay(period), TimeUnit.MILLISECONDS); + } + catch (Exception e) + { + LOGGER.warning(StringUtil.concat("ThreadPool: Failed to schedule recurring task ", runnable.getClass().getSimpleName(), " with initial delay ", String.valueOf(initialDelay), "ms and period ", String.valueOf(period), "ms: ", e.getMessage(), System.lineSeparator(), TraceUtil.getStackTrace(e))); + return null; + } + } + + /** + * Creates and executes a periodic action using high priority thread pool.
+ * Designed for tasks requiring immediate or high-priority execution. + * @param runnable the task to execute + * @param initialDelay the time to delay first execution + * @param period the period between successive executions + * @return a ScheduledFuture representing pending completion of the task and whose get() method will throw an exception upon cancellation + */ + public static ScheduledFuture schedulePriorityTaskAtFixedRate(Runnable runnable, long initialDelay, long period) + { + if (HIGH_PRIORITY_SCHEDULED_POOL == null) + { + return scheduleAtFixedRate(runnable, initialDelay, period); + } + + try + { + return HIGH_PRIORITY_SCHEDULED_POOL.scheduleAtFixedRate(new RunnableWrapper(runnable), validateDelay(initialDelay), validateDelay(period), TimeUnit.MILLISECONDS); + } + catch (Exception e) + { + LOGGER.warning(StringUtil.concat("ThreadPool: Failed to schedule high priority task ", runnable.getClass().getSimpleName(), " with initial delay ", String.valueOf(initialDelay), "ms and period ", String.valueOf(period), "ms: ", e.getMessage(), System.lineSeparator(), TraceUtil.getStackTrace(e))); + return null; + } + } + + /** + * Executes the given task sometime in the future using the instant thread pool. + * @param runnable the task to execute + */ + public static void execute(Runnable runnable) + { + try + { + INSTANT_POOL.execute(new RunnableWrapper(runnable)); + } + catch (Exception e) + { + LOGGER.warning(StringUtil.concat("ThreadPool: Failed to execute task ", runnable.getClass().getSimpleName(), ": ", e.getMessage(), System.lineSeparator(), TraceUtil.getStackTrace(e))); + } + } + + /** + * Validates delay value to ensure it falls within acceptable bounds. + * @param delay the delay to validate + * @return a valid delay value between MIN_DELAY_MS and ONE_HUNDRED_YEARS_MS + */ + private static long validateDelay(long delay) + { + if (delay < MIN_DELAY_MS) + { + LOGGER.warning(StringUtil.concat("ThreadPool: Invalid delay ", String.valueOf(delay), "ms is below minimum, using ", String.valueOf(MIN_DELAY_MS), "ms instead.")); + LOGGER.warning(TraceUtil.getStackTrace(new Exception())); + return MIN_DELAY_MS; + } + + if (delay > ONE_HUNDRED_YEARS_MS) + { + LOGGER.warning(StringUtil.concat("ThreadPool: Invalid delay ", String.valueOf(delay), "ms exceeds maximum, using ", String.valueOf(ONE_HUNDRED_YEARS_MS), "ms instead.")); + LOGGER.warning(TraceUtil.getStackTrace(new Exception())); + return ONE_HUNDRED_YEARS_MS; + } + + return delay; + } + + /** + * Shutdown thread pooling system correctly. + */ + public static void shutdown() + { + try + { + LOGGER.info("ThreadPool: Shutting down all thread pools."); + SCHEDULED_POOL.shutdownNow(); + INSTANT_POOL.shutdownNow(); + if (HIGH_PRIORITY_SCHEDULED_POOL != null) + { + HIGH_PRIORITY_SCHEDULED_POOL.shutdownNow(); + } + } + catch (Throwable t) + { + LOGGER.warning(StringUtil.concat("ThreadPool: Exception occurred during shutdown: ", t.getMessage())); + } + } + + /** + * Handles tasks rejected by ThreadPoolExecutor by running them in new thread or current thread.
+ * Decision based on current thread priority to prevent blocking high priority operations. + */ + private static class RejectedExecutionHandlerImpl implements RejectedExecutionHandler + { + private static final Logger LOGGER = Logger.getLogger(RejectedExecutionHandlerImpl.class.getName()); + + @Override + public void rejectedExecution(Runnable runnable, ThreadPoolExecutor executor) + { + if (executor.isShutdown()) + { + return; + } + + LOGGER.warning(StringUtil.concat("ThreadPool: Task ", runnable.getClass().getSimpleName(), " rejected by executor ", String.valueOf(executor), ", attempting recovery execution.")); + + // Run in new thread for high priority contexts, current thread otherwise. + if (Thread.currentThread().getPriority() > Thread.NORM_PRIORITY) + { + new Thread(runnable).start(); + } + else + { + runnable.run(); + } + } + } + + /** + * Wraps a Runnable to handle uncaught exceptions during execution.
+ * Passes exceptions to the thread's uncaught exception handler for proper error management. + */ + private static class RunnableWrapper implements Runnable + { + private final Runnable _wrappedRunnable; + + /** + * Creates a new RunnableWrapper for the specified runnable. + * @param runnable the runnable to wrap + */ + public RunnableWrapper(Runnable runnable) + { + _wrappedRunnable = runnable; + } + + @Override + public void run() + { + try + { + _wrappedRunnable.run(); + } + catch (Throwable t) + { + final Thread currentThread = Thread.currentThread(); + final UncaughtExceptionHandler exceptionHandler = currentThread.getUncaughtExceptionHandler(); + if (exceptionHandler != null) + { + exceptionHandler.uncaughtException(currentThread, t); + } + } + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/threads/ThreadPriority.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/threads/ThreadPriority.java new file mode 100644 index 0000000..79090dd --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/threads/ThreadPriority.java @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.commons.threads; + +/** + * Defines different levels of thread priorities.
+ * This enum provides a convenient way to set thread priorities in a readable and maintainable manner.
+ * The priorities range from 1 (lowest) to 10 (highest), aligning with Java's standard thread priority levels. + * @author Mobius + * @since December 8th, 2023 + */ +public enum ThreadPriority +{ + /** + * Priority level 1, equivalent to {@link Thread#MIN_PRIORITY}. + */ + PRIORITY_1(1), + + /** + * Priority level 2. + */ + PRIORITY_2(2), + + /** + * Priority level 3. + */ + PRIORITY_3(3), + + /** + * Priority level 4. + */ + PRIORITY_4(4), + + /** + * Priority level 5, equivalent to {@link Thread#NORM_PRIORITY}. + */ + PRIORITY_5(5), + + /** + * Priority level 6. + */ + PRIORITY_6(6), + + /** + * Priority level 7. + */ + PRIORITY_7(7), + + /** + * Priority level 8. + */ + PRIORITY_8(8), + + /** + * Priority level 9. + */ + PRIORITY_9(9), + + /** + * Priority level 10, equivalent to {@link Thread#MAX_PRIORITY}. + */ + PRIORITY_10(10); + + private final int _id; + + /** + * Constructs a new {@code ThreadPriority} instance with the specified priority level. + * @param id the priority level, ranging from 1 to 10. + */ + ThreadPriority(int id) + { + _id = id; + } + + /** + * Returns the numerical ID of the priority level. + * @return the priority level as an integer. + */ + public int getId() + { + return _id; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/threads/ThreadProvider.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/threads/ThreadProvider.java new file mode 100644 index 0000000..18b5c3c --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/threads/ThreadProvider.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.commons.threads; + +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * ThreadFactory implementation that allows setting a thread name prefix, priority, and daemon status when creating new threads. + * @author Mobius + * @since October 18th 2022 + */ +public class ThreadProvider implements ThreadFactory +{ + private final AtomicInteger _id = new AtomicInteger(); + private final String _prefix; + private final int _priority; + private final boolean _daemon; + + /** + * Creates a new ThreadProvider with the specified prefix, normal thread priority, and non-daemon threads. + * @param prefix the prefix to be used for thread names + */ + public ThreadProvider(String prefix) + { + this(prefix, ThreadPriority.PRIORITY_5, false); + } + + /** + * Creates a new ThreadProvider with the specified prefix and daemon status, and normal thread priority. + * @param prefix the prefix to be used for thread names + * @param daemon whether the threads should be daemon threads + */ + public ThreadProvider(String prefix, boolean daemon) + { + this(prefix, ThreadPriority.PRIORITY_5, daemon); + } + + /** + * Creates a new ThreadProvider with the specified prefix and priority, and non-daemon threads. + * @param prefix the prefix to be used for thread names + * @param priority the priority of the threads + */ + public ThreadProvider(String prefix, ThreadPriority priority) + { + this(prefix, priority, false); + } + + /** + * Creates a new ThreadProvider with the specified prefix, priority, and daemon status. + * @param prefix the prefix to be used for thread names + * @param priority the priority of the threads + * @param daemon whether the threads should be daemon threads + */ + public ThreadProvider(String prefix, ThreadPriority priority, boolean daemon) + { + _prefix = prefix + " "; + _priority = priority.getId(); + _daemon = daemon; + } + + /** + * Creates a new Thread with the specified Runnable object and with the properties defined in this ThreadProvider. + * @param runnable the object whose run method is invoked when this thread is started + * @return the created Thread + */ + @Override + public Thread newThread(Runnable runnable) + { + final Thread thread = new Thread(runnable, _prefix + _id.incrementAndGet()); + thread.setPriority(_priority); + thread.setDaemon(_daemon); + return thread; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/time/SchedulingPattern.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/time/SchedulingPattern.java new file mode 100644 index 0000000..4f66119 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/time/SchedulingPattern.java @@ -0,0 +1,744 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.commons.time; + +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.TimeZone; +import java.util.concurrent.TimeUnit; + +import org.l2jmobius.commons.util.Rnd; +import org.l2jmobius.commons.util.StringUtil; + +/** + * A UNIX cron-like pattern parser for scheduling tasks.
+ * Supports extended syntax with randomization and time offset modifiers for flexible task scheduling. + *
    + *
  • Standard cron format with 5 or 6 space-separated fields (minute, hour, day, month, weekday, optional week offset).
  • + *
  • Extended modifiers: ~N for random delays, +N for time offsets, L for last day of month.
  • + *
  • Multiple pattern support using pipe (|) separator for OR conditions.
  • + *
  • Month and weekday name aliases (jan-dec, sun-sat) for improved readability.
  • + *
+ * @author Mobius + */ +public class SchedulingPattern +{ + // Constants. + private static final int MINUTE_MIN = 0; + private static final int MINUTE_MAX = 59; + private static final int HOUR_MIN = 0; + private static final int HOUR_MAX = 23; + private static final int DAY_MIN = 1; + private static final int DAY_MAX = 31; + private static final int MONTH_MIN = 1; + private static final int MONTH_MAX = 12; + private static final int DAY_OF_WEEK_MIN = 0; // 0 = Sunday + private static final int DAY_OF_WEEK_MAX = 6; + private static final int LAST_DAY_MARKER = 32; // Special marker for last day of month. + private static final int CALENDAR_MONTH_OFFSET = 1; // Calendar months are 0-based. + private static final int CALENDAR_DAY_OF_WEEK_OFFSET = 1; // Convert to 0 = Sunday. + private static final int SEARCH_LIMIT_YEARS = 4; // Maximum years to search for next match. + private static final int MINIMUM_CRON_FIELDS = 5; + private static final int MAXIMUM_CRON_FIELDS = 6; + private static final int CRON_PARTS_EXPECTED = 2; + private static final String PIPE_SEPARATOR = "\\|"; + private static final String WHITESPACE_PATTERN = "\\s+"; + private static final String FIELD_VALIDATION_REGEX = "^[0-9a-zA-Z*,\\-/:~+L]+$"; + private static final String NO_FUTURE_MATCH_MESSAGE = "No future match."; + + // Month aliases for improved readability. + private static final Map MONTH_ALIASES = new HashMap<>(); + static + { + MONTH_ALIASES.put("jan", 1); + MONTH_ALIASES.put("feb", 2); + MONTH_ALIASES.put("mar", 3); + MONTH_ALIASES.put("apr", 4); + MONTH_ALIASES.put("may", 5); + MONTH_ALIASES.put("jun", 6); + MONTH_ALIASES.put("jul", 7); + MONTH_ALIASES.put("aug", 8); + MONTH_ALIASES.put("sep", 9); + MONTH_ALIASES.put("oct", 10); + MONTH_ALIASES.put("nov", 11); + MONTH_ALIASES.put("dec", 12); + } + + // Day of week aliases for improved readability. + private static final Map DAY_ALIASES = new HashMap<>(); + static + { + DAY_ALIASES.put("sun", 0); + DAY_ALIASES.put("mon", 1); + DAY_ALIASES.put("tue", 2); + DAY_ALIASES.put("wed", 3); + DAY_ALIASES.put("thu", 4); + DAY_ALIASES.put("fri", 5); + DAY_ALIASES.put("sat", 6); + } + + // Pattern data. + private final String _originalPattern; + private final List _cronExpressions; + + /** + * Creates a new scheduling pattern from a cron-like string. + * @param pattern The cron pattern string + * @throws RuntimeException if the pattern is invalid. + */ + public SchedulingPattern(String pattern) throws RuntimeException + { + _originalPattern = Objects.requireNonNull(pattern, "Pattern cannot be null."); + try + { + _cronExpressions = parsePattern(pattern); + } + catch (Exception e) + { + throw new RuntimeException("Invalid scheduling pattern: " + pattern, e); + } + } + + /** + * Validates whether a string is a valid scheduling pattern. + * @param schedulingPattern The pattern to validate + * @return true if valid, false otherwise + */ + public static boolean validate(String schedulingPattern) + { + if (schedulingPattern == null) + { + return false; + } + + try + { + // Lightweight validation without full parsing. + final String[] orPatterns = schedulingPattern.split(PIPE_SEPARATOR); + for (String orPattern : orPatterns) + { + final String[] fields = orPattern.trim().split(WHITESPACE_PATTERN); + if ((fields.length < MINIMUM_CRON_FIELDS) || (fields.length > MAXIMUM_CRON_FIELDS)) + { + return false; + } + + // Basic syntax validation for each field. + if (!isValidField(fields[0]) || !isValidField(fields[1]) || !isValidField(fields[2]) || !isValidField(fields[3]) || !isValidField(fields[4])) + { + return false; + } + + // Validate optional week offset field. + if (fields.length == MAXIMUM_CRON_FIELDS) + { + final String weekField = fields[5].trim(); + if (!weekField.startsWith("+") || !StringUtil.isNumeric(weekField.substring(1))) + { + return false; + } + } + } + + return true; + } + catch (Exception e) + { + return false; + } + } + + /** + * Lightweight field validation for pattern syntax checking. + * @param field the field to validate + * @return true if field has valid basic syntax + */ + private static boolean isValidField(String field) + { + if ((field == null) || field.trim().isEmpty()) + { + return false; + } + + // Check for valid characters and basic syntax. + return field.matches(FIELD_VALIDATION_REGEX); + } + + /** + * Checks if the given timestamp matches this pattern. + * @param timezone The timezone to use + * @param millis The timestamp in milliseconds + * @return true if the timestamp matches + */ + public boolean match(TimeZone timezone, long millis) + { + final Calendar calendar = Calendar.getInstance(timezone); + calendar.setTimeInMillis(millis); + + // Normalize to minute precision. + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MILLISECOND, 0); + + // Use traditional loop for better performance in hot paths. + for (CronExpression cronExpression : _cronExpressions) + { + if (cronExpression.matches(calendar)) + { + return true; + } + } + + return false; + } + + /** + * Checks if the given timestamp matches this pattern using system timezone. + * @param millis The timestamp in milliseconds + * @return true if the timestamp matches + */ + public boolean match(long millis) + { + return match(TimeZone.getDefault(), millis); + } + + /** + * Finds the next matching time after the given timestamp. + * @param timezone The timezone to use + * @param millis The timestamp to search after + * @return The next matching timestamp in milliseconds + */ + public long next(TimeZone timezone, long millis) + { + long earliestMatch = -1L; + + // Use traditional loop for better performance in hot paths. + for (CronExpression cronExpression : _cronExpressions) + { + final long nextMatch = cronExpression.getNextMatch(millis, timezone); + if ((nextMatch > millis) && ((earliestMatch == -1L) || (nextMatch < earliestMatch))) + { + earliestMatch = nextMatch; + } + } + + return earliestMatch; + } + + /** + * Finds the next matching time after the given timestamp using system timezone. + * @param millis The timestamp to search after + * @return The next matching timestamp in milliseconds + */ + public long next(long millis) + { + return next(TimeZone.getDefault(), millis); + } + + /** + * Gets delay from a specific time until next match. + * @param millis The base timestamp + * @return Delay in milliseconds until next match after millis + */ + public long nextFrom(long millis) + { + final long nextMatch = next(millis); + return nextMatch > millis ? nextMatch - millis : -1; + } + + /** + * Gets delay from current time until next match. + * @return Delay in milliseconds until next match from now + */ + public long nextFromNow() + { + return nextFrom(System.currentTimeMillis()); + } + + /** + * Gets the delay in milliseconds until the next match from now. + * @return Delay in milliseconds until next match + */ + public long getDelayToNextFromNow() + { + return nextFromNow(); + } + + /** + * Gets delay with offset subtraction. + * @param offsetInMinutes Offset to subtract from delay + * @return Adjusted delay in milliseconds + */ + public long getOffsettedDelayToNextFromNow(int offsetInMinutes) + { + final long delay = getDelayToNextFromNow(); + final long offsetMillis = TimeUnit.MINUTES.toMillis(offsetInMinutes); + return Math.max(0, delay - offsetMillis); + } + + /** + * Gets the next matching time from now as a formatted date string. + * @return Formatted date string of next match + */ + public String getNextAsFormattedDateString() + { + final long nextMatch = next(System.currentTimeMillis()); + return nextMatch > 0 ? new Date(nextMatch).toString() : NO_FUTURE_MATCH_MESSAGE; + } + + @Override + public String toString() + { + return _originalPattern; + } + + /** + * Parses the pattern string into cron expressions. + * @param pattern the pattern string to parse + * @return list of cron expressions + */ + private List parsePattern(String pattern) + { + final List result = new ArrayList<>(); + + // Split on pipe for OR expressions. + final String[] orPatterns = pattern.split(PIPE_SEPARATOR); + + for (String orPattern : orPatterns) + { + final String[] fields = orPattern.trim().split(WHITESPACE_PATTERN); + if ((fields.length < MINIMUM_CRON_FIELDS) || (fields.length > MAXIMUM_CRON_FIELDS)) + { + throw new IllegalArgumentException("Pattern must have 5 or 6 fields: " + orPattern); + } + + try + { + // Parse fields with extended syntax support. + final ExtendedFieldResult minuteResult = parseExtendedField(fields[0]); + final ExtendedFieldResult hourResult = parseExtendedField(fields[1]); + final ExtendedFieldResult dayResult = parseExtendedField(fields[2]); + + final FieldMatcher minuteMatcher = parseField(minuteResult.pattern, MINUTE_MIN, MINUTE_MAX, null); + final FieldMatcher hourMatcher = parseField(hourResult.pattern, HOUR_MIN, HOUR_MAX, null); + final FieldMatcher dayMatcher = parseField(dayResult.pattern, DAY_MIN, DAY_MAX, null); + final FieldMatcher monthMatcher = parseField(fields[3], MONTH_MIN, MONTH_MAX, MONTH_ALIASES); + final FieldMatcher dayOfWeekMatcher = parseField(fields[4], DAY_OF_WEEK_MIN, DAY_OF_WEEK_MAX, DAY_ALIASES); + + // Parse optional week offset (6th field). + int weekOffset = 0; + if (fields.length == MAXIMUM_CRON_FIELDS) + { + final String weekField = fields[5].trim(); + if (weekField.startsWith("+")) + { + weekOffset = Integer.parseInt(weekField.substring(1)); + } + else + { + throw new IllegalArgumentException("Week offset must start with '+': " + weekField); + } + } + + result.add(new CronExpression(minuteMatcher, hourMatcher, dayMatcher, monthMatcher, dayOfWeekMatcher, minuteResult.randomModifier, hourResult.randomModifier, hourResult.addModifier, dayResult.addModifier, weekOffset)); + } + catch (Exception e) + { + throw new IllegalArgumentException("Invalid pattern format: " + orPattern, e); + } + } + + return result; + } + + /** + * Result of parsing an extended field with modifiers. + */ + private static class ExtendedFieldResult + { + final String pattern; + final int randomModifier; + final int addModifier; + + ExtendedFieldResult(String pattern, int randomModifier, int addModifier) + { + this.pattern = pattern; + this.randomModifier = randomModifier; + this.addModifier = addModifier; + } + } + + /** + * Parses a field that may contain extended syntax modifiers.
+ * Format: [modifier:]pattern where modifier can be ~N or +N. + * @param field the field to parse + * @return extended field result with pattern and modifiers + */ + private ExtendedFieldResult parseExtendedField(String field) + { + if (!field.contains(":")) + { + return new ExtendedFieldResult(field, 0, 0); + } + + final String[] parts = field.split(":"); + if (parts.length != CRON_PARTS_EXPECTED) + { + throw new IllegalArgumentException("Invalid extended field format: " + field); + } + + final String modifier = parts[0]; + final String pattern = parts[1]; + + int randomModifier = 0; + int addModifier = 0; + + if (modifier.startsWith("~")) + { + randomModifier = Integer.parseInt(modifier.substring(1)); + } + else if (modifier.startsWith("+")) + { + addModifier = Integer.parseInt(modifier.substring(1)); + } + else if (!modifier.isEmpty()) + { + throw new IllegalArgumentException("Unknown modifier: " + modifier); + } + + return new ExtendedFieldResult(pattern, randomModifier, addModifier); + } + + /** + * Parses a field value with support for wildcards, ranges, lists and step values. + * @param field the field string to parse + * @param min minimum allowed value + * @param max maximum allowed value + * @param aliases optional aliases map for named values + * @return field matcher for the parsed field + */ + private FieldMatcher parseField(String field, int min, int max, Map aliases) + { + if ("*".equals(field)) + { + return new WildcardMatcher(); + } + + final Set values = new HashSet<>(); + final String[] parts = field.split(","); + for (String part : parts) + { + values.addAll(parseFieldPart(part.trim(), min, max, aliases)); + } + + return new ValueSetMatcher(values); + } + + /** + * Parses a single field part with support for ranges and step values. + * @param part the field part to parse + * @param min minimum allowed value + * @param max maximum allowed value + * @param aliases optional aliases map for named values + * @return set of integer values matching the part + */ + private Set parseFieldPart(String part, int min, int max, Map aliases) + { + final Set values = new HashSet<>(); + + // Handle step values (e.g., */5 or 1-10/2). + final String[] stepParts = part.split("/"); + final int step = stepParts.length > 1 ? Integer.parseInt(stepParts[1]) : 1; + final String rangePart = stepParts[0]; + + if ("*".equals(rangePart)) + { + // */step pattern. + for (int i = min; i <= max; i += step) + { + values.add(i); + } + } + else if (rangePart.contains("-")) + { + // Range pattern (e.g., 1-5 or mon-fri). + final String[] range = rangePart.split("-", 2); + final int start = parseValue(range[0], aliases); + final int end = parseValue(range[1], aliases); + if (start <= end) + { + for (int i = start; i <= end; i += step) + { + values.add(i); + } + } + else + { + // Wrap-around range (e.g., fri-mon for days). + for (int i = start; i <= max; i += step) + { + values.add(i); + } + + for (int i = min; i <= end; i += step) + { + values.add(i); + } + } + } + else // Single value. + { + values.add(parseValue(rangePart, aliases)); + } + + return values; + } + + /** + * Parses a single value with support for aliases and special markers. + * @param value the value string to parse + * @param aliases optional aliases map for named values + * @return parsed integer value + */ + private int parseValue(String value, Map aliases) + { + if ("L".equalsIgnoreCase(value)) + { + return LAST_DAY_MARKER; // Special marker for last day of month. + } + + if ((aliases != null) && aliases.containsKey(value.toLowerCase())) + { + return aliases.get(value.toLowerCase()); + } + + try + { + return Integer.parseInt(value); + } + catch (NumberFormatException e) + { + throw new IllegalArgumentException("Invalid value: " + value, e); + } + } + + /** + * Interface for matching field values against calendar dates. + */ + private interface FieldMatcher + { + boolean matches(int value, Calendar calendar); + } + + /** + * Matcher that accepts any value (wildcard). + */ + private static class WildcardMatcher implements FieldMatcher + { + @Override + public boolean matches(int value, Calendar calendar) + { + return true; + } + } + + /** + * Matcher that checks against a predefined set of values. + */ + private static class ValueSetMatcher implements FieldMatcher + { + private final Set _values; + + ValueSetMatcher(Set values) + { + _values = new HashSet<>(values); + } + + @Override + public boolean matches(int value, Calendar calendar) + { + if (_values.contains(value)) + { + return true; + } + + // Handle last day of month (L). + if (_values.contains(LAST_DAY_MARKER) && isLastDayOfMonth(calendar)) + { + return true; + } + + return false; + } + + /** + * Checks if the calendar date is the last day of the month. + * @param calendar the calendar to check + * @return true if it's the last day of the month + */ + private boolean isLastDayOfMonth(Calendar calendar) + { + final int currentDay = calendar.get(Calendar.DAY_OF_MONTH); + final int lastDay = calendar.getActualMaximum(Calendar.DAY_OF_MONTH); + return currentDay == lastDay; + } + } + + /** + * Represents a single cron expression with extended modifiers. + */ + private static class CronExpression + { + // Field matchers. + private final FieldMatcher _minuteMatcher; + private final FieldMatcher _hourMatcher; + private final FieldMatcher _dayMatcher; + private final FieldMatcher _monthMatcher; + private final FieldMatcher _dayOfWeekMatcher; + + // Extended syntax modifiers. + private final int _minuteRandomModifier; + private final int _hourRandomModifier; + private final int _hourAddModifier; + private final int _dayAddModifier; + private final int _weekOffset; + + CronExpression(FieldMatcher minuteMatcher, FieldMatcher hourMatcher, FieldMatcher dayMatcher, FieldMatcher monthMatcher, FieldMatcher dayOfWeekMatcher, int minuteRandomModifier, int hourRandomModifier, int hourAddModifier, int dayAddModifier, int weekOffset) + { + _minuteMatcher = minuteMatcher; + _hourMatcher = hourMatcher; + _dayMatcher = dayMatcher; + _monthMatcher = monthMatcher; + _dayOfWeekMatcher = dayOfWeekMatcher; + _minuteRandomModifier = minuteRandomModifier; + _hourRandomModifier = hourRandomModifier; + _hourAddModifier = hourAddModifier; + _dayAddModifier = dayAddModifier; + _weekOffset = weekOffset; + } + + /** + * Checks if the calendar date matches this cron expression. + * @param calendar the calendar to test + * @return true if the date matches + */ + boolean matches(Calendar calendar) + { + // Create a copy for testing with offsets applied. + final Calendar testCalendar = Calendar.getInstance(calendar.getTimeZone()); + testCalendar.setTimeInMillis(calendar.getTimeInMillis()); + + // Apply reverse offsets for matching (subtract what would be added). + if (_weekOffset != 0) + { + testCalendar.add(Calendar.WEEK_OF_YEAR, -_weekOffset); + } + + if (_dayAddModifier != 0) + { + testCalendar.add(Calendar.DAY_OF_YEAR, -_dayAddModifier); + } + + if (_hourAddModifier != 0) + { + testCalendar.add(Calendar.HOUR_OF_DAY, -_hourAddModifier); + } + + final int minute = testCalendar.get(Calendar.MINUTE); + final int hour = testCalendar.get(Calendar.HOUR_OF_DAY); + final int day = testCalendar.get(Calendar.DAY_OF_MONTH); + final int month = testCalendar.get(Calendar.MONTH) + CALENDAR_MONTH_OFFSET; // Calendar months are 0-based. + final int dayOfWeek = testCalendar.get(Calendar.DAY_OF_WEEK) - CALENDAR_DAY_OF_WEEK_OFFSET; // Convert to 0 = Sunday. + return _minuteMatcher.matches(minute, testCalendar) && _hourMatcher.matches(hour, testCalendar) && _dayMatcher.matches(day, testCalendar) && _monthMatcher.matches(month, testCalendar) && _dayOfWeekMatcher.matches(dayOfWeek, testCalendar); + } + + /** + * Finds the next matching time after the specified timestamp. + * @param afterMillis timestamp to search after + * @param timeZone timezone for calculation + * @return next matching timestamp in milliseconds + */ + long getNextMatch(long afterMillis, TimeZone timeZone) + { + final Calendar calendar = Calendar.getInstance(timeZone); + calendar.setTimeInMillis(afterMillis); + calendar.add(Calendar.MINUTE, 1); // Start from next minute. + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MILLISECOND, 0); + + // Search up to 4 years in the future to avoid infinite loops. + final Calendar endCalendar = Calendar.getInstance(timeZone); + endCalendar.setTimeInMillis(afterMillis); + endCalendar.add(Calendar.YEAR, SEARCH_LIMIT_YEARS); + + // Reuse single calendar instance for result calculations. + final Calendar resultCalendar = Calendar.getInstance(timeZone); + + while (calendar.before(endCalendar)) + { + if (matches(calendar)) + { + // Apply forward offsets and randomization. + resultCalendar.setTimeInMillis(calendar.getTimeInMillis()); + + // Apply fixed offsets. + if (_weekOffset != 0) + { + resultCalendar.add(Calendar.WEEK_OF_YEAR, _weekOffset); + } + + if (_dayAddModifier != 0) + { + resultCalendar.add(Calendar.DAY_OF_YEAR, _dayAddModifier); + } + + if (_hourAddModifier != 0) + { + resultCalendar.add(Calendar.HOUR_OF_DAY, _hourAddModifier); + } + + // Apply random offsets. + if (_hourRandomModifier > 0) + { + resultCalendar.add(Calendar.HOUR_OF_DAY, Rnd.get(_hourRandomModifier + 1)); + } + + if (_minuteRandomModifier > 0) + { + resultCalendar.add(Calendar.MINUTE, Rnd.get(_minuteRandomModifier + 1)); + } + + return resultCalendar.getTimeInMillis(); + } + + calendar.add(Calendar.MINUTE, 1); + } + + return -1; // No match found. + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/time/TimeUtil.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/time/TimeUtil.java new file mode 100644 index 0000000..bbb8830 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/time/TimeUtil.java @@ -0,0 +1,268 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.commons.time; + +import java.time.Duration; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.Calendar; +import java.util.Date; + +/** + * Utility class for time-related operations, such as parsing durations, scheduling future dates and formatting dates. + * @author Mobius + */ +public class TimeUtil +{ + /** + * Parses a string duration (e.g., "5days", "2hours") into a {@link Duration} object. + * @param durationString the string representing the duration with a numeric value and time unit (e.g., "5days", "10hours", "2weeks"). + * @return a {@link Duration} object representing the specified duration. + * @throws IllegalArgumentException if the input format is invalid or the unit is unrecognized. + */ + public static Duration parseDuration(String durationString) + { + int index = 0; + while ((index < durationString.length()) && Character.isDigit(durationString.charAt(index))) + { + index++; + } + + if ((index == 0) || (index == durationString.length())) + { + throw new IllegalArgumentException("Invalid duration format: " + durationString); + } + + int durationValue; + String durationUnit; + try + { + durationValue = Integer.parseInt(durationString.substring(0, index)); + durationUnit = durationString.substring(index).toLowerCase(); + } + catch (NumberFormatException e) + { + throw new IllegalArgumentException("Invalid duration format: " + durationString); + } + + switch (durationUnit) + { + case "sec": + case "secs": + { + return Duration.ofSeconds(durationValue); + } + case "min": + case "mins": + { + return Duration.ofMinutes(durationValue); + } + case "hour": + case "hours": + { + return Duration.ofHours(durationValue); + } + case "day": + case "days": + { + return Duration.ofDays(durationValue); + } + case "week": + case "weeks": + { + return Duration.ofDays(durationValue * 7L); + } + case "month": + case "months": + { + return Duration.ofDays(durationValue * 30L); + } + case "year": + case "years": + { + return Duration.ofDays(durationValue * 365L); + } + default: + { + throw new IllegalArgumentException("Unrecognized time unit: " + durationUnit); + } + } + } + + /** + * Formats a duration in milliseconds into a user-friendly string, specifying the number of days, hours, minutes, seconds, and milliseconds (if any). + * @param millis the duration in milliseconds. + * @return a formatted string representing the duration. + */ + public static String formatDuration(long millis) + { + if (millis < 1) + { + return "0 milliseconds"; + } + + long days = millis / (24 * 60 * 60 * 1000); + millis %= (24 * 60 * 60 * 1000); + + long hours = millis / (60 * 60 * 1000); + millis %= (60 * 60 * 1000); + + long minutes = millis / (60 * 1000); + millis %= (60 * 1000); + + long seconds = millis / 1000; + millis %= 1000; + + final StringBuilder sb = new StringBuilder(); + if (days > 0) + { + sb.append(days).append(" day").append(days > 1 ? "s" : "").append(", "); + } + + if (hours > 0) + { + sb.append(hours).append(" hour").append(hours > 1 ? "s" : "").append(", "); + } + + if (minutes > 0) + { + sb.append(minutes).append(" minute").append(minutes > 1 ? "s" : "").append(", "); + } + + if (seconds > 0) + { + sb.append(seconds).append(" second").append(seconds > 1 ? "s" : "").append(", "); + } + + if (millis > 0) + { + sb.append(millis).append(" millisecond").append(millis > 1 ? "s" : ""); + } + + // Remove the trailing comma and space, if present. + if ((sb.length() > 2) && (sb.charAt(sb.length() - 2) == ',')) + { + sb.setLength(sb.length() - 2); + } + + return sb.toString(); + } + + /** + * Formats a date into a string based on the provided format pattern. + * @param date the {@link Date} object to format. + * @param format the date format pattern (e.g., "dd/MM/yyyy"). + * @return a formatted date string or null if the date is null. + */ + public static String formatDate(Date date, String format) + { + return date == null ? null : DateTimeFormatter.ofPattern(format).format(date.toInstant().atZone(ZoneId.systemDefault())); + } + + /** + * Formats a date to a string in the "dd/MM/yyyy" format. + * @param date the {@link Date} object to format. + * @return a formatted date string or null if the date is null. + */ + public static String getDateString(Date date) + { + return formatDate(date, "dd/MM/yyyy"); + } + + /** + * Formats a date to a string in the "dd/MM/yyyy HH:mm:ss" format. + * @param date the {@link Date} object to format. + * @return a formatted date-time string or null if the date is null. + */ + public static String getDateTimeString(Date date) + { + return formatDate(date, "dd/MM/yyyy HH:mm:ss"); + } + + /** + * Formats a timestamp (in milliseconds) to a string in the "dd/MM/yyyy" format. + * @param millis the timestamp in milliseconds. + * @return a formatted date string. + */ + public static String getDateString(long millis) + { + return getDateString(new Date(millis)); + } + + /** + * Formats a timestamp (in milliseconds) to a string in the "dd/MM/yyyy HH:mm:ss" format. + * @param millis the timestamp in milliseconds. + * @return a formatted date-time string. + */ + public static String getDateTimeString(long millis) + { + return getDateTimeString(new Date(millis)); + } + + /** + * Gets the next occurrence of the specified day of the week, hour, and minute. If the specified time is in the past for today, the next week will be scheduled. + * @param dayOfWeek the desired day of the week (e.g., {@link Calendar#MONDAY}). + * @param hour the hour of the day (0-23). + * @param minute the minute of the hour (0-59). + * @return a {@link Calendar} object set to the next occurrence of the specified day and time. + */ + public static Calendar getNextDayTime(int dayOfWeek, int hour, int minute) + { + final Calendar calendar = Calendar.getInstance(); + final int today = calendar.get(Calendar.DAY_OF_WEEK); + int daysUntilNext = ((dayOfWeek - today) + 7) % 7; + if ((daysUntilNext == 0) && ((calendar.get(Calendar.HOUR_OF_DAY) > hour) || ((calendar.get(Calendar.HOUR_OF_DAY) == hour) && (calendar.get(Calendar.MINUTE) >= minute)))) + { + daysUntilNext = 7; // Schedule for the next week if today's time has passed. + } + + calendar.add(Calendar.DAY_OF_MONTH, daysUntilNext); + calendar.set(Calendar.HOUR_OF_DAY, hour); + calendar.set(Calendar.MINUTE, minute); + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MILLISECOND, 0); + + return calendar; + } + + /** + * Gets the next occurrence of the specified hour and minute on the current day. If the specified time is in the past for today, the next day will be scheduled. + * @param hour the hour of the day (0-23). + * @param minute the minute of the hour (0-59). + * @return a {@link Calendar} object set to the next occurrence of the specified time. + */ + public static Calendar getNextTime(int hour, int minute) + { + final Calendar calendar = Calendar.getInstance(); + calendar.set(Calendar.HOUR_OF_DAY, hour); + calendar.set(Calendar.MINUTE, minute); + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MILLISECOND, 0); + + // If the target time has already passed today, schedule for the next day. + if (calendar.before(Calendar.getInstance())) + { + calendar.add(Calendar.DAY_OF_YEAR, 1); + } + + return calendar; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/ui/DarkTheme.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/ui/DarkTheme.java new file mode 100644 index 0000000..2a815da --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/ui/DarkTheme.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.commons.ui; + +import java.awt.Color; + +import javax.swing.UIManager; +import javax.swing.plaf.nimbus.NimbusLookAndFeel; + +/** + * @author Mobius + */ +public class DarkTheme +{ + public static void activate() + { + // Modify existing white Nimbus look and feel to dark. + UIManager.put("control", new Color(128, 128, 128)); + UIManager.put("info", new Color(128, 128, 128)); + UIManager.put("nimbusBase", Color.DARK_GRAY); // new Color(18, 30, 49) + UIManager.put("nimbusAlertYellow", new Color(248, 187, 0)); + UIManager.put("nimbusDisabledText", new Color(128, 128, 128)); + UIManager.put("nimbusFocus", Color.DARK_GRAY); // new Color(115, 164, 209) + UIManager.put("nimbusGreen", new Color(176, 179, 50)); + UIManager.put("nimbusInfoBlue", Color.DARK_GRAY); // new Color(66, 139, 221) + UIManager.put("nimbusLightBackground", Color.DARK_GRAY); // new Color(18, 30, 49) + UIManager.put("nimbusOrange", new Color(191, 98, 4)); + UIManager.put("nimbusRed", new Color(169, 46, 34)); + UIManager.put("nimbusSelectedText", new Color(255, 255, 255)); + UIManager.put("nimbusSelectionBackground", new Color(104, 93, 156)); + UIManager.put("text", new Color(230, 230, 230)); + + // Set look and feel. + try + { + UIManager.setLookAndFeel(new NimbusLookAndFeel()); + } + catch (Exception e) + { + e.printStackTrace(); + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/ui/LineLimitListener.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/ui/LineLimitListener.java new file mode 100644 index 0000000..9508592 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/ui/LineLimitListener.java @@ -0,0 +1,170 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.commons.ui; + +import javax.swing.SwingUtilities; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import javax.swing.text.BadLocationException; +import javax.swing.text.Document; +import javax.swing.text.Element; + +/** + * A {@link DocumentListener} to limit the maximum number of lines in a Document.
+ * If the number of lines exceeds the specified limit, excess lines will be removed either from the start or the end of the Document, depending on the specified configuration:
+ * a) Removing from the start is typically used when appending text.
+ * b) Removing from the end is used when inserting text at the beginning. + * @author Mobius + */ +public class LineLimitListener implements DocumentListener +{ + private final boolean _removeFromStart; + private final int _maxLines; + + /** + * Constructs a LineLimitListener with a specified maximum line count.
+ * By default, this configuration removes excess lines from the start of the Document. + * @param maxLines the maximum number of lines to retain in the Document + */ + public LineLimitListener(int maxLines) + { + this(maxLines, true); + } + + /** + * Constructs a LineLimitListener with a specified maximum line count
+ * and a setting to control where excess lines are removed from. + * @param maxLines the maximum number of lines to retain in the Document + * @param removeFromStart if true, excess lines are removed from the start; if false, from the end + */ + public LineLimitListener(int maxLines, boolean removeFromStart) + { + _removeFromStart = removeFromStart; + _maxLines = maxLines; + } + + /** + * Returns the maximum number of lines that this listener will retain in the Document. + * @return the maximum line count allowed in the Document + */ + public int getLimitLines() + { + return _maxLines; + } + + /** + * Removes excess lines from the Document when the line count exceeds the maximum limit.
+ * This method determines whether to remove lines from the start or end based on the configured setting. + * @param event the DocumentEvent that triggered this method call + */ + private void removeLines(DocumentEvent event) + { + // The root Element of the Document will tell us the total number of line in the Document. + final Document document = event.getDocument(); + final Element root = document.getDefaultRootElement(); + + while (root.getElementCount() > _maxLines) + { + if (_removeFromStart) + { + removeFromStart(document, root); + } + else + { + removeFromEnd(document, root); + } + } + } + + /** + * Removes lines from the start of the Document until the line count is within the limit. + * @param document the Document to be modified + * @param root the root Element representing all lines in the Document + */ + private void removeFromStart(Document document, Element root) + { + final Element line = root.getElement(0); + final int end = line.getEndOffset(); + + try + { + document.remove(0, end); + } + catch (BadLocationException ble) + { + System.out.println(ble); + } + } + + /** + * Removes lines from the end of the Document until the line count is within the limit.
+ * The newline character preceding the last line is also removed to maintain line integrity. + * @param document the Document to be modified + * @param root the root Element representing all lines in the Document + */ + private void removeFromEnd(Document document, Element root) + { + // We use start minus 1 to make sure we remove the newline character of the previous line. + final Element line = root.getElement(root.getElementCount() - 1); + final int start = line.getStartOffset(); + final int end = line.getEndOffset(); + + try + { + document.remove(start - 1, end - start); + } + catch (BadLocationException e) + { + System.out.println(e); + } + } + + /** + * Handles the insertion of new text into the Document. After text is inserted,
+ * this method schedules a check (on the EDT) to remove lines if the maximum line count is exceeded. + * @param event the DocumentEvent representing the text insertion + */ + @Override + public void insertUpdate(DocumentEvent event) + { + SwingUtilities.invokeLater(() -> removeLines(event)); + } + + /** + * No action taken on text removal, as this event does not affect line limit enforcement. + * @param event the DocumentEvent representing the text removal + */ + @Override + public void removeUpdate(DocumentEvent event) + { + // No action required. + } + + /** + * No action taken on attribute changes, as this event does not affect line limit enforcement. + * @param event the DocumentEvent representing the attribute change + */ + @Override + public void changedUpdate(DocumentEvent event) + { + // No action required. + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/ui/SplashScreen.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/ui/SplashScreen.java new file mode 100644 index 0000000..21fad79 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/ui/SplashScreen.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.commons.ui; + +import java.awt.Color; +import java.awt.Frame; +import java.awt.Graphics; +import java.awt.Image; +import java.awt.Toolkit; +import java.util.Timer; +import java.util.TimerTask; + +import javax.swing.ImageIcon; +import javax.swing.JFrame; +import javax.swing.JWindow; + +/** + * @author Mobius + */ +public class SplashScreen extends JWindow +{ + private final Image _image; + + /** + * @param path of image file + * @param time in milliseconds + * @param parent frame to set visible after time ends + */ + public SplashScreen(String path, long time, JFrame parent) + { + setBackground(new Color(0, 255, 0, 0)); // Transparency. + _image = Toolkit.getDefaultToolkit().getImage(path); + final ImageIcon imageIcon = new ImageIcon(_image); + setSize(imageIcon.getIconWidth(), imageIcon.getIconHeight()); + setLocationRelativeTo(null); + setAlwaysOnTop(true); + setVisible(true); + + new Timer().schedule(new TimerTask() + { + @Override + public void run() + { + setVisible(false); + if (parent != null) + { + // Make parent visible. + parent.setVisible(true); + + // Focus parent window. + parent.toFront(); + parent.setState(Frame.ICONIFIED); + parent.setState(Frame.NORMAL); + } + + dispose(); + } + }, imageIcon.getIconWidth() > 0 ? time : 100); + } + + @Override + public void paint(Graphics g) + { + g.drawImage(_image, 0, 0, null); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/util/ConfigReader.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/util/ConfigReader.java new file mode 100644 index 0000000..76cf12d --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/util/ConfigReader.java @@ -0,0 +1,411 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.commons.util; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.time.Duration; +import java.util.Arrays; +import java.util.Collection; +import java.util.Properties; +import java.util.logging.Logger; + +import org.l2jmobius.commons.time.TimeUtil; + +/** + * ConfigReader is a utility class that reads and provides access to configuration properties from a file. + * @author Mobius + */ +public class ConfigReader +{ + private static final Logger LOGGER = Logger.getLogger(ConfigReader.class.getName()); + + private final Properties _properties = new Properties(); + private final File _file; + + /** + * Constructs a ConfigReader with the specified file path using the system's default Charset. + * @param filePath the path to the configuration file + */ + public ConfigReader(String filePath) + { + _file = new File(filePath); + + if (!Files.exists(_file.toPath())) + { + LOGGER.warning("Configuration file not found: " + _file.getAbsolutePath()); + return; + } + + try (InputStream input = Files.newInputStream(_file.toPath()); + InputStreamReader reader = new InputStreamReader(input, Charset.defaultCharset())) + { + _properties.load(reader); + } + catch (IOException e) + { + LOGGER.warning("Failed to load configurations from " + _file.getName() + ": " + e.getMessage()); + } + } + + /** + * Checks if the specified configuration key exists in the configurations. + * @param config the configuration key + * @return true if the key exists, false otherwise + */ + public boolean containsKey(String config) + { + return _properties.containsKey(config); + } + + /** + * Retrieves the value associated with the specified key as a String. + * @param config the configuration key + * @return the property value as a String, or null if the key does not exist + */ + public String getValue(String config) + { + return _properties.getProperty(config); + } + + /** + * Returns an unmodifiable collection of all property names that have string values. + * @return a {@link Collection} of property names as strings. + */ + public Collection getStringPropertyNames() + { + return _properties.stringPropertyNames(); + } + + /** + * Retrieves the value associated with the specified key as a boolean. + * @param config the configuration key + * @param defaultValue the default value if the key does not exist or is malformed + * @return the property value as a boolean, or the default value if the key does not exist or is malformed + */ + public boolean getBoolean(String config, boolean defaultValue) + { + final String value = _properties.getProperty(config); + if (value != null) + { + try + { + return Boolean.parseBoolean(value); + } + catch (Exception e) + { + LOGGER.warning("Invalid boolean for config '" + config + "' in file '" + _file.getName() + "', using default: " + defaultValue + "."); + } + } + else + { + LOGGER.warning("Config '" + config + "' not found in file '" + _file.getName() + "', using default: " + defaultValue + "."); + } + + return defaultValue; + } + + /** + * Retrieves the value associated with the specified key as a byte. + * @param config the configuration key + * @param defaultValue the default value if the key does not exist or is malformed + * @return the property value as a byte, or the default value if the key does not exist or is malformed + */ + public byte getByte(String config, byte defaultValue) + { + final String value = _properties.getProperty(config); + if (value != null) + { + try + { + return Byte.parseByte(value); + } + catch (Exception e) + { + LOGGER.warning("Invalid byte for config '" + config + "' in file '" + _file.getName() + "', using default: " + defaultValue + "."); + } + } + else + { + LOGGER.warning("Config '" + config + "' not found in file '" + _file.getName() + "', using default: " + defaultValue + "."); + } + + return defaultValue; + } + + /** + * Retrieves the value associated with the specified key as a short. + * @param config the configuration key + * @param defaultValue the default value if the key does not exist or is malformed + * @return the property value as a short, or the default value if the key does not exist or is malformed + */ + public short getShort(String config, short defaultValue) + { + final String value = _properties.getProperty(config); + if (value != null) + { + try + { + return Short.parseShort(value); + } + catch (Exception e) + { + LOGGER.warning("Invalid short for config '" + config + "' in file '" + _file.getName() + "', using default: " + defaultValue + "."); + } + } + else + { + LOGGER.warning("Config '" + config + "' not found in file '" + _file.getName() + "', using default: " + defaultValue + "."); + } + + return defaultValue; + } + + /** + * Retrieves the value associated with the specified key as an int. + * @param config the configuration key + * @param defaultValue the default value if the key does not exist or is malformed + * @return the property value as an int, or the default value if the key does not exist or is malformed + */ + public int getInt(String config, int defaultValue) + { + final String value = _properties.getProperty(config); + if (value != null) + { + try + { + return Integer.parseInt(value); + } + catch (Exception e) + { + LOGGER.warning("Invalid int for config '" + config + "' in file '" + _file.getName() + "', using default: " + defaultValue + "."); + } + } + else + { + LOGGER.warning("Config '" + config + "' not found in file '" + _file.getName() + "', using default: " + defaultValue + "."); + } + + return defaultValue; + } + + /** + * Retrieves the value associated with the specified key as a long. + * @param config the configuration key + * @param defaultValue the default value if the key does not exist or is malformed + * @return the property value as a long, or the default value if the key does not exist or is malformed + */ + public long getLong(String config, long defaultValue) + { + final String value = _properties.getProperty(config); + if (value != null) + { + try + { + return Long.parseLong(value); + } + catch (Exception e) + { + LOGGER.warning("Invalid long for config '" + config + "' in file '" + _file.getName() + "', using default: " + defaultValue + "."); + } + } + else + { + LOGGER.warning("Config '" + config + "' not found in file '" + _file.getName() + "', using default: " + defaultValue + "."); + } + + return defaultValue; + } + + /** + * Retrieves the value associated with the specified key as a float. + * @param config the configuration key + * @param defaultValue the default value if the key does not exist or is malformed + * @return the property value as a float, or the default value if the key does not exist or is malformed + */ + public float getFloat(String config, float defaultValue) + { + final String value = _properties.getProperty(config); + if (value != null) + { + try + { + return Float.parseFloat(value); + } + catch (Exception e) + { + LOGGER.warning("Invalid float for config '" + config + "' in file '" + _file.getName() + "', using default: " + defaultValue + "."); + } + } + else + { + LOGGER.warning("Config '" + config + "' not found in file '" + _file.getName() + "', using default: " + defaultValue + "."); + } + + return defaultValue; + } + + /** + * Retrieves the value associated with the specified key as a double. + * @param config the configuration key + * @param defaultValue the default value if the key does not exist or is malformed + * @return the property value as a double, or the default value if the key does not exist or is malformed + */ + public double getDouble(String config, double defaultValue) + { + final String value = _properties.getProperty(config); + if (value != null) + { + try + { + return Double.parseDouble(value); + } + catch (Exception e) + { + LOGGER.warning("Invalid double for config '" + config + "' in file '" + _file.getName() + "', using default: " + defaultValue + "."); + } + } + else + { + LOGGER.warning("Config '" + config + "' not found in file '" + _file.getName() + "', using default: " + defaultValue + "."); + } + + return defaultValue; + } + + /** + * Retrieves the value associated with the specified key as a String. + * @param config the configuration key + * @param defaultValue the default value if the key does not exist + * @return the property value as a String, or the default value if the key does not exist + */ + public String getString(String config, String defaultValue) + { + final String value = _properties.getProperty(config); + if (value == null) + { + LOGGER.warning("Config '" + config + "' not found in file '" + _file.getName() + "', using default: " + defaultValue + "."); + return defaultValue; + } + + return value; + } + + /** + * Retrieves the value associated with the specified key as an enum constant of the specified type. + * @param the type of the enum + * @param config the configuration key + * @param clazz the enum class to parse the value as + * @param defaultValue the default value if the key does not exist or is malformed + * @return the property value as an enum constant, or the default value if the key does not exist or is malformed + */ + public > T getEnum(String config, Class clazz, T defaultValue) + { + final String value = _properties.getProperty(config); + if (value != null) + { + try + { + return Enum.valueOf(clazz, value); + } + catch (Exception e) + { + LOGGER.warning("Invalid enum for config '" + config + "' in file '" + _file.getName() + "', using default: " + defaultValue + "."); + } + } + else + { + LOGGER.warning("Config '" + config + "' not found in file '" + _file.getName() + "', using default: " + defaultValue + "."); + } + + return defaultValue; + } + + /** + * Retrieves the value associated with the specified key as a Duration. + * @param config the configuration key + * @param defaultValue the default value as a string if the key does not exist or is malformed + * @return the property value as a Duration, or the parsed default value if the key does not exist or is malformed + */ + public Duration getDuration(String config, String defaultValue) + { + final String value = _properties.getProperty(config); + if (value != null) + { + try + { + return TimeUtil.parseDuration(value); + } + catch (Exception e) + { + LOGGER.warning("Invalid duration for config '" + config + "' in file '" + _file.getName() + "', using default: " + defaultValue + "."); + } + } + else + { + LOGGER.warning("Config '" + config + "' not found in file '" + _file.getName() + "', using default: " + defaultValue + "."); + } + + return TimeUtil.parseDuration(defaultValue); + } + + /** + * Retrieves the value associated with the specified key as an int array. + * @param config the configuration key + * @param delimiter the separator used to split the string into integers + * @param defaultValue the default value as a string if the key does not exist or is malformed + * @return the property value, or the default value if the key does not exist or is malformed, as an int array + */ + public int[] getIntArray(String config, String delimiter, String defaultValue) + { + final String value = _properties.getProperty(config); + if (value != null) + { + try + { + return Arrays.stream(value.split(delimiter)).map(String::trim).mapToInt(Integer::parseInt).toArray(); + } + catch (NumberFormatException e) + { + LOGGER.warning("Invalid int array for config '" + config + "' in file '" + _file.getName() + "', using default values."); + } + } + else + { + LOGGER.warning("Config '" + config + "' not found in file '" + _file.getName() + "', using default values."); + } + + try + { + return Arrays.stream(defaultValue.split(delimiter)).map(String::trim).mapToInt(Integer::parseInt).toArray(); + } + catch (NumberFormatException e) + { + LOGGER.warning("Invalid default values for config '" + config + "' in file '" + _file.getName() + "', using empty array."); + } + + return new int[0]; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/util/HexUtil.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/util/HexUtil.java new file mode 100644 index 0000000..b08b1e4 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/util/HexUtil.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.commons.util; + +/** + * Utility class for handling operations related to hexadecimal data. + * @author Mobius + */ +public class HexUtil +{ + /** + * Generates a byte array of the specified size filled with random non-zero values. + * @param size the size of the byte array to generate + * @return a byte array filled with random non-zero values + */ + public static byte[] generateHexBytes(int size) + { + final byte[] array = new byte[size]; + Rnd.nextBytes(array); + + // Ensure no zero values are in the array. + for (int i = 0; i < array.length; i++) + { + while (array[i] == 0) + { + array[i] = (byte) Rnd.get(Byte.MAX_VALUE); + } + } + + return array; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/util/IXmlReader.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/util/IXmlReader.java new file mode 100644 index 0000000..c0bbc67 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/util/IXmlReader.java @@ -0,0 +1,738 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.commons.util; + +import java.io.File; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; +import java.util.function.Predicate; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; + +import org.w3c.dom.Document; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXParseException; + +import org.l2jmobius.commons.config.ThreadConfig; + +/** + * Interface for XML parsers. + * @author Zoey76, Mobius + */ +public interface IXmlReader +{ + static final Logger LOGGER = Logger.getLogger(IXmlReader.class.getName()); + + static final String JAXP_SCHEMA_LANGUAGE = "http://java.sun.com/xml/jaxp/properties/schemaLanguage"; + static final String W3C_XML_SCHEMA = "http://www.w3.org/2001/XMLSchema"; + + /** + * Loads or reloads the data. It is recommended to clear the data storage (either a list or a map) before loading. + */ + void load(); + + /** + * Parses an XML file located within the datapack directory. This is a helper method for {@link #parseFile(File)}. + * @param path the relative path of the XML file within the datapack directory. + */ + default void parseDatapackFile(String path) + { + parseFile(new File(".", path)); + } + + /** + * Parses a single XML file. Calls {@link #parseDocument(Document, File)} if the file is successfully parsed. Validation is enabled by default. + * @param file the XML file to parse. + */ + default void parseFile(File file) + { + if (!isValidXmlFile(file)) + { + LOGGER.warning("Cannot parse " + file.getName() + ": file does not exist or is not valid."); + return; + } + + final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setNamespaceAware(true); + factory.setValidating(isValidating()); + factory.setIgnoringComments(true); + try + { + factory.setAttribute(JAXP_SCHEMA_LANGUAGE, W3C_XML_SCHEMA); + final DocumentBuilder builder = factory.newDocumentBuilder(); + parseDocument(builder.parse(file), file); + } + catch (SAXParseException e) + { + LOGGER.log(Level.WARNING, "Error parsing " + file.getName() + " at line " + e.getLineNumber() + ", column " + e.getColumnNumber() + ".", e); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Error parsing " + file.getName(), e); + } + } + + /** + * Parses XML files in the specified directory. This is a helper method for {@link #parseDirectory(File, boolean)}. + * @param directory the path to the directory with XML files. + * @return {@code false} if the directory is not found, {@code true} otherwise. + */ + default boolean parseDirectory(File directory) + { + return parseDirectory(directory, false); + } + + /** + * Parses XML files in a directory within the datapack. This is a helper method for {@link #parseDirectory(File, boolean)}. + * @param path the path to the directory within the datapack. + * @param recursive if {@code true}, parses files in all subdirectories. + * @return {@code false} if the directory is not found, {@code true} otherwise. + */ + default boolean parseDatapackDirectory(String path, boolean recursive) + { + return parseDirectory(new File(".", path), recursive); + } + + /** + * Loads all XML files from the specified directory and parses each file. + * @param directory the directory to scan for XML files. + * @param recursive if {@code true}, parses files in all subdirectories. + * @return {@code false} if the directory is not found, {@code true} otherwise. + */ + default boolean parseDirectory(File directory, boolean recursive) + { + if (!directory.exists()) + { + LOGGER.warning("Directory not found: " + directory.getAbsolutePath()); + return false; + } + + // If multithreading is enabled, use a thread pool to parse files. + if (ThreadConfig.THREADS_FOR_LOADING) + { + final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(Runtime.getRuntime().availableProcessors()); + final List> tasks = new ArrayList<>(); + + final File[] files = directory.listFiles(); + if (files != null) + { + for (File file : files) + { + if (recursive && file.isDirectory()) + { + parseDirectory(file, true); + } + else if (isValidXmlFile(file)) + { + tasks.add(executorService.schedule(() -> parseFile(file), 0, TimeUnit.MILLISECONDS)); + } + } + } + + for (Future task : tasks) + { + try + { + task.get(); + } + catch (Exception e) + { + LOGGER.warning("Failed to parse file: " + e.getMessage()); + } + } + + executorService.shutdown(); + try + { + executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS); + } + catch (InterruptedException e) + { + Thread.currentThread().interrupt(); + LOGGER.warning("Parsing process was interrupted: " + e.getMessage()); + } + } + else // Parse files sequentially if multithreading is not enabled. + { + final File[] files = directory.listFiles(); + if (files != null) + { + for (File file : files) + { + if (recursive && file.isDirectory()) + { + parseDirectory(file, true); + } + else if (isValidXmlFile(file)) + { + parseFile(file); + } + } + } + } + + return true; + } + + /** + * Abstract method for parsing the current document. Called from {@link #parseFile(File)}. + * @param document the document to parse + * @param file the file being processed + */ + void parseDocument(Document document, File file); + + /** + * Parses a boolean value from the given node. + * @param node the XML node to parse + * @param defaultValue the default value to return if the node is null + * @return the parsed boolean value, or the default value if the node is null + */ + default Boolean parseBoolean(Node node, Boolean defaultValue) + { + return node != null ? Boolean.valueOf(node.getNodeValue()) : defaultValue; + } + + /** + * Parses a boolean value from the given node. + * @param node the XML node to parse + * @return the parsed boolean value, or null if the node is null + */ + default Boolean parseBoolean(Node node) + { + return parseBoolean(node, null); + } + + /** + * Parses a boolean value from the specified attribute in the given attributes map. + * @param attributes the attributes map + * @param name the name of the attribute to parse + * @return the parsed boolean value, or null if the attribute is not found + */ + default Boolean parseBoolean(NamedNodeMap attributes, String name) + { + return parseBoolean(attributes.getNamedItem(name)); + } + + /** + * Parses a boolean value from the specified attribute in the given attributes map. + * @param attributes the attributes map + * @param name the name of the attribute to parse + * @param defaultValue the default value to return if the attribute is not found + * @return the parsed boolean value, or the default value if the attribute is not found + */ + default Boolean parseBoolean(NamedNodeMap attributes, String name, Boolean defaultValue) + { + return parseBoolean(attributes.getNamedItem(name), defaultValue); + } + + /** + * Parses a byte value from the given node. + * @param node the XML node to parse + * @param defaultValue the default value to return if the node is null + * @return the parsed byte value, or the default value if the node is null + */ + default Byte parseByte(Node node, Byte defaultValue) + { + return node != null ? Byte.decode(node.getNodeValue()) : defaultValue; + } + + /** + * Parses a byte value from the given node. + * @param node the XML node to parse + * @return the parsed byte value, or null if the node is null + */ + default Byte parseByte(Node node) + { + return parseByte(node, null); + } + + /** + * Parses a byte value from the specified attribute in the given attributes map. + * @param attributes the attributes map + * @param name the name of the attribute to parse + * @return the parsed byte value, or null if the attribute is not found + */ + default Byte parseByte(NamedNodeMap attributes, String name) + { + return parseByte(attributes.getNamedItem(name)); + } + + /** + * Parses a byte value from the specified attribute in the given attributes map. + * @param attributes the attributes map + * @param name the name of the attribute to parse + * @param defaultValue the default value to return if the attribute is not found + * @return the parsed byte value, or the default value if the attribute is not found + */ + default Byte parseByte(NamedNodeMap attributes, String name, Byte defaultValue) + { + return parseByte(attributes.getNamedItem(name), defaultValue); + } + + /** + * Parses a short value from the given node. + * @param node the XML node to parse + * @param defaultValue the default value to return if the node is null + * @return the parsed short value, or the default value if the node is null + */ + default Short parseShort(Node node, Short defaultValue) + { + return node != null ? Short.decode(node.getNodeValue()) : defaultValue; + } + + /** + * Parses a short value from the given node. + * @param node the XML node to parse + * @return the parsed short value, or null if the node is null + */ + default Short parseShort(Node node) + { + return parseShort(node, null); + } + + /** + * Parses a short value from the specified attribute in the given attributes map. + * @param attributes the attributes map + * @param name the name of the attribute to parse + * @return the parsed short value, or null if the attribute is not found + */ + default Short parseShort(NamedNodeMap attributes, String name) + { + return parseShort(attributes.getNamedItem(name)); + } + + /** + * Parses a short value from the specified attribute in the given attributes map. + * @param attributes the attributes map + * @param name the name of the attribute to parse + * @param defaultValue the default value to return if the attribute is not found + * @return the parsed short value, or the default value if the attribute is not found + */ + default Short parseShort(NamedNodeMap attributes, String name, Short defaultValue) + { + return parseShort(attributes.getNamedItem(name), defaultValue); + } + + /** + * Parses an int value from the given node. + * @param node the XML node to parse + * @param defaultValue the default value to return if the node is null + * @return the parsed int value, or the default value if the node is null + */ + default int parseInt(Node node, Integer defaultValue) + { + return node != null ? Integer.decode(node.getNodeValue()) : defaultValue; + } + + /** + * Parses an int value from the given node, using -1 as the default value. + * @param node the XML node to parse + * @return the parsed int value, or -1 if the node is null + */ + default int parseInt(Node node) + { + return parseInt(node, -1); + } + + /** + * Parses an Integer value from the given node. + * @param node the XML node to parse + * @param defaultValue the default value to return if the node is null + * @return the parsed Integer value, or the default value if the node is null + */ + default Integer parseInteger(Node node, Integer defaultValue) + { + return node != null ? Integer.decode(node.getNodeValue()) : defaultValue; + } + + /** + * Parses an Integer value from the given node. + * @param node the XML node to parse + * @return the parsed Integer value, or null if the node is null + */ + default Integer parseInteger(Node node) + { + return parseInteger(node, null); + } + + /** + * Parses an Integer value from the specified attribute in the given attributes map. + * @param attributes the attributes map + * @param name the name of the attribute to parse + * @return the parsed Integer value, or null if the attribute is not found + */ + default Integer parseInteger(NamedNodeMap attributes, String name) + { + return parseInteger(attributes.getNamedItem(name)); + } + + /** + * Parses an Integer value from the specified attribute in the given attributes map. + * @param attributes the attributes map + * @param name the name of the attribute to parse + * @param defaultValue the default value to return if the attribute is not found + * @return the parsed Integer value, or the default value if the attribute is not found + */ + default Integer parseInteger(NamedNodeMap attributes, String name, Integer defaultValue) + { + return parseInteger(attributes.getNamedItem(name), defaultValue); + } + + /** + * Parses a Long value from the given node. + * @param node the XML node to parse + * @param defaultValue the default value to return if the node is null + * @return the parsed Long value, or the default value if the node is null + */ + default Long parseLong(Node node, Long defaultValue) + { + return node != null ? Long.decode(node.getNodeValue()) : defaultValue; + } + + /** + * Parses a Long value from the given node. + * @param node the XML node to parse + * @return the parsed Long value, or null if the node is null + */ + default Long parseLong(Node node) + { + return parseLong(node, null); + } + + /** + * Parses a Long value from the specified attribute in the given attributes map. + * @param attributes the attributes map + * @param name the name of the attribute to parse + * @return the parsed Long value, or null if the attribute is not found + */ + default Long parseLong(NamedNodeMap attributes, String name) + { + return parseLong(attributes.getNamedItem(name)); + } + + /** + * Parses a Long value from the specified attribute in the given attributes map. + * @param attributes the attributes map + * @param name the name of the attribute to parse + * @param defaultValue the default value to return if the attribute is not found + * @return the parsed Long value, or the default value if the attribute is not found + */ + default Long parseLong(NamedNodeMap attributes, String name, Long defaultValue) + { + return parseLong(attributes.getNamedItem(name), defaultValue); + } + + /** + * Parses a float value from the given node. + * @param node the XML node to parse + * @param defaultValue the default value to return if the node is null + * @return the parsed float value, or the default value if the node is null + */ + default Float parseFloat(Node node, Float defaultValue) + { + return node != null ? Float.valueOf(node.getNodeValue()) : defaultValue; + } + + /** + * Parses a float value from the given node. + * @param node the XML node to parse + * @return the parsed float value, or null if the node is null + */ + default Float parseFloat(Node node) + { + return parseFloat(node, null); + } + + /** + * Parses a float value from the specified attribute in the given attributes map. + * @param attributes the attributes map + * @param name the name of the attribute to parse + * @return the parsed float value, or null if the attribute is not found + */ + default Float parseFloat(NamedNodeMap attributes, String name) + { + return parseFloat(attributes.getNamedItem(name)); + } + + /** + * Parses a float value from the specified attribute in the given attributes map. + * @param attributes the attributes map + * @param name the name of the attribute to parse + * @param defaultValue the default value to return if the attribute is not found + * @return the parsed float value, or the default value if the attribute is not found + */ + default Float parseFloat(NamedNodeMap attributes, String name, Float defaultValue) + { + return parseFloat(attributes.getNamedItem(name), defaultValue); + } + + /** + * Parses a double value from the given node. + * @param node the XML node to parse + * @param defaultValue the default value to return if the node is null + * @return the parsed double value, or the default value if the node is null + */ + default Double parseDouble(Node node, Double defaultValue) + { + return node != null ? Double.valueOf(node.getNodeValue()) : defaultValue; + } + + /** + * Parses a double value from the given node. + * @param node the XML node to parse + * @return the parsed double value, or null if the node is null + */ + default Double parseDouble(Node node) + { + return parseDouble(node, null); + } + + /** + * Parses a double value from the specified attribute in the given attributes map. + * @param attributes the attributes map + * @param name the name of the attribute to parse + * @return the parsed double value, or null if the attribute is not found + */ + default Double parseDouble(NamedNodeMap attributes, String name) + { + return parseDouble(attributes.getNamedItem(name)); + } + + /** + * Parses a double value from the specified attribute in the given attributes map. + * @param attributes the attributes map + * @param name the name of the attribute to parse + * @param defaultValue the default value to return if the attribute is not found + * @return the parsed double value, or the default value if the attribute is not found + */ + default Double parseDouble(NamedNodeMap attributes, String name, Double defaultValue) + { + return parseDouble(attributes.getNamedItem(name), defaultValue); + } + + /** + * Parses a String value from the given node. + * @param node the XML node to parse + * @param defaultValue the default value to return if the node is null + * @return the parsed String value, or the default value if the node is null + */ + default String parseString(Node node, String defaultValue) + { + return node != null ? node.getNodeValue() : defaultValue; + } + + /** + * Parses a String value from the given node. + * @param node the XML node to parse + * @return the parsed String value, or null if the node is null + */ + default String parseString(Node node) + { + return parseString(node, null); + } + + /** + * Parses a String value from the specified attribute in the given attributes map. + * @param attributes the attributes map + * @param name the name of the attribute to parse + * @return the parsed String value, or null if the attribute is not found + */ + default String parseString(NamedNodeMap attributes, String name) + { + return parseString(attributes.getNamedItem(name)); + } + + /** + * Parses a String value from the specified attribute in the given attributes map. + * @param attributes the attributes map + * @param name the name of the attribute to parse + * @param defaultValue the default value to return if the attribute is not found + * @return the parsed String value, or the default value if the attribute is not found + */ + default String parseString(NamedNodeMap attributes, String name, String defaultValue) + { + return parseString(attributes.getNamedItem(name), defaultValue); + } + + /** + * Parses an enum value from the given node. + * @param the enum type + * @param node the XML node to parse + * @param enumClass the class of the enum type + * @param defaultValue the default value to return if parsing fails + * @return the parsed enum value, or the default value if parsing fails + */ + default > T parseEnum(Node node, Class enumClass, T defaultValue) + { + if (node == null) + { + return defaultValue; + } + + try + { + return Enum.valueOf(enumClass, node.getNodeValue()); + } + catch (IllegalArgumentException e) + { + LOGGER.warning("Invalid value for node: " + node.getNodeName() + ", specified value: " + node.getNodeValue() + " should be an enum of type \"" + enumClass.getSimpleName() + "\". Using default value: " + defaultValue); + return defaultValue; + } + } + + /** + * Parses an enum value from the given node. + * @param the enum type + * @param node the XML node to parse + * @param enumClass the class of the enum type + * @return the parsed enum value, or null if parsing fails + */ + default > T parseEnum(Node node, Class enumClass) + { + return parseEnum(node, enumClass, null); + } + + /** + * Parses an enum value from the specified attribute in the given attributes map. + * @param the enum type + * @param attributes the attributes map + * @param enumClass the class of the enum type + * @param name the name of the attribute to parse + * @return the parsed enum value, or null if the attribute is not found or parsing fails + */ + default > T parseEnum(NamedNodeMap attributes, Class enumClass, String name) + { + return parseEnum(attributes.getNamedItem(name), enumClass); + } + + /** + * Parses an enum value from the specified attribute in the given attributes map. + * @param the enum type + * @param attributes the attributes map + * @param enumClass the class of the enum type + * @param name the name of the attribute to parse + * @param defaultValue the default value to return if parsing fails + * @return the parsed enum value, or the default value if parsing fails + */ + default > T parseEnum(NamedNodeMap attributes, Class enumClass, String name, T defaultValue) + { + return parseEnum(attributes.getNamedItem(name), enumClass, defaultValue); + } + + /** + * Parses all attributes from the given node into a map. + * @param node the XML node to parse + * @return a map containing all attributes of the node as key-value pairs + */ + default Map parseAttributes(Node node) + { + final NamedNodeMap attributes = node.getAttributes(); + final Map attributeMap = new LinkedHashMap<>(); + for (int i = 0; i < attributes.getLength(); i++) + { + final Node attribute = attributes.item(i); + attributeMap.put(attribute.getNodeName(), attribute.getNodeValue()); + } + + return attributeMap; + } + + /** + * Applies an action to each child node. + * @param node the parent XML node + * @param action the action to perform on each child node + */ + default void forEach(Node node, Consumer action) + { + forEach(node, _ -> true, action); + } + + /** + * Applies an action to each child node with a matching name. + * @param node the parent XML node + * @param nodeName the name of the child nodes to match + * @param action the action to perform on each matching child node + */ + default void forEach(Node node, String nodeName, Consumer action) + { + forEach(node, child -> nodeName.equalsIgnoreCase(child.getNodeName()), action); + } + + /** + * Applies an action to each child node that meets a specified filter condition. + * @param node the parent XML node + * @param filter a filter to select specific child nodes + * @param action the action to perform on each matching child node + */ + default void forEach(Node node, Predicate filter, Consumer action) + { + final NodeList children = node.getChildNodes(); + for (int i = 0; i < children.getLength(); i++) + { + final Node childNode = children.item(i); + if (filter.test(childNode)) + { + action.accept(childNode); + } + } + } + + /** + * Checks if the specified file is a valid XML file. + * @param file the file to check + * @return true if the file is an XML file and exists, false otherwise + */ + default boolean isValidXmlFile(File file) + { + return (file != null) && file.isFile() && file.getName().toLowerCase().endsWith(".xml"); + } + + /** + * Checks if XML validation is enabled. + * @return {@code true} if validation is enabled, {@code false} otherwise. + */ + default boolean isValidating() + { + return true; + } + + /** + * Checks if a node is of element type. + * @param node the XML node to check + * @return {@code true} if the node is an element, {@code false} otherwise + */ + static boolean isNode(Node node) + { + return node.getNodeType() == Node.ELEMENT_NODE; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/util/Rnd.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/util/Rnd.java new file mode 100644 index 0000000..f4a73b5 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/util/Rnd.java @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.commons.util; + +import java.util.concurrent.ThreadLocalRandom; + +/** + * @author Mobius + * @since September 15th 2018 + */ +public class Rnd +{ + private static final int MINIMUM_POSITIVE_INT = 1; + private static final long MINIMUM_POSITIVE_LONG = 1L; + private static final float MINIMUM_POSITIVE_FLOAT = Float.intBitsToFloat(0x1); + private static final double MINIMUM_POSITIVE_DOUBLE = Double.longBitsToDouble(0x1L); + + /** + * @return a random boolean value. + */ + public static boolean nextBoolean() + { + return ThreadLocalRandom.current().nextBoolean(); + } + + /** + * Generates random bytes and places them into a user-supplied byte array. The number of random bytes produced is equal to the length of the byte array. + * @param bytes the byte array to fill with random bytes. + */ + public static void nextBytes(byte[] bytes) + { + ThreadLocalRandom.current().nextBytes(bytes); + } + + /** + * @param bound (int) + * @return a random int value between zero (inclusive) and the specified bound (exclusive). + */ + public static int get(int bound) + { + return bound <= 0 ? 0 : ThreadLocalRandom.current().nextInt(bound); + } + + /** + * @param origin (int) + * @param bound (int) + * @return a random int value between the specified origin (inclusive) and the specified bound (inclusive). + */ + public static int get(int origin, int bound) + { + return origin >= bound ? origin : ThreadLocalRandom.current().nextInt(origin, bound == Integer.MAX_VALUE ? bound : bound + MINIMUM_POSITIVE_INT); + } + + /** + * @return a random int value. + */ + public static int nextInt() + { + return ThreadLocalRandom.current().nextInt(); + } + + /** + * @param bound (long) + * @return a random long value between zero (inclusive) and the specified bound (exclusive). + */ + public static long get(long bound) + { + return bound <= 0 ? 0 : ThreadLocalRandom.current().nextLong(bound); + } + + /** + * @param origin (long) + * @param bound (long) + * @return a random long value between the specified origin (inclusive) and the specified bound (inclusive). + */ + public static long get(long origin, long bound) + { + return origin >= bound ? origin : ThreadLocalRandom.current().nextLong(origin, bound == Long.MAX_VALUE ? bound : bound + MINIMUM_POSITIVE_LONG); + } + + /** + * @return a random long value. + */ + public static long nextLong() + { + return ThreadLocalRandom.current().nextLong(); + } + + /** + * @param bound (float) + * @return a random float value between zero (inclusive) and the specified bound (exclusive). + */ + public static float get(float bound) + { + return bound <= 0 ? 0 : ThreadLocalRandom.current().nextFloat(bound); + } + + /** + * @param origin (float) + * @param bound (float) + * @return a random float value between the specified origin (inclusive) and the specified bound (inclusive). + */ + public static float get(float origin, float bound) + { + return origin >= bound ? origin : ThreadLocalRandom.current().nextFloat(origin, bound == Float.MAX_VALUE ? bound : bound + MINIMUM_POSITIVE_FLOAT); + } + + /** + * @return a random float value between zero (inclusive) and one (exclusive). + */ + public static float nextFloat() + { + return ThreadLocalRandom.current().nextFloat(); + } + + /** + * @param bound (double) + * @return a random double value between zero (inclusive) and the specified bound (exclusive). + */ + public static double get(double bound) + { + return bound <= 0 ? 0 : ThreadLocalRandom.current().nextDouble(bound); + } + + /** + * @param origin (double) + * @param bound (double) + * @return a random double value between the specified origin (inclusive) and the specified bound (inclusive). + */ + public static double get(double origin, double bound) + { + return origin >= bound ? origin : ThreadLocalRandom.current().nextDouble(origin, bound == Double.MAX_VALUE ? bound : bound + MINIMUM_POSITIVE_DOUBLE); + } + + /** + * @return a random double value between zero (inclusive) and one (exclusive). + */ + public static double nextDouble() + { + return ThreadLocalRandom.current().nextDouble(); + } + + /** + * @return the next random, Gaussian ("normally") distributed double value with mean 0.0 and standard deviation 1.0 from this random number generator's sequence. + */ + public static double nextGaussian() + { + return ThreadLocalRandom.current().nextGaussian(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/util/StringUtil.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/util/StringUtil.java new file mode 100644 index 0000000..0fef1f5 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/util/StringUtil.java @@ -0,0 +1,499 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.commons.util; + +import java.util.StringJoiner; +import java.util.StringTokenizer; + +/** + * Utility class for String operations, providing methods to efficiently build and format strings. + * @author Mobius + */ +public class StringUtil +{ + /** + * Appends a string to a given StringBuilder.
+ * This method avoids the unnecessary use of `public static void append(StringBuilder sb, String... args)`,
+ * which could introduce overhead due to varargs processing when only a single argument is appended. + * @param sb the StringBuilder to append to + * @param arg the string to append + */ + public static void append(StringBuilder sb, String arg) + { + sb.append(arg); + } + + /** + * Appends multiple strings to a given StringBuilder. + * @param sb the StringBuilder to append to + * @param args the strings to append + */ + public static void append(StringBuilder sb, String... args) + { + // Directly calculate the required capacity and ensure it in one step. + int totalLength = sb.length(); + for (String arg : args) + { + totalLength += (arg != null ? arg.length() : 4); + } + + sb.ensureCapacity(totalLength); + + // Append each argument. + for (String arg : args) + { + sb.append(arg); + } + } + + /** + * Appends multiple objects to a given StringBuilder. + * @param sb the StringBuilder to append to + * @param args the objects to append + */ + public static void append(StringBuilder sb, Object... args) + { + // Calculate the total length and store converted strings. + int totalLength = sb.length(); + final String[] strings = new String[args.length]; + for (int i = 0; i < args.length; i++) + { + strings[i] = String.valueOf(args[i]); + totalLength += strings[i].length(); + } + + sb.ensureCapacity(totalLength); + + // Append each stored string. + for (String string : strings) + { + sb.append(string); + } + } + + /** + * Concatenates multiple strings into a single string. + * @param args the strings to concatenate + * @return the concatenated string + */ + public static String concat(String... args) + { + // Calculate the total length of all strings. + int totalLength = 0; + for (String arg : args) + { + totalLength += (arg != null ? arg.length() : 4); + } + + // Append each argument. + final StringBuilder sb = new StringBuilder(totalLength); + for (String arg : args) + { + sb.append(arg); + } + + return sb.toString(); + } + + /** + * Concatenates multiple objects into a single string. + * @param args the objects to concatenate + * @return the concatenated string + */ + public static String concat(Object... args) + { + // Calculate the total length and store converted strings. + int totalLength = 0; + final String[] strings = new String[args.length]; + for (int i = 0; i < args.length; i++) + { + strings[i] = String.valueOf(args[i]); + totalLength += strings[i].length(); + } + + // Append each stored string. + final StringBuilder sb = new StringBuilder(totalLength); + for (String string : strings) + { + sb.append(string); + } + + return sb.toString(); + } + + /** + * Concatenates elements in an Iterable into a single string with a specified delimiter. + * @param the type of elements in the iterable + * @param items the iterable collection of elements to join + * @param delimiter the delimiter to place between elements + * @return a single string with each element separated by the delimiter + */ + public static String implode(Iterable items, String delimiter) + { + final StringJoiner joiner = new StringJoiner(delimiter); + for (T item : items) + { + joiner.add(item.toString()); + } + + return joiner.toString(); + } + + /** + * Concatenates elements in an array into a single string with a specified delimiter. + * @param the type of elements in the array + * @param array the array of elements to join + * @param delimiter the delimiter to place between elements + * @return a single string with each element separated by the delimiter + */ + public static String implode(T[] array, String delimiter) + { + final StringJoiner joiner = new StringJoiner(delimiter); + for (T element : array) + { + joiner.add(element.toString()); + } + + return joiner.toString(); + } + + /** + * Capitalizes the first letter of a given string and converts the rest to lowercase. + * @param text the input string to be formatted + * @return the formatted string with the first letter capitalized and the rest in lowercase, or the original string if it is null or empty + */ + public static String capitalizeFirst(String text) + { + // Return the original if it's null or empty. + if ((text == null) || text.isEmpty()) + { + return text; + } + + // Capitalize the first letter and set the remaining letters to lowercase. + return Character.toUpperCase(text.charAt(0)) + text.substring(1).toLowerCase(); + } + + /** + * Splits a camelCase or PascalCase string into words separated by a space. + * @param text the string to split into words + * @return a string with words separated by a space, or the original text if null or empty + */ + public static String separateWords(String text) + { + if ((text == null) || text.isEmpty()) + { + return text; + } + + final StringBuilder result = new StringBuilder(); + final char[] chars = text.toCharArray(); + for (int i = 0; i < chars.length; i++) + { + final char current = chars[i]; + + // Check if the current character is uppercase and it's not the first character. + if (Character.isUpperCase(current) && (i > 0) && Character.isLowerCase(chars[i - 1])) + { + result.append(' '); + } + + result.append(current); + } + + return result.toString(); + } + + /** + * Converts an enum constant's name to a formatted string with proper casing.
+ * For example, an enum constant named "ENUM_CONSTANT_NAME" will be formatted as "Enum Constant Name". + * @param enumeration the enum constant to format + * @return a formatted string with each word capitalized + */ + public static String enumToString(Enum enumeration) + { + final String name = enumeration.name().toLowerCase(); + final StringBuilder sb = new StringBuilder(name.length()); + + boolean capitalizeNext = true; + for (int i = 0; i < name.length(); i++) + { + char c = name.charAt(i); + if (c == '_') + { + sb.append(' '); + capitalizeNext = true; + } + else if (capitalizeNext) + { + sb.append(Character.toUpperCase(c)); + capitalizeNext = false; + } + else + { + sb.append(c); + } + } + + return sb.toString(); + } + + /** + * Parse a string value to the appropriate data type. + * @param value the string value to parse + * @return the parsed value as Boolean, Long, Integer, Float, Double, or String + */ + public static Object parseValue(String value) + { + if (value == null) + { + return null; + } + + final String val = value.trim(); + if (val.equalsIgnoreCase("true")) + { + return Boolean.TRUE; + } + else if (val.equalsIgnoreCase("false")) + { + return Boolean.FALSE; + } + else + { + try + { + // Try Long first (to avoid Integer parsing of larger numbers). + return Long.valueOf(val); + } + catch (NumberFormatException e1) + { + try + { + // Try Integer. + return Integer.valueOf(val); + } + catch (NumberFormatException e2) + { + try + { + // Try Float before Double to get single-precision when possible. + return Float.valueOf(val); + } + catch (NumberFormatException e3) + { + try + { + // Try Double. + return Double.valueOf(val); + } + catch (NumberFormatException e4) + { + return val; + } + } + } + } + } + } + + /** + * Parses a string as an integer, returning a default value if parsing fails. + * @param text the string to parse + * @param defaultValue the value to return if parsing fails + * @return the parsed integer, or the default value if parsing fails + */ + public static int parseInt(String text, int defaultValue) + { + try + { + return Integer.parseInt(text); + } + catch (NumberFormatException e) + { + return defaultValue; + } + } + + /** + * Parses the next token from a StringTokenizer as an integer, returning a default value if parsing fails or if there are no more tokens. + * @param tokenizer the StringTokenizer containing tokens + * @param defaultValue the value to return if parsing fails or if there are no more tokens + * @return the parsed integer, or the default value if parsing fails or if there are no tokens + */ + public static int parseNextInt(StringTokenizer tokenizer, int defaultValue) + { + if (tokenizer.hasMoreTokens()) + { + try + { + final String value = tokenizer.nextToken().trim(); + return Integer.parseInt(value); + } + catch (NumberFormatException e) + { + // Parsing failed, fall back to default. + } + } + + return defaultValue; + } + + /** + * Checks if the given text contains only letters and/or numbers. + * @param text the text to check + * @return {@code true} if {@code text} contains only alphanumeric characters, {@code false} otherwise + */ + public static boolean isAlphaNumeric(String text) + { + if ((text == null) || text.isEmpty()) + { + return false; + } + + for (int i = 0; i < text.length(); i++) + { + if (!Character.isLetterOrDigit(text.charAt(i))) + { + return false; + } + } + + return true; + } + + /** + * Checks if the given text contains only digits. + * @param text the text to check + * @return {@code true} if {@code text} contains only numbers, {@code false} otherwise + */ + public static boolean isNumeric(String text) + { + if ((text == null) || text.isEmpty()) + { + return false; + } + + for (int i = 0; i < text.length(); i++) + { + if (!Character.isDigit(text.charAt(i))) + { + return false; + } + } + + return true; + } + + /** + * Checks if the given text represents a valid integer. + * @param text the text to check + * @return {@code true} if {@code text} is an integer, {@code false} otherwise + */ + public static boolean isInteger(String text) + { + if ((text == null) || text.isEmpty()) + { + return false; + } + + try + { + Integer.parseInt(text); + return true; + } + catch (NumberFormatException e) + { + return false; + } + } + + /** + * Checks if the given text represents a valid float. + * @param text the text to check + * @return {@code true} if {@code text} is a float, {@code false} otherwise + */ + public static boolean isFloat(String text) + { + if ((text == null) || text.isEmpty()) + { + return false; + } + + try + { + Float.parseFloat(text); + return true; + } + catch (NumberFormatException e) + { + return false; + } + } + + /** + * Checks if the given text represents a valid double. + * @param text the text to check + * @return {@code true} if {@code text} is a double, {@code false} otherwise + */ + public static boolean isDouble(String text) + { + if ((text == null) || text.isEmpty()) + { + return false; + } + + try + { + Double.parseDouble(text); + return true; + } + catch (NumberFormatException e) + { + return false; + } + } + + /** + * Checks if the given text matches any constant in the specified enum type. + * @param name the text to check + * @param enumType the class of the enum + * @param the type of the enum + * @return {@code true} if {@code text} is a valid enum constant, {@code false} otherwise + */ + public static > boolean isEnum(String name, Class enumType) + { + if ((name == null) || name.isEmpty()) + { + return false; + } + + try + { + Enum.valueOf(enumType, name); + return true; + } + catch (IllegalArgumentException e) + { + return false; + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/util/TraceUtil.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/util/TraceUtil.java new file mode 100644 index 0000000..e6a7654 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/commons/util/TraceUtil.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.commons.util; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.StringJoiner; + +/** + * Utility class for handling and formatting stack traces. + * @author Mobius + */ +public class TraceUtil +{ + /** + * Returns the stack trace of a throwable as a String. + * @param throwable the throwable whose stack trace is needed + * @return the stack trace as a String + */ + public static String getStackTrace(Throwable throwable) + { + final StringWriter writer = new StringWriter(); + throwable.printStackTrace(new PrintWriter(writer)); + return writer.toString(); + } + + /** + * Constructs a string from an array of stack trace elements, each element on a new line. + * @param stackTraceElements the array of stack trace elements + * @return a String containing the stack trace elements, each on a new line + */ + public static String getTraceString(StackTraceElement[] stackTraceElements) + { + final StringJoiner joiner = new StringJoiner(System.lineSeparator()); + for (StackTraceElement stackTraceElement : stackTraceElements) + { + joiner.add(stackTraceElement.toString()); + } + + return joiner.toString(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/GameServer.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/GameServer.java new file mode 100644 index 0000000..ea741b0 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/GameServer.java @@ -0,0 +1,493 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver; + +import java.awt.Toolkit; +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; +import java.net.InetSocketAddress; +import java.time.Duration; +import java.util.logging.Level; +import java.util.logging.LogManager; +import java.util.logging.Logger; + +import org.l2jmobius.commons.config.InterfaceConfig; +import org.l2jmobius.commons.database.DatabaseFactory; +import org.l2jmobius.commons.network.ConnectionManager; +import org.l2jmobius.commons.threads.ThreadPool; +import org.l2jmobius.commons.time.TimeUtil; +import org.l2jmobius.commons.util.StringUtil; +import org.l2jmobius.gameserver.cache.HtmCache; +import org.l2jmobius.gameserver.config.ConfigLoader; +import org.l2jmobius.gameserver.config.DevelopmentConfig; +import org.l2jmobius.gameserver.config.GeneralConfig; +import org.l2jmobius.gameserver.config.ServerConfig; +import org.l2jmobius.gameserver.config.custom.CustomMailManagerConfig; +import org.l2jmobius.gameserver.config.custom.MultilingualSupportConfig; +import org.l2jmobius.gameserver.config.custom.OfflinePlayConfig; +import org.l2jmobius.gameserver.config.custom.OfflineTradeConfig; +import org.l2jmobius.gameserver.config.custom.PremiumSystemConfig; +import org.l2jmobius.gameserver.config.custom.SellBuffsConfig; +import org.l2jmobius.gameserver.config.custom.WeddingConfig; +import org.l2jmobius.gameserver.data.AugmentationData; +import org.l2jmobius.gameserver.data.MerchantPriceConfigTable; +import org.l2jmobius.gameserver.data.SchemeBufferTable; +import org.l2jmobius.gameserver.data.sql.AnnouncementsTable; +import org.l2jmobius.gameserver.data.sql.CharInfoTable; +import org.l2jmobius.gameserver.data.sql.CharSummonTable; +import org.l2jmobius.gameserver.data.sql.ClanHallTable; +import org.l2jmobius.gameserver.data.sql.ClanTable; +import org.l2jmobius.gameserver.data.sql.CrestTable; +import org.l2jmobius.gameserver.data.sql.OfflinePlayTable; +import org.l2jmobius.gameserver.data.sql.OfflineTraderTable; +import org.l2jmobius.gameserver.data.xml.AdminData; +import org.l2jmobius.gameserver.data.xml.ArmorSetData; +import org.l2jmobius.gameserver.data.xml.BuyListData; +import org.l2jmobius.gameserver.data.xml.CategoryData; +import org.l2jmobius.gameserver.data.xml.ClassListData; +import org.l2jmobius.gameserver.data.xml.CubicData; +import org.l2jmobius.gameserver.data.xml.DoorData; +import org.l2jmobius.gameserver.data.xml.DynamicExpRateData; +import org.l2jmobius.gameserver.data.xml.EnchantItemData; +import org.l2jmobius.gameserver.data.xml.EnchantItemGroupsData; +import org.l2jmobius.gameserver.data.xml.EnchantItemHPBonusData; +import org.l2jmobius.gameserver.data.xml.EnchantSkillTreeData; +import org.l2jmobius.gameserver.data.xml.ExperienceData; +import org.l2jmobius.gameserver.data.xml.ExperienceLossData; +import org.l2jmobius.gameserver.data.xml.FenceData; +import org.l2jmobius.gameserver.data.xml.FishData; +import org.l2jmobius.gameserver.data.xml.FishingMonstersData; +import org.l2jmobius.gameserver.data.xml.FishingRodsData; +import org.l2jmobius.gameserver.data.xml.HennaData; +import org.l2jmobius.gameserver.data.xml.HitConditionBonusData; +import org.l2jmobius.gameserver.data.xml.InitialEquipmentData; +import org.l2jmobius.gameserver.data.xml.InitialShortcutData; +import org.l2jmobius.gameserver.data.xml.ItemData; +import org.l2jmobius.gameserver.data.xml.KarmaLossData; +import org.l2jmobius.gameserver.data.xml.LevelUpCrystalData; +import org.l2jmobius.gameserver.data.xml.MapRegionData; +import org.l2jmobius.gameserver.data.xml.MultisellData; +import org.l2jmobius.gameserver.data.xml.NpcData; +import org.l2jmobius.gameserver.data.xml.NpcNameLocalisationData; +import org.l2jmobius.gameserver.data.xml.OptionData; +import org.l2jmobius.gameserver.data.xml.PetDataTable; +import org.l2jmobius.gameserver.data.xml.PetSkillData; +import org.l2jmobius.gameserver.data.xml.PlayerTemplateData; +import org.l2jmobius.gameserver.data.xml.RecipeData; +import org.l2jmobius.gameserver.data.xml.SendMessageLocalisationData; +import org.l2jmobius.gameserver.data.xml.SiegeScheduleData; +import org.l2jmobius.gameserver.data.xml.SkillData; +import org.l2jmobius.gameserver.data.xml.SkillLearnData; +import org.l2jmobius.gameserver.data.xml.SkillTreeData; +import org.l2jmobius.gameserver.data.xml.SpawnData; +import org.l2jmobius.gameserver.data.xml.StaticObjectData; +import org.l2jmobius.gameserver.data.xml.TeleporterData; +import org.l2jmobius.gameserver.entity.World; +import org.l2jmobius.gameserver.entity.groups.matching.PartyMatchRoomList; +import org.l2jmobius.gameserver.entity.groups.matching.PartyMatchWaitingList; +import org.l2jmobius.gameserver.entity.spawns.AutoSpawnHandler; +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.handler.EffectHandler; +import org.l2jmobius.gameserver.managers.AntiFeedManager; +import org.l2jmobius.gameserver.managers.BoatManager; +import org.l2jmobius.gameserver.managers.CHSiegeManager; +import org.l2jmobius.gameserver.managers.CaptchaManager; +import org.l2jmobius.gameserver.managers.CastleManager; +import org.l2jmobius.gameserver.managers.CastleManorManager; +import org.l2jmobius.gameserver.managers.ClanHallAuctionManager; +import org.l2jmobius.gameserver.managers.CoupleManager; +import org.l2jmobius.gameserver.managers.CursedWeaponsManager; +import org.l2jmobius.gameserver.managers.CustomMailManager; +import org.l2jmobius.gameserver.managers.DailyResetManager; +import org.l2jmobius.gameserver.managers.DayNightSpawnManager; +import org.l2jmobius.gameserver.managers.DimensionalRiftManager; +import org.l2jmobius.gameserver.managers.EventDropManager; +import org.l2jmobius.gameserver.managers.FakePlayerChatManager; +import org.l2jmobius.gameserver.managers.FishingChampionshipManager; +import org.l2jmobius.gameserver.managers.GlobalVariablesManager; +import org.l2jmobius.gameserver.managers.GrandBossManager; +import org.l2jmobius.gameserver.managers.IdManager; +import org.l2jmobius.gameserver.managers.InstanceManager; +import org.l2jmobius.gameserver.managers.ItemsOnGroundManager; +import org.l2jmobius.gameserver.managers.MercTicketManager; +import org.l2jmobius.gameserver.managers.PcCafePointsManager; +import org.l2jmobius.gameserver.managers.PetitionManager; +import org.l2jmobius.gameserver.managers.PrecautionaryRestartManager; +import org.l2jmobius.gameserver.managers.PremiumManager; +import org.l2jmobius.gameserver.managers.PunishmentManager; +import org.l2jmobius.gameserver.managers.RaidBossPointsManager; +import org.l2jmobius.gameserver.managers.RaidBossSpawnManager; +import org.l2jmobius.gameserver.managers.ScriptManager; +import org.l2jmobius.gameserver.managers.SellBuffsManager; +import org.l2jmobius.gameserver.managers.ServerRestartManager; +import org.l2jmobius.gameserver.managers.SiegeManager; +import org.l2jmobius.gameserver.managers.WalkingManager; +import org.l2jmobius.gameserver.managers.ZoneManager; +import org.l2jmobius.gameserver.managers.games.LotteryManager; +import org.l2jmobius.gameserver.managers.games.MonsterRaceManager; +import org.l2jmobius.gameserver.mechanics.events.EventDispatcher; +import org.l2jmobius.gameserver.mechanics.events.EventType; +import org.l2jmobius.gameserver.mechanics.events.holders.OnServerStart; +import org.l2jmobius.gameserver.mechanics.olympiad.Hero; +import org.l2jmobius.gameserver.mechanics.olympiad.Olympiad; +import org.l2jmobius.gameserver.mechanics.sevensigns.SevenSigns; +import org.l2jmobius.gameserver.mechanics.sevensigns.SevenSignsFestival; +import org.l2jmobius.gameserver.network.GameClient; +import org.l2jmobius.gameserver.network.GamePacketHandler; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.scripting.ScriptEngine; +import org.l2jmobius.gameserver.taskmanagers.GameTimeTaskManager; +import org.l2jmobius.gameserver.taskmanagers.ItemLifeTimeTaskManager; +import org.l2jmobius.gameserver.taskmanagers.ItemsAutoDestroyTaskManager; +import org.l2jmobius.gameserver.ui.Gui; +import org.l2jmobius.gameserver.util.DeadlockWatcher; + +public class GameServer +{ + private static final Logger LOGGER = Logger.getLogger(GameServer.class.getName()); + + private static final long START_TIME = System.currentTimeMillis(); + private long _sectionStartTime = START_TIME; + private String _previousSectionName = null; + + public GameServer() throws Exception + { + // GUI + InterfaceConfig.load(); + if (InterfaceConfig.ENABLE_GUI) + { + System.out.println("GameServer: Running in GUI mode."); + new Gui(); + } + + // Create log folder + final File logFolder = new File(".", "log"); + logFolder.mkdir(); + + // Create input stream for log file -- or store file data into memory. + try (InputStream is = new FileInputStream(new File("./log.cfg"))) + { + LogManager.getLogManager().readConfiguration(is); + } + + // Initialize config + ConfigLoader.init(); + + printSection("Database"); + DatabaseFactory.init(); + + printSection("ThreadPool"); + ThreadPool.init(); + + // Start game time task manager early. + GameTimeTaskManager.getInstance(); + + printSection("IdManager"); + IdManager.getInstance(); + + printSection("Scripting Engine"); + EventDispatcher.getInstance(); + ScriptEngine.getInstance(); + + printSection("World"); + InstanceManager.getInstance(); + World.init(); + MapRegionData.getInstance(); + AnnouncementsTable.getInstance(); + GlobalVariablesManager.getInstance(); + + printSection("Data"); + CategoryData.getInstance(); + CubicData.getInstance(); + DynamicExpRateData.getInstance(); + + printSection("Skills"); + EffectHandler.getInstance().executeScript(); + EnchantSkillTreeData.getInstance(); + SkillTreeData.getInstance(); + SkillData.getInstance(); + PetSkillData.getInstance(); + + printSection("Items"); + ItemData.getInstance(); + EnchantItemGroupsData.getInstance(); + EnchantItemData.getInstance(); + OptionData.getInstance(); + EnchantItemHPBonusData.getInstance(); + MerchantPriceConfigTable.getInstance().loadInstances(); + BuyListData.getInstance(); + MultisellData.getInstance(); + RecipeData.getInstance(); + ArmorSetData.getInstance(); + FishData.getInstance(); + FishingMonstersData.getInstance(); + FishingRodsData.getInstance(); + HennaData.getInstance(); + PcCafePointsManager.getInstance(); + ItemLifeTimeTaskManager.getInstance(); + + printSection("Characters"); + ClassListData.getInstance(); + InitialEquipmentData.getInstance(); + InitialShortcutData.getInstance(); + ExperienceData.getInstance(); + ExperienceLossData.getInstance(); + KarmaLossData.getInstance(); + HitConditionBonusData.getInstance(); + PlayerTemplateData.getInstance(); + CharInfoTable.getInstance(); + AdminData.getInstance(); + RaidBossPointsManager.getInstance(); + PetDataTable.getInstance(); + CharSummonTable.getInstance().init(); + CaptchaManager.getInstance(); + + if (PremiumSystemConfig.PREMIUM_SYSTEM_ENABLED) + { + LOGGER.info("PremiumManager: Premium system is enabled."); + PremiumManager.getInstance(); + } + + printSection("Clans"); + ClanTable.getInstance(); + CHSiegeManager.getInstance(); + ClanHallTable.getInstance(); + ClanHallAuctionManager.getInstance(); + + printSection("Geodata"); + GeoEngine.getInstance(); + + printSection("NPCs"); + DoorData.getInstance(); + FenceData.getInstance(); + SkillLearnData.getInstance(); + NpcData.getInstance(); + LevelUpCrystalData.getInstance(); + FakePlayerChatManager.getInstance(); + WalkingManager.getInstance(); + StaticObjectData.getInstance(); + CastleManager.getInstance().loadInstances(); + SchemeBufferTable.getInstance(); + ZoneManager.getInstance(); + GrandBossManager.getInstance().initZones(); + EventDropManager.getInstance(); + + printSection("Olympiad"); + Olympiad.getInstance(); + Hero.getInstance(); + + printSection("Seven Signs"); + SevenSigns.getInstance(); + + // Call to load caches. + printSection("Cache"); + HtmCache.getInstance(); + CrestTable.getInstance(); + TeleporterData.getInstance(); + PartyMatchWaitingList.getInstance(); + PartyMatchRoomList.getInstance(); + PetitionManager.getInstance(); + AugmentationData.getInstance(); + CursedWeaponsManager.getInstance(); + + if (SellBuffsConfig.SELLBUFF_ENABLED) + { + SellBuffsManager.getInstance(); + } + + if (MultilingualSupportConfig.MULTILANG_ENABLE) + { + SystemMessageId.loadLocalisations(); + SendMessageLocalisationData.getInstance(); + NpcNameLocalisationData.getInstance(); + } + + printSection("Scripts"); + ScriptManager.getInstance(); + BoatManager.getInstance(); + + try + { + LOGGER.info("Loading server scripts..."); + ScriptEngine.getInstance().executeScript(ScriptEngine.MASTER_HANDLER_FILE); + ScriptEngine.getInstance().executeScriptList(); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Failed to execute script list!", e); + } + + SpawnData.getInstance(); + DayNightSpawnManager.getInstance().trim().notifyChangeMode(); + DimensionalRiftManager.getInstance(); + RaidBossSpawnManager.getInstance(); + + printSection("Siege"); + SiegeManager.getInstance().getSieges(); + CastleManager.getInstance().activateInstances(); + SiegeScheduleData.getInstance(); + MerchantPriceConfigTable.getInstance().updateReferences(); + CastleManorManager.getInstance(); + MercTicketManager.getInstance(); + ScriptManager.getInstance().report(); + + if (GeneralConfig.SAVE_DROPPED_ITEM) + { + ItemsOnGroundManager.getInstance(); + } + + if ((GeneralConfig.AUTODESTROY_ITEM_AFTER > 0) || (GeneralConfig.HERB_AUTO_DESTROY_TIME > 0)) + { + ItemsAutoDestroyTaskManager.getInstance(); + } + + MonsterRaceManager.getInstance(); + LotteryManager.getInstance(); + SevenSigns.getInstance().spawnSevenSignsNPC(); + SevenSignsFestival.getInstance(); + AutoSpawnHandler.getInstance(); + LOGGER.info("AutoSpawnHandler: Loaded " + AutoSpawnHandler.getInstance().size() + " handlers in total."); + + if (WeddingConfig.ALLOW_WEDDING) + { + CoupleManager.getInstance(); + } + + if (GeneralConfig.ALT_FISH_CHAMPIONSHIP_ENABLED) + { + FishingChampionshipManager.getInstance(); + } + + DailyResetManager.getInstance(); + AntiFeedManager.getInstance().registerEvent(AntiFeedManager.GAME_ID); + + if (OfflinePlayConfig.ENABLE_OFFLINE_PLAY_COMMAND) + { + AntiFeedManager.getInstance().registerEvent(AntiFeedManager.OFFLINE_PLAY); + } + + if (CustomMailManagerConfig.CUSTOM_MAIL_MANAGER_ENABLED) + { + CustomMailManager.getInstance(); + } + + if (EventDispatcher.getInstance().hasListener(EventType.ON_SERVER_START)) + { + EventDispatcher.getInstance().notifyEventAsync(new OnServerStart()); + } + + PunishmentManager.getInstance(); + + Runtime.getRuntime().addShutdownHook(Shutdown.getInstance()); + LOGGER.info("IdManager: Free ObjectID's remaining: " + IdManager.getInstance().getAvailableIdCount()); + + if ((OfflineTradeConfig.OFFLINE_TRADE_ENABLE || OfflineTradeConfig.OFFLINE_CRAFT_ENABLE) && OfflineTradeConfig.RESTORE_OFFLINERS) + { + OfflineTraderTable.getInstance().restoreOfflineTraders(); + } + + if (OfflinePlayConfig.ENABLE_OFFLINE_PLAY_COMMAND && OfflinePlayConfig.RESTORE_AUTO_PLAY_OFFLINERS) + { + OfflinePlayTable.getInstance().restoreOfflinePlayers(); + } + + if (ServerConfig.SERVER_RESTART_SCHEDULE_ENABLED) + { + ServerRestartManager.getInstance(); + } + + if (ServerConfig.PRECAUTIONARY_RESTART_ENABLED) + { + PrecautionaryRestartManager.getInstance(); + } + + if (ServerConfig.DEADLOCK_WATCHER) + { + final DeadlockWatcher deadlockWatcher = new DeadlockWatcher(Duration.ofSeconds(ServerConfig.DEADLOCK_CHECK_INTERVAL), () -> + { + if (ServerConfig.RESTART_ON_DEADLOCK) + { + World.broadcastToAllOnlinePlayers("Server has stability issues - restarting now."); + Shutdown.getInstance().startShutdown(null, 60, true); + } + }); + deadlockWatcher.setDaemon(true); + deadlockWatcher.start(); + } + + System.gc(); + final long totalMem = Runtime.getRuntime().maxMemory() / 1048576; + LOGGER.info(StringUtil.concat(getClass().getSimpleName(), ": Started, using ", getUsedMemoryMB(), " of ", totalMem, " MB total memory.")); + LOGGER.info(StringUtil.concat(getClass().getSimpleName(), ": Maximum number of connected players is ", ServerConfig.MAXIMUM_ONLINE_USERS, ".")); + LOGGER.info(StringUtil.concat(getClass().getSimpleName(), ": Server loaded in ", ((System.currentTimeMillis() - START_TIME) / 1000), " seconds.")); + + new ConnectionManager<>(new InetSocketAddress(ServerConfig.PORT_GAME), GameClient::new, new GamePacketHandler()); + + LoginServerThread.getInstance().start(); + + Toolkit.getDefaultToolkit().beep(); + } + + private void printSection(String section) + { + if (DevelopmentConfig.LOG_SERVER_LOAD_TIMES) + { + // Calculate elapsed time for previous section. + final long currentTime = System.currentTimeMillis(); + final long sectionElapsed = currentTime - _sectionStartTime; + + // Log elapsed time for previous section if not the first section. + if (_previousSectionName != null) + { + LOGGER.info(StringUtil.concat("...section [ ", _previousSectionName, " ] loaded in ", TimeUtil.formatDuration(sectionElapsed), ".")); + } + + // Update for next measurement. + _previousSectionName = section; + _sectionStartTime = currentTime; + } + + // Build and log the new section header. + final StringBuilder sb = new StringBuilder(61); + sb.append("=[ ").append(section).append(" ]"); + while (sb.length() < 61) + { + sb.insert(0, '-'); + } + LOGGER.info(sb.toString()); + } + + public long getUsedMemoryMB() + { + return (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) / 1048576; + } + + public static long getStartTime() + { + return START_TIME; + } + + public static void main(String[] args) throws Exception + { + new GameServer(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/LoginServerThread.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/LoginServerThread.java new file mode 100644 index 0000000..7395b94 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/LoginServerThread.java @@ -0,0 +1,828 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver; + +import java.io.BufferedOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.math.BigInteger; +import java.net.Socket; +import java.net.SocketException; +import java.net.UnknownHostException; +import java.security.GeneralSecurityException; +import java.security.KeyFactory; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.RSAKeyGenParameterSpec; +import java.security.spec.RSAPublicKeySpec; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.commons.crypt.NewCrypt; +import org.l2jmobius.commons.database.DatabaseFactory; +import org.l2jmobius.commons.network.packet.SimpleWritablePacket; +import org.l2jmobius.commons.util.HexUtil; +import org.l2jmobius.commons.util.TraceUtil; +import org.l2jmobius.gameserver.config.GeneralConfig; +import org.l2jmobius.gameserver.config.ServerConfig; +import org.l2jmobius.gameserver.entity.World; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.network.ConnectionState; +import org.l2jmobius.gameserver.network.Disconnection; +import org.l2jmobius.gameserver.network.GameClient; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.loginserverpackets.receive.AuthResponse; +import org.l2jmobius.gameserver.network.loginserverpackets.receive.ChangePasswordResponse; +import org.l2jmobius.gameserver.network.loginserverpackets.receive.InitLS; +import org.l2jmobius.gameserver.network.loginserverpackets.receive.KickPlayer; +import org.l2jmobius.gameserver.network.loginserverpackets.receive.LoginServerFail; +import org.l2jmobius.gameserver.network.loginserverpackets.receive.PlayerAuthResponse; +import org.l2jmobius.gameserver.network.loginserverpackets.receive.RequestCharacters; +import org.l2jmobius.gameserver.network.loginserverpackets.send.AuthRequest; +import org.l2jmobius.gameserver.network.loginserverpackets.send.BlowFishKey; +import org.l2jmobius.gameserver.network.loginserverpackets.send.ChangeAccessLevel; +import org.l2jmobius.gameserver.network.loginserverpackets.send.ChangePassword; +import org.l2jmobius.gameserver.network.loginserverpackets.send.PlayerAuthRequest; +import org.l2jmobius.gameserver.network.loginserverpackets.send.PlayerInGame; +import org.l2jmobius.gameserver.network.loginserverpackets.send.PlayerLogout; +import org.l2jmobius.gameserver.network.loginserverpackets.send.PlayerTracert; +import org.l2jmobius.gameserver.network.loginserverpackets.send.ReplyCharacters; +import org.l2jmobius.gameserver.network.loginserverpackets.send.SendMail; +import org.l2jmobius.gameserver.network.loginserverpackets.send.ServerStatus; +import org.l2jmobius.gameserver.network.loginserverpackets.send.TempBan; +import org.l2jmobius.gameserver.network.serverpackets.CharSelectionInfo; +import org.l2jmobius.gameserver.network.serverpackets.LoginFail; +import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; + +/** + * Handles communication between the game server and login server.
+ * Manages player authentication, server status updates and various administrative functions. + */ +public class LoginServerThread extends Thread +{ + protected static final Logger LOGGER = Logger.getLogger(LoginServerThread.class.getName()); + protected static final Logger ACCOUNTING_LOGGER = Logger.getLogger("accounting"); + + // Protocol constants. + private static final int REVISION = 0x0106; // @see org.l2jmobius.loginserver.LoginServer#PROTOCOL_REV + private static final int RECONNECT_DELAY = 5000; // 5 seconds. + private static final int BLOWFISH_KEY_SIZE = 40; + private static final int HEX_ID_SIZE = 16; + private static final int PACKET_PADDING = 8; + + // Connection configuration. + private final String _hostname; + private final int _port; + private final int _gamePort; + private final boolean _acceptAlternate; + private final boolean _reserveHost; + private final List _subnets; + private final List _hosts; + + // Connection state. + private Socket _socket; + private OutputStream _out; + private NewCrypt _blowfish; + private byte[] _hexID; + private int _requestID; + + // Server state. + private int _maxPlayer; + private final Set _waitingClients = ConcurrentHashMap.newKeySet(); + private final Map _accountsInGameServer = new ConcurrentHashMap<>(); + private int _status; + private String _serverName; + + protected LoginServerThread() + { + super("LoginServerThread"); + + // Initialize connection settings. + _port = ServerConfig.GAME_SERVER_LOGIN_PORT; + _gamePort = ServerConfig.PORT_GAME; + _hostname = ServerConfig.GAME_SERVER_LOGIN_HOST; + _acceptAlternate = ServerConfig.ACCEPT_ALTERNATE_ID; + _reserveHost = ServerConfig.RESERVE_HOST_ON_LOGIN; + _subnets = ServerConfig.GAME_SERVER_SUBNETS; + _hosts = ServerConfig.GAME_SERVER_HOSTS; + _maxPlayer = ServerConfig.MAXIMUM_ONLINE_USERS; + + // Initialize server identification. + _hexID = ServerConfig.HEX_ID; + if (_hexID == null) + { + _requestID = ServerConfig.REQUEST_ID; + _hexID = HexUtil.generateHexBytes(HEX_ID_SIZE); + } + else + { + _requestID = ServerConfig.SERVER_ID; + } + } + + @Override + public void run() + { + while (!isInterrupted()) + { + int lengthHi = 0; + int lengthLo = 0; + int length = 0; + boolean checksumOk = false; + + try + { + // Establish connection to login server. + LOGGER.info(getClass().getSimpleName() + ": Connecting to login on " + _hostname + ":" + _port); + _socket = new Socket(_hostname, _port); + final InputStream in = _socket.getInputStream(); + _out = new BufferedOutputStream(_socket.getOutputStream()); + + // Initialize Blowfish encryption with default key. + final byte[] blowfishKey = HexUtil.generateHexBytes(BLOWFISH_KEY_SIZE); + _blowfish = new NewCrypt("_;v.]05-31!|+-%xT!^[$\00"); + + while (!isInterrupted()) + { + // Read packet length. + lengthLo = in.read(); + lengthHi = in.read(); + length = (lengthHi * 256) + lengthLo; + if (lengthHi < 0) + { + LOGGER.finer(getClass().getSimpleName() + ": Login terminated the connection."); + break; + } + + // Read packet data. + final byte[] incoming = new byte[length - 2]; + int receivedBytes = 0; + int newBytes = 0; + int left = length - 2; + while ((newBytes != -1) && (receivedBytes < (length - 2))) + { + newBytes = in.read(incoming, receivedBytes, left); + receivedBytes += newBytes; + left -= newBytes; + } + + if (receivedBytes != (length - 2)) + { + LOGGER.warning(getClass().getSimpleName() + ": Incomplete packet received, closing connection (LS)"); + break; + } + + // Decrypt and verify packet. + _blowfish.decrypt(incoming, 0, incoming.length); + checksumOk = NewCrypt.verifyChecksum(incoming); + if (!checksumOk) + { + LOGGER.warning(getClass().getSimpleName() + ": Incorrect packet checksum, ignoring packet (LS)"); + break; + } + + // Process packet based on type. + final int packetType = incoming[0] & 0xff; + switch (packetType) + { + case 0x00: // InitLS - Initialize login server communication. + { + final InitLS init = new InitLS(incoming); + if (init.getRevision() != REVISION) + { + LOGGER.warning("/!\\ Revision mismatch between LS and GS /!\\"); + break; + } + + // Generate RSA public key from login server data. + RSAPublicKey publicKey; + try + { + final KeyFactory kfac = KeyFactory.getInstance("RSA"); + final BigInteger modulus = new BigInteger(init.getRSAKey()); + final RSAPublicKeySpec kspec1 = new RSAPublicKeySpec(modulus, RSAKeyGenParameterSpec.F4); + publicKey = (RSAPublicKey) kfac.generatePublic(kspec1); + } + catch (GeneralSecurityException e) + { + LOGGER.warning(getClass().getSimpleName() + ": Trouble initializing RSA public key from login server."); + break; + } + + // Send encrypted blowfish key and switch to secure communication. + sendPacket(new BlowFishKey(blowfishKey, publicKey)); + _blowfish = new NewCrypt(blowfishKey); + sendPacket(new AuthRequest(_requestID, _acceptAlternate, _hexID, _gamePort, _reserveHost, _maxPlayer, _subnets, _hosts)); + break; + } + case 0x01: // LoginServerFail - Registration failure. + { + final LoginServerFail lsf = new LoginServerFail(incoming); + LOGGER.info(getClass().getSimpleName() + ": Registration failed: " + lsf.getReasonString()); + break; + } + case 0x02: // AuthResponse - Server registration successful. + { + final AuthResponse aresp = new AuthResponse(incoming); + final int serverID = aresp.getServerId(); + _serverName = aresp.getServerName(); + ServerConfig.saveHexid(serverID, hexToString(_hexID)); + LOGGER.info(getClass().getSimpleName() + ": Registered on login as Server " + serverID + ": " + _serverName); + + // Configure and send server status. + final ServerStatus st = new ServerStatus(); + if (ServerConfig.SERVER_LIST_BRACKET) + { + st.addAttribute(ServerStatus.SERVER_LIST_SQUARE_BRACKET, ServerStatus.ON); + } + else + { + st.addAttribute(ServerStatus.SERVER_LIST_SQUARE_BRACKET, ServerStatus.OFF); + } + + st.addAttribute(ServerStatus.SERVER_TYPE, ServerConfig.SERVER_LIST_TYPE); + if (GeneralConfig.SERVER_GMONLY) + { + st.addAttribute(ServerStatus.SERVER_LIST_STATUS, ServerStatus.STATUS_GM_ONLY); + } + else + { + st.addAttribute(ServerStatus.SERVER_LIST_STATUS, ServerStatus.STATUS_AUTO); + } + + if (ServerConfig.SERVER_LIST_AGE == 15) + { + st.addAttribute(ServerStatus.SERVER_AGE, ServerStatus.SERVER_AGE_15); + } + else if (ServerConfig.SERVER_LIST_AGE == 18) + { + st.addAttribute(ServerStatus.SERVER_AGE, ServerStatus.SERVER_AGE_18); + } + else + { + st.addAttribute(ServerStatus.SERVER_AGE, ServerStatus.SERVER_AGE_ALL); + } + + sendPacket(st); + + // Send list of currently online players. + final List playerList = new ArrayList<>(); + for (Player player : World.getPlayers()) + { + if (!player.isInOfflineMode()) + { + playerList.add(player.getAccountName()); + } + } + + if (!playerList.isEmpty()) + { + sendPacket(new PlayerInGame(playerList)); + } + break; + } + case 0x03: // PlayerAuthResponse - Player authentication result. + { + final PlayerAuthResponse par = new PlayerAuthResponse(incoming); + final String account = par.getAccount(); + WaitingClient wcToRemove = null; + + // Find the waiting client for this account. + synchronized (_waitingClients) + { + for (WaitingClient wc : _waitingClients) + { + if (wc.account.equals(account)) + { + wcToRemove = wc; + break; + } + } + } + + if (wcToRemove != null) + { + if (par.isAuthed()) + { + // Authentication successful - setup client. + final PlayerInGame pig = new PlayerInGame(par.getAccount()); + sendPacket(pig); + wcToRemove.gameClient.setConnectionState(ConnectionState.AUTHENTICATED); + wcToRemove.gameClient.setSessionId(wcToRemove.sessionKey); + final CharSelectionInfo cl = new CharSelectionInfo(wcToRemove.account, wcToRemove.gameClient.getSessionId().playOkID1); + wcToRemove.gameClient.sendPacket(cl); + wcToRemove.gameClient.setCharSelection(cl.getCharInfo()); + } + else + { + // Authentication failed - close connection. + LOGGER.warning(getClass().getSimpleName() + ": Session key incorrect. Closing connection for account " + wcToRemove.account); + wcToRemove.gameClient.close(new LoginFail(LoginFail.SYSTEM_ERROR_LOGIN_LATER)); + sendLogout(wcToRemove.account); + } + + _waitingClients.remove(wcToRemove); + } + break; + } + case 0x04: // KickPlayer - Force disconnect player. + { + final KickPlayer kp = new KickPlayer(incoming); + doKickPlayer(kp.getAccount()); + break; + } + case 0x05: // RequestCharacters - Get character info for account. + { + final RequestCharacters rc = new RequestCharacters(incoming); + getCharsOnServer(rc.getAccount()); + break; + } + case 0x06: // ChangePasswordResponse - Password change result. + { + new ChangePasswordResponse(incoming); + break; + } + } + } + } + catch (UnknownHostException e) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Unknown host: ", e); + } + catch (SocketException e) + { + LOGGER.warning(getClass().getSimpleName() + ": LoginServer not available, trying to reconnect..."); + } + catch (IOException e) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Disconnected from Login, trying to reconnect: ", e); + } + finally + { + // Clean up connection resources. + try + { + _socket.close(); + if (isInterrupted()) + { + return; + } + } + catch (Exception e) + { + // Ignore cleanup exceptions. + } + } + + // Wait before attempting reconnection. + try + { + Thread.sleep(RECONNECT_DELAY); + } + catch (InterruptedException e) + { + Thread.currentThread().interrupt(); + break; + } + } + } + + /** + * Adds waiting client and sends authentication request to login server. + * @param accountName the account name + * @param client the game client + * @param key the session key + */ + public void addWaitingClientAndSendRequest(String accountName, GameClient client, SessionKey key) + { + synchronized (_waitingClients) + { + _waitingClients.add(new WaitingClient(accountName, client, key)); + } + + sendPacket(new PlayerAuthRequest(accountName, key)); + } + + /** + * Removes waiting client from authentication queue. + * @param client the game client to remove + */ + public void removeWaitingClient(GameClient client) + { + WaitingClient toRemove = null; + synchronized (_waitingClients) + { + for (WaitingClient c : _waitingClients) + { + if (c.gameClient == client) + { + toRemove = c; + break; + } + } + + if (toRemove != null) + { + _waitingClients.remove(toRemove); + } + } + } + + /** + * Sends logout notification for specified account. + * @param account the account name + */ + public void sendLogout(String account) + { + if (account == null) + { + return; + } + + final GameClient removed = _accountsInGameServer.remove(account); + if (removed != null) + { + removed.disconnect(); + } + + sendPacket(new PlayerLogout(account)); + } + + /** + * Adds game server login entry for account tracking. + * @param account the account name + * @param client the game client + * @return true if account was not already logged in, false otherwise + */ + public boolean addGameServerLogin(String account, GameClient client) + { + return _accountsInGameServer.putIfAbsent(account, client) == null; + } + + /** + * Sends access level change notification to login server. + * @param account the account name + * @param level the new access level + */ + public void sendAccessLevel(String account, int level) + { + sendPacket(new ChangeAccessLevel(account, level)); + } + + /** + * Sends client trace route information to login server. + * @param account the account name + * @param address the trace route addresses (5 hops) + */ + public void sendClientTracert(String account, String[] address) + { + sendPacket(new PlayerTracert(account, address[0], address[1], address[2], address[3], address[4])); + } + + /** + * Sends mail notification to login server. + * @param account the account name + * @param mailId the mail identifier + * @param args additional mail arguments + */ + public void sendMail(String account, String mailId, String... args) + { + sendPacket(new SendMail(account, mailId, args)); + } + + /** + * Sends temporary ban notification to login server. + * @param account the account name + * @param ip the IP address to ban + * @param time the ban duration in milliseconds + */ + public void sendTempBan(String account, String ip, long time) + { + sendPacket(new TempBan(account, ip, time)); + } + + /** + * Converts hex byte array to hexadecimal string representation. + * @param hex the hex byte array + * @return the hex string + */ + private String hexToString(byte[] hex) + { + return new BigInteger(hex).toString(16); + } + + /** + * Forcibly kicks player from the game server. + * @param account the account name to kick + */ + private void doKickPlayer(String account) + { + final GameClient client = _accountsInGameServer.get(account); + if (client != null) + { + final SystemMessage msg = new SystemMessage(SystemMessageId.ANOTHER_PERSON_HAS_LOGGED_IN_WITH_THE_SAME_ACCOUNT); + + if (client.isDetached()) + { + final Player player = client.getPlayer(); + if (player != null) + { + player.storeMe(); + player.deleteMe(); + } + + client.close(msg); + } + else + { + Disconnection.of(client).storeAndDeleteWith(msg); + ACCOUNTING_LOGGER.info("Kicked by login, " + client); + } + } + + sendLogout(account); + } + + /** + * Retrieves character count and deletion times for specified account. + * @param account the account name + */ + private void getCharsOnServer(String account) + { + int chars = 0; + final List charToDel = new ArrayList<>(); + + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement("SELECT deletetime FROM characters WHERE account_name=?")) + { + ps.setString(1, account); + try (ResultSet rs = ps.executeQuery()) + { + while (rs.next()) + { + chars++; + final long delTime = rs.getLong("deletetime"); + if (delTime != 0) + { + charToDel.add(delTime); + } + } + } + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Exception in getCharsOnServer: " + e.getMessage(), e); + } + + sendPacket(new ReplyCharacters(account, chars, charToDel)); + } + + /** + * Sends packet to login server with proper encryption and checksums. + * @param packet the packet to send + */ + private void sendPacket(SimpleWritablePacket packet) + { + if ((_blowfish == null) || (_socket == null) || _socket.isClosed()) + { + return; + } + + try + { + packet.write(); // Write initial packet data. + packet.writeInt(0); // Reserved space for checksum. + int size = packet.getLength() - 2; // Size without header. + + // Add padding to align packet to 8-byte boundary. + final int padding = size % PACKET_PADDING; + if (padding != 0) + { + for (int i = padding; i < PACKET_PADDING; i++) + { + packet.writeByte(0); + } + } + + // Get final packet data for encryption. + final byte[] data = packet.getSendableBytes(); + size = data.length - 2; // Data size without header. + + synchronized (_out) + { + NewCrypt.appendChecksum(data, 2, size); + _blowfish.crypt(data, 2, size); + + _out.write(data); + try + { + _out.flush(); + } + catch (IOException e) + { + // LoginServer might have terminated connection. + } + } + } + catch (Exception e) + { + LOGGER.severe("LoginServerThread: IOException while sending packet " + packet.getClass().getSimpleName()); + LOGGER.severe(TraceUtil.getStackTrace(e)); + } + } + + /** + * Sets maximum player count and notifies login server. + * @param maxPlayer the maximum player count + */ + public void setMaxPlayer(int maxPlayer) + { + sendServerStatus(ServerStatus.MAX_PLAYERS, maxPlayer); + _maxPlayer = maxPlayer; + } + + /** + * Gets current maximum player count. + * @return the maximum player count + */ + public int getMaxPlayer() + { + return _maxPlayer; + } + + /** + * Sends server status update to login server. + * @param id the status attribute identifier + * @param value the status value + */ + public void sendServerStatus(int id, int value) + { + final ServerStatus serverStatus = new ServerStatus(); + serverStatus.addAttribute(id, value); + sendPacket(serverStatus); + } + + /** + * Sends server type configuration to login server. + */ + public void sendServerType() + { + final ServerStatus serverStatus = new ServerStatus(); + serverStatus.addAttribute(ServerStatus.SERVER_TYPE, ServerConfig.SERVER_LIST_TYPE); + sendPacket(serverStatus); + } + + /** + * Sends password change request to login server. + * @param accountName the account name + * @param charName the character name + * @param oldpass the current password + * @param newpass the new password + */ + public void sendChangePassword(String accountName, String charName, String oldpass, String newpass) + { + sendPacket(new ChangePassword(accountName, charName, oldpass, newpass)); + } + + /** + * Gets current server status. + * @return the server status code + */ + public int getServerStatus() + { + return _status; + } + + /** + * Gets server status as human-readable string. + * @return the status string representation + */ + public String getStatusString() + { + return ServerStatus.STATUS_STRING[_status]; + } + + /** + * Gets registered server name. + * @return the server name + */ + public String getServerName() + { + return _serverName; + } + + /** + * Sets server status and notifies login server. + * @param status the new server status + */ + public void setServerStatus(int status) + { + switch (status) + { + case ServerStatus.STATUS_AUTO: + case ServerStatus.STATUS_DOWN: + case ServerStatus.STATUS_FULL: + case ServerStatus.STATUS_GM_ONLY: + case ServerStatus.STATUS_GOOD: + case ServerStatus.STATUS_NORMAL: + { + sendServerStatus(ServerStatus.SERVER_LIST_STATUS, status); + _status = status; + break; + } + default: + { + throw new IllegalArgumentException("Invalid server status: " + status); + } + } + } + + /** + * Gets game client for specified account name. + * @param name the account name + * @return the game client or null if not found + */ + public GameClient getClient(String name) + { + return name != null ? _accountsInGameServer.get(name) : null; + } + + /** + * Session key container for player authentication between login and game servers. + */ + public static class SessionKey + { + public int playOkID1; + public int playOkID2; + public int loginOkID1; + public int loginOkID2; + + public SessionKey(int loginOK1, int loginOK2, int playOK1, int playOK2) + { + playOkID1 = playOK1; + playOkID2 = playOK2; + loginOkID1 = loginOK1; + loginOkID2 = loginOK2; + } + + @Override + public String toString() + { + return "PlayOk: " + playOkID1 + " " + playOkID2 + " LoginOk:" + loginOkID1 + " " + loginOkID2; + } + } + + /** + * Represents a client waiting for authentication response from login server. + */ + private static class WaitingClient + { + public String account; + public GameClient gameClient; + public SessionKey sessionKey; + + public WaitingClient(String acc, GameClient client, SessionKey key) + { + account = acc; + gameClient = client; + sessionKey = key; + } + } + + public static LoginServerThread getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final LoginServerThread INSTANCE = new LoginServerThread(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/Shutdown.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/Shutdown.java new file mode 100644 index 0000000..bcf4834 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/Shutdown.java @@ -0,0 +1,601 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver; + +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.commons.config.DatabaseConfig; +import org.l2jmobius.commons.database.DatabaseBackup; +import org.l2jmobius.commons.database.DatabaseFactory; +import org.l2jmobius.commons.threads.ThreadPool; +import org.l2jmobius.gameserver.config.GeneralConfig; +import org.l2jmobius.gameserver.config.ServerConfig; +import org.l2jmobius.gameserver.config.custom.AutoPlayConfig; +import org.l2jmobius.gameserver.config.custom.OfflinePlayConfig; +import org.l2jmobius.gameserver.config.custom.OfflineTradeConfig; +import org.l2jmobius.gameserver.data.SchemeBufferTable; +import org.l2jmobius.gameserver.data.sql.ClanTable; +import org.l2jmobius.gameserver.data.sql.OfflinePlayTable; +import org.l2jmobius.gameserver.data.sql.OfflineTraderTable; +import org.l2jmobius.gameserver.entity.World; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.managers.CHSiegeManager; +import org.l2jmobius.gameserver.managers.CastleManorManager; +import org.l2jmobius.gameserver.managers.CursedWeaponsManager; +import org.l2jmobius.gameserver.managers.FishingChampionshipManager; +import org.l2jmobius.gameserver.managers.GlobalVariablesManager; +import org.l2jmobius.gameserver.managers.GrandBossManager; +import org.l2jmobius.gameserver.managers.ItemsOnGroundManager; +import org.l2jmobius.gameserver.managers.PrecautionaryRestartManager; +import org.l2jmobius.gameserver.managers.RaidBossSpawnManager; +import org.l2jmobius.gameserver.managers.ScriptManager; +import org.l2jmobius.gameserver.mechanics.olympiad.Hero; +import org.l2jmobius.gameserver.mechanics.olympiad.Olympiad; +import org.l2jmobius.gameserver.mechanics.sevensigns.SevenSigns; +import org.l2jmobius.gameserver.mechanics.sevensigns.SevenSignsFestival; +import org.l2jmobius.gameserver.network.Disconnection; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.loginserverpackets.send.ServerStatus; +import org.l2jmobius.gameserver.network.serverpackets.ServerClose; +import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; +import org.l2jmobius.gameserver.taskmanagers.GameTimeTaskManager; + +/** + * This class provides the functions for shutting down and restarting the server.
+ * It closes all open client connections and saves all data. + * @version $Revision: 1.2.4.5 $ $Date: 2005/03/27 15:29:09 $ + */ +public class Shutdown extends Thread +{ + private static final Logger LOGGER = Logger.getLogger(Shutdown.class.getName()); + + private static final int SIGTERM = 0; + private static final int GM_SHUTDOWN = 1; + private static final int GM_RESTART = 2; + private static final int ABORT = 3; + private static final String[] MODE_TEXT = + { + "SIGTERM", + "shutting down", + "restarting", + "aborting" + }; + + private static Shutdown _counterInstance; + private static boolean _countdownFinished; + + private static volatile int _secondsShut; + private static volatile int _shutdownMode; + + /** + * This function starts a shutdown count down (Copied from Function startShutdown()) + * @param seconds seconds until shutdown + */ + private void sendServerQuit(int seconds) + { + final SystemMessage sysm = new SystemMessage(SystemMessageId.THE_SERVER_WILL_BE_COMING_DOWN_IN_S1_SECOND_S_PLEASE_FIND_A_SAFE_PLACE_TO_LOG_OUT); + sysm.addInt(seconds); + World.broadcastToAllOnlinePlayers(sysm); + } + + /** + * Default constructor is only used internal to create the shutdown-hook instance + */ + protected Shutdown() + { + _secondsShut = -1; + _shutdownMode = SIGTERM; + } + + /** + * This creates a countdown instance of Shutdown. + * @param seconds how many seconds until shutdown + * @param restart true is the server shall restart after shutdown + */ + public Shutdown(int seconds, boolean restart) + { + _secondsShut = Math.max(0, seconds); + _shutdownMode = restart ? GM_RESTART : GM_SHUTDOWN; + } + + /** + * This function is called, when a new thread starts if this thread is the thread of getInstance, then this is the shutdown hook and we save all data and disconnect all clients.
+ * After this thread ends, the server will completely exit if this is not the thread of getInstance, then this is a countdown thread.
+ * We start the countdown, and when we finished it, and it was not aborted, we tell the shutdown-hook why we call exit, and then call exit when the exit status of the server is 1, startServer.sh / startServer.bat will restart the server. + */ + @Override + public void run() + { + if (this == getInstance()) + { + startShutdownActions(); + return; + } + + if (_countdownFinished) + { + return; + } + + // Send warnings and then call exit to start shutdown sequence. + countdown(); + + // Last point where logging is operational. + LOGGER.warning("GM shutdown countdown is over. " + MODE_TEXT[_shutdownMode] + " NOW!"); + + switch (_shutdownMode) + { + case GM_SHUTDOWN: + { + getInstance().setMode(GM_SHUTDOWN); + startShutdownActions(); + System.exit(0); + break; + } + case GM_RESTART: + { + getInstance().setMode(GM_RESTART); + startShutdownActions(); + System.exit(2); + break; + } + case ABORT: + { + LoginServerThread.getInstance().setServerStatus(ServerStatus.STATUS_AUTO); + break; + } + } + } + + /** + * This functions starts a shutdown countdown. + * @param player GM who issued the shutdown command + * @param seconds seconds until shutdown + * @param restart true if the server will restart after shutdown + */ + public void startShutdown(Player player, int seconds, boolean restart) + { + _shutdownMode = restart ? GM_RESTART : GM_SHUTDOWN; + + if (player != null) + { + LOGGER.warning("GM: " + player.getName() + "(" + player.getObjectId() + ") issued shutdown command. " + MODE_TEXT[_shutdownMode] + " in " + seconds + " seconds!"); + } + else + { + LOGGER.warning("Server scheduled restart issued shutdown command. " + (restart ? "Restart" : "Shutdown") + " in " + seconds + " seconds!"); + } + + if (_shutdownMode > 0) + { + switch (seconds) + { + case 540: + case 480: + case 420: + case 360: + case 300: + case 240: + case 180: + case 120: + case 60: + case 30: + case 10: + case 5: + case 4: + case 3: + case 2: + case 1: + { + break; + } + default: + { + sendServerQuit(seconds); + } + } + } + + if (_counterInstance != null) + { + _counterInstance.abort(); + } + + if (ServerConfig.PRECAUTIONARY_RESTART_ENABLED) + { + PrecautionaryRestartManager.getInstance().restartEnabled(); + } + + // The main instance should only run for shutdown hook, so we start a new instance. + _counterInstance = new Shutdown(seconds, restart); + _counterInstance.start(); + } + + /** + * This function aborts a running countdown. + * @param player GM who issued the abort command + */ + public void abort(Player player) + { + if (_countdownFinished) + { + LOGGER.warning("GM: " + (player != null ? player.getName() + "(" + player.getObjectId() + ") " : "") + "shutdown ABORT failed because countdown has finished."); + return; + } + + LOGGER.warning("GM: " + (player != null ? player.getName() + "(" + player.getObjectId() + ") " : "") + "issued shutdown ABORT. " + MODE_TEXT[_shutdownMode] + " has been stopped!"); + if (_counterInstance != null) + { + _counterInstance.abort(); + + if (ServerConfig.PRECAUTIONARY_RESTART_ENABLED) + { + PrecautionaryRestartManager.getInstance().restartAborted(); + } + + World.broadcastToAllOnlinePlayers("Server aborts " + MODE_TEXT[_shutdownMode] + " and continues normal operation!", false); + } + } + + /** + * Set the shutdown mode. + * @param mode what mode shall be set + */ + private void setMode(int mode) + { + _shutdownMode = mode; + } + + /** + * Set shutdown mode to ABORT. + */ + private void abort() + { + _shutdownMode = ABORT; + } + + /** + * This counts the countdown and reports it to all players countdown is aborted if mode changes to ABORT. + */ + private void countdown() + { + try + { + while (_secondsShut > 0) + { + // Rehabilitate previous server status if shutdown is aborted. + if (_shutdownMode == ABORT) + { + if (LoginServerThread.getInstance().getServerStatus() == ServerStatus.STATUS_DOWN) + { + LoginServerThread.getInstance().setServerStatus((GeneralConfig.SERVER_GMONLY) ? ServerStatus.STATUS_GM_ONLY : ServerStatus.STATUS_AUTO); + } + break; + } + + switch (_secondsShut) + { + case 540: + case 480: + case 420: + case 360: + case 300: + case 240: + case 180: + case 120: + case 60: + case 30: + case 10: + case 5: + case 4: + case 3: + case 2: + case 1: + { + sendServerQuit(_secondsShut); + } + } + + // Prevent players from logging in. + if ((_secondsShut <= 60) && (LoginServerThread.getInstance().getServerStatus() != ServerStatus.STATUS_DOWN)) + { + LoginServerThread.getInstance().setServerStatus(ServerStatus.STATUS_DOWN); + } + + _secondsShut--; + + Thread.sleep(1000); + } + } + catch (Exception e) + { + // This will never happen. + } + } + + /** + * Actions performed when shutdown countdown completes. + */ + private void startShutdownActions() + { + if (_countdownFinished) + { + return; + } + + _countdownFinished = true; + + final TimeCounter tc = new TimeCounter(); + final TimeCounter tc1 = new TimeCounter(); + + try + { + if ((OfflineTradeConfig.OFFLINE_TRADE_ENABLE || OfflineTradeConfig.OFFLINE_CRAFT_ENABLE) && OfflineTradeConfig.RESTORE_OFFLINERS && !OfflineTradeConfig.STORE_OFFLINE_TRADE_IN_REALTIME) + { + OfflineTraderTable.getInstance().storeOffliners(); + LOGGER.info("Offline Traders Table: Offline shops stored(" + tc.getEstimatedTimeAndRestartCounter() + "ms)."); + } + } + catch (Throwable t) + { + LOGGER.log(Level.WARNING, "Error saving offline shops.", t); + } + + try + { + if (OfflinePlayConfig.RESTORE_AUTO_PLAY_OFFLINERS && AutoPlayConfig.ENABLE_AUTO_ASSIST) + { + OfflinePlayTable.getInstance().storeOfflineGroups(); + LOGGER.info("Offline Play Table: Offline play groups stored(" + tc.getEstimatedTimeAndRestartCounter() + "ms)."); + } + } + catch (Throwable t) + { + LOGGER.log(Level.WARNING, "Error saving offline play groups.", t); + } + + try + { + disconnectAllCharacters(); + LOGGER.info("All players disconnected and saved(" + tc.getEstimatedTimeAndRestartCounter() + "ms)."); + } + catch (Throwable t) + { + // ignore + } + + // ensure all services are stopped + + try + { + GameTimeTaskManager.getInstance().interrupt(); + LOGGER.info("Game Time Task Manager: Thread interruped(" + tc.getEstimatedTimeAndRestartCounter() + "ms)."); + } + catch (Throwable t) + { + // ignore + } + + // stop all thread pools + try + { + ThreadPool.shutdown(); + LOGGER.info("Thread Pool Manager: Manager has been shut down(" + tc.getEstimatedTimeAndRestartCounter() + "ms)."); + } + catch (Throwable t) + { + // ignore + } + + try + { + LoginServerThread.getInstance().interrupt(); + LOGGER.info("Login Server Thread: Thread interruped(" + tc.getEstimatedTimeAndRestartCounter() + "ms)."); + } + catch (Throwable t) + { + // ignore + } + + // last byebye, save all data and quit this server + saveData(); + tc.restartCounter(); + + // commit data, last chance + try + { + DatabaseFactory.close(); + LOGGER.info("Database Factory: Database connection has been shut down(" + tc.getEstimatedTimeAndRestartCounter() + "ms)."); + } + catch (Throwable t) + { + // ignore + } + + // Backup database. + if (DatabaseConfig.BACKUP_DATABASE) + { + DatabaseBackup.performBackup("game"); + } + + LOGGER.info("The server has been successfully shut down in " + (tc1.getEstimatedTime() / 1000) + "seconds."); + } + + /** + * This sends a last byebye, disconnects all players and saves data. + */ + private void saveData() + { + switch (_shutdownMode) + { + case SIGTERM: + { + LOGGER.info("SIGTERM received. Shutting down NOW!"); + break; + } + case GM_SHUTDOWN: + { + LOGGER.info("GM shutdown received. Shutting down NOW!"); + break; + } + case GM_RESTART: + { + LOGGER.info("GM restart received. Restarting NOW!"); + break; + } + } + + final TimeCounter tc = new TimeCounter(); + + // Seven Signs data is now saved along with Festival data. + if (!SevenSigns.getInstance().isSealValidationPeriod()) + { + SevenSignsFestival.getInstance().saveFestivalData(false); + LOGGER.info("SevenSignsFestival: Festival data saved(" + tc.getEstimatedTimeAndRestartCounter() + "ms)."); + } + + // Save Seven Signs data before closing. :) + SevenSigns.getInstance().saveSevenSignsData(); + LOGGER.info("SevenSigns: Seven Signs data saved(" + tc.getEstimatedTimeAndRestartCounter() + "ms)."); + SevenSigns.getInstance().saveSevenSignsStatus(); + LOGGER.info("SevenSigns: Seven Signs status saved(" + tc.getEstimatedTimeAndRestartCounter() + "ms)."); + + // Save all raidboss and GrandBoss status ^_^ + RaidBossSpawnManager.getInstance().cleanUp(); + LOGGER.info("RaidBossSpawnManager: All raidboss info saved(" + tc.getEstimatedTimeAndRestartCounter() + "ms)."); + GrandBossManager.getInstance().cleanUp(); + LOGGER.info("GrandBossManager: All Grand Boss info saved(" + tc.getEstimatedTimeAndRestartCounter() + "ms)."); + Olympiad.getInstance().saveOlympiadStatus(); + LOGGER.info("Olympiad System: Data saved(" + tc.getEstimatedTimeAndRestartCounter() + "ms)."); + Hero.getInstance().shutdown(); + LOGGER.info("Hero System: Data saved(" + tc.getEstimatedTimeAndRestartCounter() + "ms)."); + ClanTable.getInstance().shutdown(); + LOGGER.info("Clan System: Data saved(" + tc.getEstimatedTimeAndRestartCounter() + "ms)."); + + // Save Cursed Weapons data before closing. + CursedWeaponsManager.getInstance().saveData(); + LOGGER.info("Cursed Weapons Manager: Data saved(" + tc.getEstimatedTimeAndRestartCounter() + "ms)."); + + // Save all manor data. + if (!GeneralConfig.ALT_MANOR_SAVE_ALL_ACTIONS) + { + CastleManorManager.getInstance().storeMe(); + LOGGER.info("Castle Manor Manager: Data saved(" + tc.getEstimatedTimeAndRestartCounter() + "ms)."); + } + + CHSiegeManager.getInstance().onServerShutDown(); + LOGGER.info("CHSiegeManager: Siegable hall attacker lists saved!"); + + // Save all global (non-player specific) Quest data that needs to persist after reboot. + ScriptManager.getInstance().save(); + LOGGER.info("Script Manager: Data saved(" + tc.getEstimatedTimeAndRestartCounter() + "ms)."); + + // Save all global variables data. + GlobalVariablesManager.getInstance().storeMe(); + LOGGER.info("Global Variables Manager: Variables saved(" + tc.getEstimatedTimeAndRestartCounter() + "ms)."); + + // Save Fishing tournament data. + if (GeneralConfig.ALT_FISH_CHAMPIONSHIP_ENABLED) + { + FishingChampionshipManager.getInstance().shutdown(); + LOGGER.info("Fishing Championship Manager: Data saved(" + tc.getEstimatedTimeAndRestartCounter() + "ms)."); + } + + // Schemes save. + SchemeBufferTable.getInstance().saveSchemes(); + LOGGER.info("SchemeBufferTable: Data saved(" + tc.getEstimatedTimeAndRestartCounter() + "ms)."); + + // Save items on ground before closing. + if (GeneralConfig.SAVE_DROPPED_ITEM) + { + ItemsOnGroundManager.getInstance().saveInDb(); + LOGGER.info("Items On Ground Manager: Data saved(" + tc.getEstimatedTimeAndRestartCounter() + "ms)."); + ItemsOnGroundManager.getInstance().cleanUp(); + LOGGER.info("Items On Ground Manager: Cleaned up(" + tc.getEstimatedTimeAndRestartCounter() + "ms)."); + } + + try + { + Thread.sleep(5000); + } + catch (Exception e) + { + // This will never happen. + } + } + + /** + * This disconnects all clients from the server. + */ + private void disconnectAllCharacters() + { + for (Player player : World.getPlayers()) + { + Disconnection.of(player).storeAndDeleteWith(ServerClose.STATIC_PACKET); + } + } + + /** + * A simple class used to track down the estimated time of method executions.
+ * Once this class is created, it saves the start time, and when you want to get the estimated time, use the getEstimatedTime() method. + */ + private static class TimeCounter + { + private long _startTime; + + protected TimeCounter() + { + restartCounter(); + } + + public void restartCounter() + { + _startTime = System.currentTimeMillis(); + } + + public long getEstimatedTimeAndRestartCounter() + { + final long toReturn = System.currentTimeMillis() - _startTime; + restartCounter(); + return toReturn; + } + + public long getEstimatedTime() + { + return System.currentTimeMillis() - _startTime; + } + } + + /** + * Get the shutdown-hook instance the shutdown-hook instance is created by the first call of this function, but it has to be registered externally. + * @return instance of Shutdown, to be used as shutdown hook + */ + public static Shutdown getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final Shutdown INSTANCE = new Shutdown(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/ai/AbstractAI.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/ai/AbstractAI.java new file mode 100644 index 0000000..eb889c2 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/ai/AbstractAI.java @@ -0,0 +1,568 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.ai; + +import org.l2jmobius.gameserver.entity.Location; +import org.l2jmobius.gameserver.entity.WorldObject; +import org.l2jmobius.gameserver.entity.WorldRegion; +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.Summon; +import org.l2jmobius.gameserver.interfaces.ILocational; +import org.l2jmobius.gameserver.mechanics.skill.Skill; +import org.l2jmobius.gameserver.network.serverpackets.ActionFailed; +import org.l2jmobius.gameserver.network.serverpackets.AutoAttackStart; +import org.l2jmobius.gameserver.network.serverpackets.AutoAttackStop; +import org.l2jmobius.gameserver.network.serverpackets.Die; +import org.l2jmobius.gameserver.network.serverpackets.MoveToLocation; +import org.l2jmobius.gameserver.network.serverpackets.MoveToPawn; +import org.l2jmobius.gameserver.network.serverpackets.StopMove; +import org.l2jmobius.gameserver.taskmanagers.AttackStanceTaskManager; +import org.l2jmobius.gameserver.taskmanagers.CreatureFollowTaskManager; +import org.l2jmobius.gameserver.taskmanagers.GameTimeTaskManager; + +/** + * Mother class of all objects AI in the world.
+ * AbastractAI:
+ *
  • CreatureAI
  • + */ +public abstract class AbstractAI +{ + /** The creature that this AI manages. */ + protected final Creature _actor; + + /** Current long-term intention. */ + protected Intention _intention = Intention.IDLE; + + /** Flags about client's state, in order to know which messages to send. */ + protected volatile boolean _clientAutoAttacking; + /** Flags about client's state, in order to know which messages to send. */ + protected int _clientMovingToPawnOffset; + + /** Different targets this AI maintains. */ + private WorldObject _target; + private Creature _castTarget; + protected Creature _attackTarget; + protected Creature _followTarget; + + /** The skill we are currently casting by INTENTION_CAST. */ + protected Skill _skill; + + /** Different internal state flags. */ + private int _moveToPawnTimeout; + + private NextAction _nextAction; + + /** + * @return the _nextAction + */ + public NextAction getNextAction() + { + return _nextAction; + } + + /** + * @param nextAction the next action to set. + */ + public void setNextAction(NextAction nextAction) + { + _nextAction = nextAction; + } + + protected AbstractAI(Creature creature) + { + _actor = creature; + } + + /** + * @return the Creature managed by this Accessor AI. + */ + public Creature getActor() + { + return _actor; + } + + /** + * @return the current Intention. + */ + public Intention getIntention() + { + return _intention; + } + + /** + * @return the saved Intention pending replay (e.g. after a CAST finishes), or {@code null} if none.
    + * Overridden in: + *
      + *
    • PlayerAI : returns the intention that was interrupted by the current CAST
    • + *
    • SummonAI : returns {@link Intention#ATTACK} if a pending attack is saved
    • + *
    + */ + public Intention getNextIntention() + { + return null; + } + + public abstract void setIntentionIdle(); + + public abstract void setIntentionActive(); + + public abstract void setIntentionRest(); + + public abstract void setIntentionAttack(WorldObject target); + + public abstract void setIntentionCast(Skill skill, WorldObject target); + + public abstract void setIntentionMoveTo(ILocational destination); + + public abstract void setIntentionFollow(WorldObject target); + + public abstract void setIntentionPickUp(WorldObject item); + + public abstract void setIntentionInteract(WorldObject object); + + public abstract void notifyActionThink(); + + public abstract void notifyActionAttacked(WorldObject attacker); + + public abstract void notifyActionAggression(WorldObject target, int aggro); + + public abstract void notifyActionStunned(); + + public abstract void notifyActionParalyzed(); + + public abstract void notifyActionSleeping(); + + public abstract void notifyActionRooted(); + + public abstract void notifyActionConfused(); + + public abstract void notifyActionMuted(); + + public abstract void notifyActionEvaded(WorldObject attacker); + + public abstract void notifyActionReadyToAct(); + + public abstract void notifyActionUserCmd(Object arg0, Object arg1); + + public abstract void notifyActionArrived(); + + public abstract void notifyActionArrivedRevalidate(); + + public abstract void notifyActionArrivedBlocked(Location location); + + public abstract void notifyActionForgetObject(WorldObject object); + + public abstract void notifyActionCancel(); + + public abstract void notifyActionDeath(); + + public abstract void notifyActionFakeDeath(); + + public abstract void notifyActionAfraid(WorldObject effector, boolean start); + + public abstract void notifyActionFinishCasting(); + + /** + * Cancel action client side by sending Server->Client packet ActionFailed to the Player actor.
    + * Caution: Low level function, used by AI subclasses + */ + protected void clientActionFailed() + { + if (_actor.isPlayer()) + { + _actor.sendPacket(ActionFailed.STATIC_PACKET); + } + } + + /** + * Move the actor to Pawn server side AND client side by sending Server->Client packet MoveToPawn (broadcast).
    + * Caution: Low level function, used by AI subclasses + * @param pawn + * @param offsetValue + */ + public void moveToPawn(WorldObject pawn, int offsetValue) + { + // Check if actor can move. + if (!_actor.isMovementDisabled() && !_actor.isAttackingNow() && !_actor.isCastingNow()) + { + int offset = offsetValue; + if (offset < 10) + { + offset = 10; + } + + // Prevent possible extra calls to this function (there is none?), also don't send movetopawn packets too often. + final int gameTime = GameTimeTaskManager.getInstance().getGameTicks(); + if (_actor.isMoving() && (_target == pawn)) + { + if (_clientMovingToPawnOffset == offset) + { + if (gameTime < _moveToPawnTimeout) + { + return; + } + } + // Minimum time to calculate new route is 2 seconds. + else if (_actor.isOnGeodataPath() && (gameTime < (_moveToPawnTimeout + 10))) + { + return; + } + } + + // Set AI movement data. + _clientMovingToPawnOffset = offset; + _target = pawn; + _moveToPawnTimeout = gameTime; + _moveToPawnTimeout += 1000 / GameTimeTaskManager.MILLIS_IN_TICK; + + if (pawn == null) + { + return; + } + + // Calculate movement data for a move to location action and add the actor to movingObjects of GameTimeTaskManager. + _actor.moveToLocation(pawn.getX(), pawn.getY(), pawn.getZ(), offset); + + // May result to make monsters stop moving. + // if (!_actor.isMoving()) + // { + // clientActionFailed(); + // return; + // } + + // Send a Server->Client packet MoveToPawn/MoveToLocation to the actor and all Player in its known players. + if (pawn.isCreature()) + { + if (_actor.isOnGeodataPath()) + { + _actor.broadcastMoveToLocation(); + _clientMovingToPawnOffset = 0; + } + else + { + final WorldRegion region = _actor.getWorldRegion(); + if ((region != null) && region.isActive()) + { + _actor.broadcastPacket(new MoveToPawn(_actor, pawn, offset)); + } + } + } + else + { + _actor.broadcastMoveToLocation(); + } + } + else + { + clientActionFailed(); + } + } + + public void moveTo(ILocational loc) + { + moveTo(loc.getX(), loc.getY(), loc.getZ()); + } + + /** + * Move the actor to Location (x,y,z) server side AND client side by sending Server->Client packet MoveToLocation (broadcast).
    + * Caution: Low level function, used by AI subclasses + * @param x + * @param y + * @param z + */ + protected void moveTo(int x, int y, int z) + { + // Check if actor can move. + if (!_actor.isMovementDisabled()) + { + // Set AI movement data. + _clientMovingToPawnOffset = 0; + + // Calculate movement data for a move to location action and add the actor to movingObjects of GameTimeTaskManager. + _actor.moveToLocation(x, y, z, 0); + + // Send a Server->Client packet MoveToLocation to the actor and all Player in its known players. + _actor.broadcastMoveToLocation(); + } + else + { + clientActionFailed(); + } + } + + /** + * Stop the actor movement server side AND client side by sending Server->Client packet StopMove/StopRotation (broadcast).
    + * Caution: Low level function, used by AI subclasses + * @param loc + */ + public void clientStopMoving(Location loc) + { + // Stop movement of the Creature. + if (_actor.isMoving()) + { + _actor.stopMove(loc); + } + + _clientMovingToPawnOffset = 0; + } + + /** + * Client has already arrived to target, no need to force StopMove packet. + */ + protected void clientStoppedMoving() + { + if (_clientMovingToPawnOffset > 0) // movetoPawn needs to be stopped. + { + _clientMovingToPawnOffset = 0; + _actor.broadcastPacket(new StopMove(_actor)); + } + } + + public boolean isAutoAttacking() + { + return _clientAutoAttacking; + } + + public void setAutoAttacking(boolean isAutoAttacking) + { + if (_actor.isSummon()) + { + final Summon summon = _actor.asSummon(); + if (summon.getOwner() != null) + { + summon.getOwner().getAI().setAutoAttacking(isAutoAttacking); + } + return; + } + + _clientAutoAttacking = isAutoAttacking; + } + + /** + * Start the actor Auto Attack client side by sending Server->Client packet AutoAttackStart (broadcast).
    + * Caution: Low level function, used by AI subclasses + */ + public void clientStartAutoAttack() + { + // Non attackable NPCs should not get in combat. + if (_actor.isNpc() && (!_actor.isAttackable() || _actor.isCoreAIDisabled())) + { + return; + } + + if (_actor.isSummon()) + { + final Summon summon = _actor.asSummon(); + if (summon.getOwner() != null) + { + summon.getOwner().getAI().clientStartAutoAttack(); + } + return; + } + + if (!_clientAutoAttacking) + { + if (_actor.isPlayer()) + { + final Player player = _actor.asPlayer(); + if (player.hasSummon()) + { + final Summon summon = player.getSummon(); + summon.broadcastPacket(new AutoAttackStart(summon.getObjectId())); + } + } + + // Send a Server->Client packet AutoAttackStart to the actor and all Player in its known players. + _actor.broadcastPacket(new AutoAttackStart(_actor.getObjectId())); + setAutoAttacking(true); + } + + AttackStanceTaskManager.getInstance().addAttackStanceTask(_actor); + } + + /** + * Stop the actor auto-attack client side by sending Server->Client packet AutoAttackStop (broadcast).
    + * Caution: Low level function, used by AI subclasses + */ + public void clientStopAutoAttack() + { + if (_actor.isSummon()) + { + final Summon summon = _actor.asSummon(); + if (summon.getOwner() != null) + { + summon.getOwner().getAI().clientStopAutoAttack(); + } + return; + } + + if (_actor.isPlayer()) + { + if (!AttackStanceTaskManager.getInstance().hasAttackStanceTask(_actor) && isAutoAttacking()) + { + AttackStanceTaskManager.getInstance().addAttackStanceTask(_actor); + } + } + else if (_clientAutoAttacking) + { + _actor.broadcastPacket(new AutoAttackStop(_actor.getObjectId())); + setAutoAttacking(false); + } + } + + public int getClientMovingToPawnOffset() + { + return _clientMovingToPawnOffset; + } + + /** + * Kill the actor client side by sending Server->Client packet AutoAttackStop, StopMove/StopRotation, Die (broadcast).
    + * Caution: Low level function, used by AI subclasses + */ + protected void clientNotifyDead() + { + // Send a Server->Client packet Die to the actor and all Player in its known players. + _actor.broadcastPacket(new Die(_actor)); + + // Init AI + _intention = Intention.IDLE; + _target = null; + _castTarget = null; + _attackTarget = null; + + // Cancel the follow task if necessary. + stopFollow(); + } + + /** + * Update the state of this actor client side by sending Server->Client packet MoveToPawn/MoveToLocation and AutoAttackStart to the Player player.
    + * Caution: Low level function, used by AI subclasses + * @param player The PlayerIstance to notify with state of this Creature + */ + public void describeStateToPlayer(Player player) + { + if (_actor.isVisibleFor(player) && _actor.isMoving()) + { + if ((_clientMovingToPawnOffset != 0) && isFollowing()) + { + // Send a Server->Client packet MoveToPawn to the actor and all Player in its known players. + player.sendPacket(new MoveToPawn(_actor, _followTarget, _clientMovingToPawnOffset)); + } + else + { + // Send a Server->Client packet MoveToLocation to the actor and all Player in its known players. + player.sendPacket(new MoveToLocation(_actor)); + } + } + } + + public boolean isFollowing() + { + return (_followTarget != null) && _followTarget.isCreature() && ((_intention == Intention.FOLLOW) || CreatureFollowTaskManager.getInstance().isFollowing(_actor)); + } + + /** + * Create and Launch an AI Follow Task to execute every 1s. + * @param target The Creature to follow + */ + public void startFollow(Creature target) + { + startFollow(target, -1); + } + + /** + * Create and Launch an AI Follow Task to execute every 0.5s, following at specified range. + * @param target The Creature to follow + * @param range + */ + public void startFollow(Creature target, int range) + { + stopFollow(); + _followTarget = target; + if (range == -1) + { + CreatureFollowTaskManager.getInstance().addNormalFollow(_actor, range); + } + else + { + CreatureFollowTaskManager.getInstance().addAttackFollow(_actor, range); + } + } + + /** + * Stop an AI Follow Task. + */ + public void stopFollow() + { + CreatureFollowTaskManager.getInstance().remove(_actor); + _followTarget = null; + } + + public Creature getFollowTarget() + { + return _followTarget; + } + + protected WorldObject getTarget() + { + return _target; + } + + protected void setTarget(WorldObject target) + { + _target = target; + } + + protected void setCastTarget(Creature target) + { + _castTarget = target; + } + + public Creature getCastTarget() + { + return _castTarget; + } + + protected void setAttackTarget(Creature target) + { + _attackTarget = target; + } + + public Creature getAttackTarget() + { + return _attackTarget; + } + + /** + * Stop all Ai tasks and futures. + */ + public void stopAITask() + { + stopFollow(); + } + + @Override + public String toString() + { + return "Actor: " + _actor; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/ai/Action.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/ai/Action.java new file mode 100644 index 0000000..38f1d72 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/ai/Action.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.ai; + +/** + * Enum representing possible actions that can occur for an AI character. + */ +public enum Action +{ + /** AI must decide the next action after a change. */ + THINK, + + /** Actor was attacked, may trigger a response. */ + ATTACKED, + + /** Increase/decrease aggression towards a target or reduce global aggression. */ + AGGRESSION, + + /** Actor is stunned and cannot act. */ + STUNNED, + + /** Actor is paralyzed or petrified and cannot move or act. */ + PARALYZED, + + /** Actor starts or stops sleeping. */ + SLEEPING, + + /** Actor is rooted and cannot move. */ + ROOTED, + + /** Actor evaded an attack. */ + EVADED, + + /** Previous action was completed, ready for the next. */ + READY_TO_ACT, + + /** User's command, such as using combat magic or changing weapons. */ + USER_CMD, + + /** Actor arrived at the assigned location. */ + ARRIVED, + + /** Actor arrived at an intermediate point and needs to revalidate destination. */ + ARRIVED_REVALIDATE, + + /** Actor cannot move further. */ + ARRIVED_BLOCKED, + + /** Actor forgets a specific object. */ + FORGET_OBJECT, + + /** Attempt to cancel the current step without changing intention. */ + CANCEL, + + /** Actor is dead. */ + DEATH, + + /** Actor appears to be dead but isn't. */ + FAKE_DEATH, + + /** Actor attacks randomly. */ + CONFUSED, + + /** Actor cannot cast spells. */ + MUTED, + + /** Actor flees in random directions. */ + AFRAID, + + /** Actor finishes casting a spell. */ + FINISH_CASTING, + + /** Actor betrays its master. */ + BETRAYED +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/ai/AttackableAI.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/ai/AttackableAI.java new file mode 100644 index 0000000..953e918 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/ai/AttackableAI.java @@ -0,0 +1,2751 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.ai; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.concurrent.Future; + +import org.l2jmobius.commons.threads.ThreadPool; +import org.l2jmobius.commons.util.Rnd; +import org.l2jmobius.gameserver.config.GeneralConfig; +import org.l2jmobius.gameserver.config.NpcConfig; +import org.l2jmobius.gameserver.config.custom.ChampionMonstersConfig; +import org.l2jmobius.gameserver.config.custom.FactionSystemConfig; +import org.l2jmobius.gameserver.config.custom.FakePlayersConfig; +import org.l2jmobius.gameserver.entity.Location; +import org.l2jmobius.gameserver.entity.World; +import org.l2jmobius.gameserver.entity.WorldObject; +import org.l2jmobius.gameserver.entity.WorldRegion; +import org.l2jmobius.gameserver.entity.actor.Attackable; +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.Npc; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.enums.npc.AISkillScope; +import org.l2jmobius.gameserver.entity.actor.enums.npc.AIType; +import org.l2jmobius.gameserver.entity.actor.holders.npc.AggroInfo; +import org.l2jmobius.gameserver.entity.actor.instance.FestivalMonster; +import org.l2jmobius.gameserver.entity.actor.instance.FriendlyMob; +import org.l2jmobius.gameserver.entity.actor.instance.GrandBoss; +import org.l2jmobius.gameserver.entity.actor.instance.Guard; +import org.l2jmobius.gameserver.entity.actor.instance.Monster; +import org.l2jmobius.gameserver.entity.actor.instance.RaidBoss; +import org.l2jmobius.gameserver.entity.actor.instance.RiftInvader; +import org.l2jmobius.gameserver.entity.actor.instance.StaticObject; +import org.l2jmobius.gameserver.entity.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.entity.groups.Party; +import org.l2jmobius.gameserver.entity.item.instance.Item; +import org.l2jmobius.gameserver.entity.spawns.Spawn; +import org.l2jmobius.gameserver.entity.zone.ZoneId; +import org.l2jmobius.gameserver.entity.zone.type.BossZone; +import org.l2jmobius.gameserver.entity.zone.type.NpcSpawnTerritory; +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.interfaces.ILocational; +import org.l2jmobius.gameserver.managers.DimensionalRiftManager; +import org.l2jmobius.gameserver.managers.ItemsOnGroundManager; +import org.l2jmobius.gameserver.managers.ZoneManager; +import org.l2jmobius.gameserver.mechanics.effects.EffectType; +import org.l2jmobius.gameserver.mechanics.events.EventDispatcher; +import org.l2jmobius.gameserver.mechanics.events.EventType; +import org.l2jmobius.gameserver.mechanics.events.holders.actor.npc.attackable.OnAttackableFactionCall; +import org.l2jmobius.gameserver.mechanics.events.holders.actor.npc.attackable.OnAttackableHate; +import org.l2jmobius.gameserver.mechanics.events.returns.TerminateReturn; +import org.l2jmobius.gameserver.mechanics.skill.AbnormalType; +import org.l2jmobius.gameserver.mechanics.skill.AbnormalVisualEffect; +import org.l2jmobius.gameserver.mechanics.skill.Skill; +import org.l2jmobius.gameserver.mechanics.skill.holders.SkillHolder; +import org.l2jmobius.gameserver.mechanics.skill.targets.TargetType; +import org.l2jmobius.gameserver.taskmanagers.AttackableThinkTaskManager; +import org.l2jmobius.gameserver.taskmanagers.GameTimeTaskManager; +import org.l2jmobius.gameserver.util.LocationUtil; +import org.l2jmobius.gameserver.util.StatSet; + +/** + * This class manages AI of Attackable. + */ +public class AttackableAI extends CreatureAI +{ + protected static final int FEAR_TICKS = 5; + private static final int RANDOM_WALK_RATE = 30; // confirmed + private static final int MAX_ATTACK_TIMEOUT = 1200; // int ticks, i.e. 2min + private static final int WANDER_ATTEMPTS = 3; + private static final int SKILL_PROBABILITY_SCALE = 10000; + private static final int MAX_PARAMETERIZED_SKILL_SLOTS = 6; + private static final int MINION_LEASH_INTERVAL = 120000; // Retail raid minion territory check runs every 120 seconds. + private static final int MINION_LEASH_OFFSET = 100; // Retail raid minion is placed on a random point around its master. + + /** The delay after which the attacked is stopped. */ + private int _attackTimeout; + /** The Attackable aggro counter. */ + private int _globalAggro; + /** The flag used to indicate that a thinking action is in progress, to prevent recursive thinking. */ + private boolean _thinking; + private int _chaosTime = 0; + /** The time of the next raid minion territory check. */ + private long _minionLeashTime; + + // Fear parameters. + private int _fearTime; + private Future _fearTask = null; + + public AttackableAI(Attackable attackable) + { + super(attackable); + _attackTimeout = Integer.MAX_VALUE; + rollGlobalAggro(); + } + + /** + * @param target The targeted WorldObject + * @return {@code true} if target can be auto attacked due aggression. + */ + private boolean isAggressiveTowards(Creature target) + { + if ((target == null) || (getActiveChar() == null)) + { + return false; + } + + // Check if the target isn't invulnerable. + if (target.isInvul()) + { + // However EffectInvincible requires to check GMs specially. + if (target.isPlayer() && target.isGM()) + { + return false; + } + + if (target.isSummon() && target.asSummon().getOwner().isGM()) + { + return false; + } + } + + // Check if the target isn't a Folk or a Door. + if (target.isDoor()) + { + return false; + } + + // Check if the target isn't dead, is in the Aggro range and is at the same height. + final Attackable me = getActiveChar(); + if (target.isAlikeDead() || (target.isPlayable() && !me.isInsideRadius3D(target, me.getAggroRange()))) + { + return false; + } + + // Check if the target is a Playable and if the AI isn't a Raid Boss, can see Silent Moving players and the target isn't in silent move mode. + if (target.isPlayable() && !(me.isRaid()) && !(me.canSeeThroughSilentMove()) && target.asPlayable().isSilentMovingAffected()) + { + return false; + } + + // Gets the player if there is any. + final Player player = target.asPlayer(); + if (player != null) + { + // Don't take the aggro if the GM has the access level below or equal to GM_DONT_TAKE_AGGRO. + if (!player.getAccessLevel().canTakeAggro()) + { + return false; + } + + // Check if the target is within the grace period for JUST getting up from fake death. + if (player.isRecentFakeDeath()) + { + return false; + } + + if (FactionSystemConfig.FACTION_SYSTEM_ENABLED && FactionSystemConfig.FACTION_GUARDS_ENABLED && ((player.isGood() && _actor.asNpc().getTemplate().isClan(FactionSystemConfig.FACTION_EVIL_TEAM_NAME)) || (player.isEvil() && _actor.asNpc().getTemplate().isClan(FactionSystemConfig.FACTION_GOOD_TEAM_NAME)))) + { + return true; + } + + // Dimensional Rift check. + if ((me instanceof RiftInvader) && player.isInParty()) + { + final Party party = player.getParty(); + if (party.isInDimensionalRift()) + { + final byte riftType = party.getDimensionalRift().getType(); + final byte riftRoom = party.getDimensionalRift().getCurrentRoom(); + if (!DimensionalRiftManager.getInstance().getRoom(riftType, riftRoom).checkIfInZone(me.getX(), me.getY(), me.getZ())) + { + return false; + } + } + } + } + + // Check if the actor is a GuardInstance. + if (me instanceof Guard) + { + // Check if the Player target has karma (=PK). + if ((player != null) && (player.getKarma() > 0)) + { + return GeoEngine.getInstance().canSeeTarget(me, player); // Los Check + } + + // Check if the Monster target is aggressive. + if (target.isMonster() && NpcConfig.GUARD_ATTACK_AGGRO_MOB) + { + return (target.asMonster().isAggressive() && GeoEngine.getInstance().canSeeTarget(me, target)); + } + + return false; + } + else if (me instanceof FriendlyMob) + { + // Check if the target isn't another Npc. + if (target instanceof Npc) + { + return false; + } + + // Check if the Player target has karma (=PK). + if (target.isPlayer() && (target.asPlayer().getKarma() > 0)) + { + return GeoEngine.getInstance().canSeeTarget(me, target); // Los Check + } + + return false; + } + else + { + if (target.isAttackable()) + { + if (!target.isAutoAttackable(me)) + { + return false; + } + + if (me.isChaos() && me.isInsideRadius2D(target, me.getAggroRange())) + { + if (target.asAttackable().isInMyClan(me)) + { + return false; + } + + // Los Check + return GeoEngine.getInstance().canSeeTarget(me, target); + } + } + + if (target.isAttackable() || (target instanceof Npc)) + { + return false; + } + + // depending on config, do not allow mobs to attack _new_ players in peacezones, + // unless they are already following those players from outside the peacezone. + if (!NpcConfig.ALT_MOB_AGRO_IN_PEACEZONE && target.isInsideZone(ZoneId.PEACE) && target.isInsideZone(ZoneId.NO_PVP)) + { + return false; + } + + if (me.isChampion() && ChampionMonstersConfig.CHAMPION_PASSIVE) + { + return false; + } + + // Check if the actor is Aggressive. + return me.isAggressive() && GeoEngine.getInstance().canSeeTarget(me, target); + } + } + + public void startAITask() + { + AttackableThinkTaskManager.getInstance().add(getActiveChar()); + } + + @Override + public void stopAITask() + { + AttackableThinkTaskManager.getInstance().remove(getActiveChar()); + super.stopAITask(); + } + + /** + * Helper: apply IDLE/ACTIVE conversion based on whether nearby players or spawn distance warrant ACTIVE mode.
    + * When the result is {@code false} (i.e. truly IDLE), the AI task is stopped and the actor detached. + * @return {@code true} if the intention should be promoted to ACTIVE; {@code false} otherwise. + */ + private boolean shouldPromoteIdleToActive() + { + final Attackable npc = getActiveChar(); + if (npc.isAlikeDead()) + { + return false; + } + + if ((World.getFirstVisibleObject(npc, Player.class) != null)) + { + return true; + } + + if ((npc.getSpawn() != null) && ((npc.getSpawn().getSpawnTerritory() != null) ? !npc.getSpawn().getSpawnTerritory().isInsideZone(npc.getX(), npc.getY()) : !npc.isInsideRadius3D(npc.getSpawn().getLocation(), NpcConfig.MAX_DRIFT_RANGE + NpcConfig.MAX_DRIFT_RANGE))) + { + return true; + } + + return false; + } + + @Override + public synchronized void setIntentionIdle() + { + if (shouldPromoteIdleToActive()) + { + setIntentionActive(); + return; + } + + super.setIntentionIdle(); + + // Stop AI task and detach AI from NPC. + stopAITask(); + + // Cancel the AI + _actor.detachAI(); + } + + @Override + public synchronized void setIntentionActive() + { + // Cancel attack timeout. + _attackTimeout = Integer.MAX_VALUE; + super.setIntentionActive(); + startAITask(); + } + + @Override + public synchronized void setIntentionRest() + { + super.setIntentionRest(); + startAITask(); + } + + /** + * Manage the Attack Intention : Stop current Attack (if necessary), Calculate attack timeout, Start a new Attack and Launch Think Action. + * @param target The Creature to attack + */ + @Override + public synchronized void setIntentionAttack(WorldObject target) + { + // Calculate the attack timeout. + _attackTimeout = MAX_ATTACK_TIMEOUT + GameTimeTaskManager.getInstance().getGameTicks(); + + // Manage the Attack Intention : Stop current Attack (if necessary), Start a new Attack and Launch Think Action. + super.setIntentionAttack(target); + startAITask(); + } + + @Override + public synchronized void setIntentionCast(Skill skill, WorldObject target) + { + if (target != null) + { + setTarget(target); + } + super.setIntentionCast(skill, target); + startAITask(); + } + + @Override + public synchronized void setIntentionMoveTo(ILocational destination) + { + super.setIntentionMoveTo(destination); + startAITask(); + } + + @Override + public synchronized void setIntentionFollow(WorldObject target) + { + super.setIntentionFollow(target); + startAITask(); + } + + @Override + public synchronized void setIntentionPickUp(WorldObject item) + { + super.setIntentionPickUp(item); + startAITask(); + } + + @Override + public synchronized void setIntentionInteract(WorldObject object) + { + super.setIntentionInteract(object); + startAITask(); + } + + @Override + public void notifyActionAfraid(WorldObject effector, boolean start) + { + if ((_fearTime > 0) && (_fearTask == null)) + { + _fearTask = ThreadPool.scheduleAtFixedRate(() -> + { + if (effector != null) + { + final int fearTimeLeft = getFearTime() - FEAR_TICKS; + setFearTime(fearTimeLeft); + notifyActionAfraid(effector, start); + } + }, 0, FEAR_TICKS * 1000); + + _actor.startAbnormalVisualEffect(true, AbnormalVisualEffect.TURN_FLEE); + } + else + { + super.notifyActionAfraid(effector, start); + + if ((_actor.isDead() || (_fearTime <= 0)) && (_fearTask != null)) + { + _fearTask.cancel(true); + _fearTask = null; + _actor.stopAbnormalVisualEffect(true, AbnormalVisualEffect.TURN_FLEE); + setIntentionIdle(); + } + } + } + + protected void thinkCast() + { + if (checkTargetLost(getCastTarget())) + { + setCastTarget(null); + return; + } + + if (maybeMoveToPawn(getCastTarget(), _actor.getMagicalAttackRange(_skill))) + { + return; + } + + clientStopMoving(null); + setIntentionActive(); + _actor.doCast(_skill); + } + + /** + * Manage AI standard thinks of a Attackable (called by onActionThink). Actions: + *
      + *
    • Update every 1s the _globalAggro counter to come close to 0
    • + *
    • If the actor is Aggressive and can attack, add all autoAttackable Creature in its Aggro Range to its _aggroList, chose a target and order to attack it
    • + *
    • If the actor is a GuardInstance that can't attack, order to it to return to its home location
    • + *
    • If the actor is a Monster that can't attack, order to it to random walk (1/100)
    • + *
    + */ + protected void thinkActive() + { + // Check if region and its neighbors are active. + final WorldRegion region = _actor.getWorldRegion(); + if ((region == null) || !region.areNeighborsActive()) + { + return; + } + + final Attackable npc = getActiveChar(); + + // Update every 1s the _globalAggro counter to come close to 0. + if (_globalAggro != 0) + { + if (_globalAggro < 0) + { + _globalAggro++; + } + else + { + _globalAggro--; + } + } + + // Add all autoAttackable Creature in Attackable Aggro Range to its _aggroList with 0 damage and 1 hate. + // An Attackable isn't aggressive during 10s after its spawn because _globalAggro is set to -10. + if (_globalAggro >= 0) + { + World.forEachVisibleObject(npc, Creature.class, target -> + { + if (target instanceof StaticObject) + { + return; + } + + if (npc.isFakePlayer() && npc.isAggressive()) + { + final List droppedItems = npc.getFakePlayerDrops(); + if (droppedItems.isEmpty()) + { + final Creature nearestTarget = World.getNearestVisibleObjectInRange(npc, Creature.class, npc.getAggroRange(), t -> (t != _actor) && !t.isDead() && ((FakePlayersConfig.FAKE_PLAYER_AGGRO_FPC && t.isFakePlayer()) || (FakePlayersConfig.FAKE_PLAYER_AGGRO_MONSTERS && t.isMonster() && !t.isFakePlayer()) || (FakePlayersConfig.FAKE_PLAYER_AGGRO_PLAYERS && t.isPlayer())) && (npc.getHating(t) == 0)); + if (nearestTarget != null) + { + npc.addDamageHate(nearestTarget, 0, 1); + } + } + else if (!npc.isInCombat()) // Must pickup items. + { + final int itemIndex = npc.getFakePlayerDrops().size() - 1; // Last item dropped - can also use 0 for first item dropped. + final Item droppedItem = npc.getFakePlayerDrops().get(itemIndex); + if ((droppedItem != null) && droppedItem.isSpawned()) + { + if (npc.calculateDistance2D(droppedItem) > 50) + { + moveTo(droppedItem); + } + else + { + npc.getFakePlayerDrops().remove(itemIndex); + droppedItem.pickupMe(npc); + if (GeneralConfig.SAVE_DROPPED_ITEM) + { + ItemsOnGroundManager.getInstance().removeObject(droppedItem); + } + + if (droppedItem.getTemplate().hasExImmediateEffect()) + { + for (SkillHolder skillHolder : droppedItem.getTemplate().getSkills()) + { + npc.doSimultaneousCast(skillHolder.getSkill()); + } + + npc.broadcastInfo(); // TODO: Check if this is necessary. + } + } + } + else + { + npc.getFakePlayerDrops().remove(itemIndex); + } + + npc.setRunning(); + } + return; + } + + /* + * Check to see if this is a festival mob spawn. If it is, then check to see if the aggro trigger is a festival participant...if so, move to attack it. + */ + if ((npc instanceof FestivalMonster) && target.isPlayer()) + { + final Player targetPlayer = target.asPlayer(); + if (!(targetPlayer.isFestivalParticipant())) + { + return; + } + } + + // For each Creature check if the target is autoattackable. + if (isAggressiveTowards(target)) // check aggression + { + if (target.isFakePlayer()) + { + if (!npc.isFakePlayer() || (npc.isFakePlayer() && FakePlayersConfig.FAKE_PLAYER_AGGRO_FPC)) + { + final long hating = npc.getHating(target); + if (hating == 0) + { + npc.addDamageHate(target, 0, 0); + } + } + return; + } + + if (target.isPlayable() && EventDispatcher.getInstance().hasListener(EventType.ON_NPC_HATE, getActiveChar())) + { + final TerminateReturn term = EventDispatcher.getInstance().notifyEvent(new OnAttackableHate(getActiveChar(), target.asPlayer(), target.isSummon()), getActiveChar(), TerminateReturn.class); + if ((term != null) && term.terminate()) + { + return; + } + } + + if (npc.getHating(target) == 0) + { + npc.addDamageHate(target, 0, 0); + } + } + }); + + // Chose a target from its aggroList. + final Creature hated = npc.isConfused() ? getAttackTarget() : npc.getMostHated(); + + // Order to the Attackable to attack the target. + if ((hated != null) && !npc.isCoreAIDisabled()) + { + // Get the hate level of the Attackable against this Creature target contained in _aggroList. + final long aggro = npc.getHating(hated); + if ((aggro + _globalAggro) > 0) + { + // Set the Creature movement type to run and send Server->Client packet ChangeMoveType to all others Player. + if (!npc.isRunning()) + { + npc.setRunning(); + } + + // Set the AI Intention to ATTACK. + setIntentionAttack(hated); + } + + return; + } + } + + // Chance to forget attackers after some time. + if ((npc.getCurrentHp() == npc.getMaxHp()) && (npc.getCurrentMp() == npc.getMaxMp()) && !npc.getAttackByList().isEmpty() && (Rnd.get(500) == 0)) + { + npc.clearAggroList(); + npc.getAttackByList().clear(); + } + + // If this is a festival monster, then it remains in the same location. + // if (npc instanceof FestivalMonster) + // { + // return; + // } + + // Check if the mob should not return to spawn point. + if (!npc.canReturnToSpawnPoint() + /* || npc.isReturningToSpawnPoint() */ ) // Commented because sometimes it stops movement. + { + return; + } + + // Order this attackable to return to its spawn because there's no target to attack. + if (!npc.isWalker() && (npc.getSpawn() != null) && ((npc.getSpawn().getSpawnTerritory() != null) ? !npc.getSpawn().getSpawnTerritory().isInsideZone(npc.getX(), npc.getY()) : (npc.calculateDistance2D(npc.getSpawn()) > NpcConfig.MAX_DRIFT_RANGE)) && ((getTarget() == null) || getTarget().isInvisible() || (getTarget().isPlayer() && !NpcConfig.ATTACKABLES_CAMP_PLAYER_CORPSES && getTarget().asPlayer().isAlikeDead()))) + { + npc.setWalking(); + npc.returnHome(); + return; + } + + // Do not leave dead player. + if ((getTarget() != null) && getTarget().isPlayer() && getTarget().asPlayer().isAlikeDead()) + { + return; + } + + // Minions following leader. + final Creature leader = npc.getLeader(); + if ((leader != null) && !leader.isAlikeDead()) + { + final int offset; + final int minRadius = 30; + if (npc.isRaidMinion()) + { + offset = 500; // For Raids. + } + else + { + offset = 200; // For normal minions. + } + + if (leader.isRunning()) + { + npc.setRunning(); + } + else + { + npc.setWalking(); + } + + if (npc.calculateDistance2D(leader) > offset) + { + int x1 = Rnd.get(minRadius * 2, offset * 2); // x + int y1 = Rnd.get(x1, offset * 2); // distance + y1 = (int) Math.sqrt((y1 * y1) - (x1 * x1)); // y + x1 = x1 > (offset + minRadius) ? (leader.getX() + x1) - offset : (leader.getX() - x1) + minRadius; + y1 = y1 > (offset + minRadius) ? (leader.getY() + y1) - offset : (leader.getY() - y1) + minRadius; + + // Move the actor to Location (x,y,z) server side AND client side by sending Server->Client packet MoveToLocation (broadcast). + moveTo(x1, y1, leader.getZ()); + return; + } + + if (Rnd.get(RANDOM_WALK_RATE) == 0) + { + for (Skill sk : npc.getTemplate().getAISkills(AISkillScope.BUFF)) + { + // Skip buffs that are already active. + if (npc.isAffectedBySkill(sk.getId())) + { + continue; + } + + // Only consider skills if we have enough MP. + if (npc.getCurrentMp() <= sk.getMpConsume()) + { + continue; + } + + // Healing skills: only cast if HP is not full. + if (sk.getAbnormalType() == AbnormalType.LIFE_FORCE_OTHERS) + { + if (npc.getCurrentHp() >= npc.getMaxHp()) + { + continue; // Skip healing if at full HP. + } + } + + npc.setTarget(npc); + npc.doCast(sk); + return; + } + } + } + // Order to the Monster to random walk (1/100). + else if ((npc.getSpawn() != null) && (Rnd.get(RANDOM_WALK_RATE) == 0) && npc.isRandomWalkingEnabled()) + { + for (Skill sk : npc.getTemplate().getAISkills(AISkillScope.BUFF)) + { + // Skip buffs that are already active. + if (npc.isAffectedBySkill(sk.getId())) + { + continue; + } + + // Only consider skills if we have enough MP. + if (npc.getCurrentMp() <= sk.getMpConsume()) + { + continue; + } + + // Healing skills: only cast if HP is not full. + if (sk.getAbnormalType() == AbnormalType.LIFE_FORCE_OTHERS) + { + if (npc.getCurrentHp() >= npc.getMaxHp()) + { + continue; // Skip healing if at full HP. + } + } + + npc.setTarget(npc); + npc.doCast(sk); + return; + } + + // A territory spawn has no fixed home, the Spawn location holds the point rolled for the last NPC of the group. + final NpcSpawnTerritory spawnTerritory = npc.getSpawn().getSpawnTerritory(); + if (spawnTerritory != null) + { + // Near a territory border most offsets fall outside, so give the roll a few attempts before giving up. + for (int i = 0; i < WANDER_ATTEMPTS; i++) + { + final int deltaX = Rnd.get(NpcConfig.MAX_DRIFT_RANGE * 2); // x + int deltaY = Rnd.get(deltaX, NpcConfig.MAX_DRIFT_RANGE * 2); // distance + deltaY = (int) Math.sqrt((deltaY * deltaY) - (deltaX * deltaX)); // y + final int x2 = (deltaX + npc.getX()) - NpcConfig.MAX_DRIFT_RANGE; + final int y2 = (deltaY + npc.getY()) - NpcConfig.MAX_DRIFT_RANGE; + if (!spawnTerritory.isInsideZone(x2, y2)) + { + continue; + } + + // Move the actor to Location (x,y,z) server side AND client side by sending Server->Client packet MoveToLocation (broadcast) + final Location moveLoc = _actor.isFlying() ? new Location(x2, y2, npc.getZ()) : GeoEngine.getInstance().getValidLocation(npc.getX(), npc.getY(), npc.getZ(), x2, y2, npc.getZ(), npc.getInstanceId()); + if (spawnTerritory.isInsideZone(moveLoc.getX(), moveLoc.getY())) + { + moveTo(moveLoc.getX(), moveLoc.getY(), moveLoc.getZ()); + break; + } + } + + return; + } + + int x1 = npc.getSpawn().getX(); + int y1 = npc.getSpawn().getY(); + int z1 = npc.getSpawn().getZ(); + if (npc.isInsideRadius2D(x1, y1, 0, NpcConfig.MAX_DRIFT_RANGE)) + { + final int deltaX = Rnd.get(NpcConfig.MAX_DRIFT_RANGE * 2); // x + int deltaY = Rnd.get(deltaX, NpcConfig.MAX_DRIFT_RANGE * 2); // distance + deltaY = (int) Math.sqrt((deltaY * deltaY) - (deltaX * deltaX)); // y + x1 = (deltaX + x1) - NpcConfig.MAX_DRIFT_RANGE; + y1 = (deltaY + y1) - NpcConfig.MAX_DRIFT_RANGE; + z1 = npc.getZ(); + } + + // Move the actor to Location (x,y,z) server side AND client side by sending Server->Client packet MoveToLocation (broadcast). + final Location moveLoc = _actor.isFlying() ? new Location(x1, y1, z1) : GeoEngine.getInstance().getValidLocation(npc.getX(), npc.getY(), npc.getZ(), x1, y1, z1, npc.getInstanceId()); + if (LocationUtil.calculateDistance(npc.getSpawn(), moveLoc, false, false) <= NpcConfig.MAX_DRIFT_RANGE) + { + moveTo(moveLoc.getX(), moveLoc.getY(), moveLoc.getZ()); + } + } + } + + /** + * Manage AI attack thinks of a Attackable (called by onActionThink).
    + *
    + * Actions: + *
      + *
    • Update the attack timeout if actor is running
    • + *
    • If target is dead or timeout is expired, stop this attack and set the Intention to ACTIVE
    • + *
    • Call all WorldObject of its Faction inside the Faction Range
    • + *
    • Chose a target and order to attack it with magic skill or physical attack
    • + *
    + */ + protected void thinkAttack() + { + final Attackable npc = getActiveChar(); + if ((npc == null) || npc.isCastingNow()) + { + return; + } + + if ((npc.isMonster() && !npc.isWalker() && !(npc instanceof GrandBoss)) && (npc.isRaid() ? NpcConfig.AGGRO_DISTANCE_CHECK_RAIDS : NpcConfig.AGGRO_DISTANCE_CHECK_ENABLED)) + { + final Spawn spawn = npc.getSpawn(); + if ((spawn != null) && (npc.calculateDistance2D(spawn.getLocation()) > (spawn.getChaseRange() > 0 ? Math.max(NpcConfig.MAX_DRIFT_RANGE, spawn.getChaseRange()) : npc.isRaid() ? NpcConfig.AGGRO_DISTANCE_CHECK_RAID_RANGE : NpcConfig.AGGRO_DISTANCE_CHECK_RANGE))) + { + if (NpcConfig.AGGRO_DISTANCE_CHECK_INSTANCES || !npc.isInInstance()) + { + if (NpcConfig.AGGRO_DISTANCE_CHECK_RESTORE_LIFE) + { + npc.setCurrentHp(npc.getMaxHp()); + npc.setCurrentMp(npc.getMaxMp()); + } + + npc.abortAttack(); + npc.clearAggroList(); + npc.getAttackByList().clear(); + if (npc.hasAI()) + { + npc.getAI().setIntentionMoveTo(spawn.getLocation()); + } + else + { + npc.teleToLocation(spawn.getLocation(), true); + } + + // Minions should return as well. + if (_actor.asMonster().hasMinions()) + { + for (Monster minion : _actor.asMonster().getMinionList().getSpawnedMinions()) + { + if (NpcConfig.AGGRO_DISTANCE_CHECK_RESTORE_LIFE) + { + minion.setCurrentHp(minion.getMaxHp()); + minion.setCurrentMp(minion.getMaxMp()); + } + + minion.abortAttack(); + minion.clearAggroList(); + minion.getAttackByList().clear(); + if (minion.hasAI()) + { + minion.getAI().setIntentionMoveTo(spawn.getLocation()); + } + else + { + minion.teleToLocation(spawn.getLocation(), true); + } + } + } + return; + } + } + } + + if (npc.isCoreAIDisabled()) + { + return; + } + + Creature mostHate = npc.getMostHated(); + if (mostHate == null) + { + setIntentionActive(); + return; + } + + if (getAttackTarget() != mostHate) + { + setAttackTarget(mostHate); + } + + if (getTarget() != mostHate) + { + setTarget(mostHate); + } + + // Immobilize condition + if (npc.isMovementDisabled()) + { + movementDisable(); + return; + } + + // Check if target is dead or if timeout is expired to stop this attack. + if (mostHate.isAlikeDead()) + { + // Stop hating this target after the attack timeout or if target is dead. + npc.stopHating(mostHate); + return; + } + + if (_attackTimeout < GameTimeTaskManager.getInstance().getGameTicks()) + { + // Remember the combat state, the intention change below clears it. + final boolean wasInCombat = npc.isInCombat(); + + // Set the AI Intention to ACTIVE. + setIntentionActive(); + + // Lose target and walk to spawn. + setTarget(null); + npc.clearAggroList(); + npc.getAttackByList().clear(); + + if (!_actor.isFakePlayer()) + { + npc.setWalking(); + } + + // Monster teleport to spawn. + if (npc.isMonster() && (npc.getSpawn() != null) && !npc.isInInstance() && (wasInCombat || (World.getFirstVisibleObject(npc, Player.class) == null))) + { + npc.teleToLocation(npc.getSpawn(), false); + } + return; + } + + // Actor should be able to see target. + if (!GeoEngine.getInstance().canSeeTarget(_actor, mostHate)) + { + if (_actor.calculateDistance3D(mostHate) < 6000) + { + moveTo(mostHate); + } + return; + } + + // Initialize data. + final NpcTemplate template = npc.getTemplate(); + final List aiSuicideSkills = template.getAISkills(AISkillScope.SUICIDE); + if (!aiSuicideSkills.isEmpty() && ((int) ((npc.getCurrentHp() / npc.getMaxHp()) * 100) < 30)) + { + final Skill skill = aiSuicideSkills.get(Rnd.get(aiSuicideSkills.size())); + if (LocationUtil.checkIfInRange(skill.getAffectRange(), npc, mostHate, false) && npc.hasSkillChance() && cast(skill)) + { + return; + } + } + + // ------------------------------------------------------ + // In case many mobs are trying to hit from same place, move a bit, circling around the target. + // Note from Gnacik: + // On l2js because of that sometimes mobs don't attack player only running around player without any sense, so decrease chance for now. + final int collision = template.getCollisionRadius(); + final int combinedCollision = collision + mostHate.getTemplate().getCollisionRadius(); + if (!npc.isMovementDisabled() && (Rnd.get(100) <= 3)) + { + final Creature currentTarget = mostHate; + if (World.getFirstVisibleObject(npc, Attackable.class, nearby -> npc.isInsideRadius2D(nearby, collision) && (nearby != currentTarget)) != null) + { + int newX = combinedCollision + Rnd.get(40); + newX = Rnd.nextBoolean() ? mostHate.getX() + newX : mostHate.getX() - newX; + int newY = combinedCollision + Rnd.get(40); + newY = Rnd.nextBoolean() ? mostHate.getY() + newY : mostHate.getY() - newY; + if (!npc.isInsideRadius2D(newX, newY, 0, collision)) + { + final int newZ = npc.getZ() + 30; + + // Mobius: Verify destination. Prevents wall collision issues and fixes monsters not avoiding obstacles. + moveTo(GeoEngine.getInstance().getValidLocation(npc.getX(), npc.getY(), npc.getZ(), newX, newY, newZ, npc.getInstanceId())); + } + return; + } + } + + // Calculate Archer movement. + if ((!npc.isMovementDisabled()) && (npc.getAiType() == AIType.ARCHER) && (Rnd.get(100) < 15)) + { + final double distance = npc.calculateDistance2D(mostHate); + if (distance <= (60 + combinedCollision)) + { + int posX = npc.getX(); + int posY = npc.getY(); + final int posZ = npc.getZ() + 30; + if (mostHate.getX() < posX) + { + posX += 300; + } + else + { + posX -= 300; + } + + if (mostHate.getY() < posY) + { + posY += 300; + } + else + { + posY -= 300; + } + + if (GeoEngine.getInstance().canMoveToTarget(npc.getX(), npc.getY(), npc.getZ(), posX, posY, posZ, npc.getInstanceId())) + { + setIntentionMoveTo(new Location(posX, posY, posZ)); + } + return; + } + } + + // BOSS/Raid Minion Target Reconsider. + if (npc.isRaid() || npc.isRaidMinion()) + { + _chaosTime++; + boolean changeTarget = false; + if ((npc instanceof RaidBoss) && (_chaosTime > NpcConfig.RAID_CHAOS_TIME)) + { + final double multiplier = npc.asMonster().hasMinions() ? 200 : 100; + changeTarget = Rnd.get(100) <= (100 - ((npc.getCurrentHp() * multiplier) / npc.getMaxHp())); + } + else if ((npc instanceof GrandBoss) && (_chaosTime > NpcConfig.GRAND_CHAOS_TIME)) + { + final double chaosRate = 100 - ((npc.getCurrentHp() * 300) / npc.getMaxHp()); + changeTarget = ((chaosRate <= 10) && (Rnd.get(100) <= 10)) || ((chaosRate > 10) && (Rnd.get(100) <= chaosRate)); + } + else if (_chaosTime > NpcConfig.MINION_CHAOS_TIME) + { + changeTarget = Rnd.get(100) <= (100 - ((npc.getCurrentHp() * 200) / npc.getMaxHp())); + } + + if (changeTarget) + { + mostHate = targetReconsider(true); + if (mostHate != null) + { + setAttackTarget(mostHate); + setTarget(mostHate); + _chaosTime = 0; + return; + } + } + } + + if (mostHate == null) + { + mostHate = targetReconsider(false); + if (mostHate == null) + { + return; + } + + setAttackTarget(mostHate); + setTarget(mostHate); + } + + // Cast skills. + if (!npc.isMoving() || (npc.getAiType() == AIType.MAGE)) + { + final List generalSkills = template.getAISkills(AISkillScope.GENERAL); + if (!generalSkills.isEmpty()) + { + // Heal Condition + final List aiHealSkills = template.getAISkills(AISkillScope.HEAL); + if (!aiHealSkills.isEmpty()) + { + if (npc.isMinion()) + { + final Creature leader = npc.getLeader(); + if ((leader != null) && !leader.isDead() && (Rnd.get(100) > ((leader.getCurrentHp() / leader.getMaxHp()) * 100))) + { + for (Skill healSkill : aiHealSkills) + { + if (healSkill.getTargetType() == TargetType.SELF) + { + continue; + } + + if (!checkSkillCastConditions(npc, healSkill)) + { + continue; + } + + if (!LocationUtil.checkIfInRange((healSkill.getCastRange() + collision + leader.getTemplate().getCollisionRadius()), npc, leader, false) && !isParty(healSkill) && !npc.isMovementDisabled()) + { + moveToPawn(leader, healSkill.getCastRange() + collision + leader.getTemplate().getCollisionRadius()); + return; + } + + if (GeoEngine.getInstance().canSeeTarget(npc, leader)) + { + clientStopMoving(null); + + final WorldObject target = npc.getTarget(); + npc.setTarget(leader); + npc.doCast(healSkill); + npc.setTarget(target); + // LOGGER.debug(this + " used heal skill " + healSkill + " on leader " + leader); + return; + } + } + } + } + + double percentage = (npc.getCurrentHp() / npc.getMaxHp()) * 100; + if (Rnd.get(100) < ((100 - percentage) / 3)) + { + for (Skill sk : aiHealSkills) + { + if (!checkSkillCastConditions(npc, sk)) + { + continue; + } + + clientStopMoving(null); + + final WorldObject target = npc.getTarget(); + npc.setTarget(npc); + npc.doCast(sk); + npc.setTarget(target); + // LOGGER.debug(this + " used heal skill " + sk + " on itself"); + return; + } + } + + for (Skill sk : aiHealSkills) + { + if (!checkSkillCastConditions(npc, sk)) + { + continue; + } + + if (sk.getTargetType() == TargetType.ONE) + { + final Attackable healTarget = World.getFirstVisibleObjectInRange(npc, Attackable.class, sk.getCastRange() + collision, obj -> obj.isDead() && obj.isInMyClan(npc) && (Rnd.get(100) < ((100 - ((obj.getCurrentHp() / obj.getMaxHp()) * 100)) / 10)) && GeoEngine.getInstance().canSeeTarget(npc, obj)); + if (healTarget != null) + { + clientStopMoving(null); + + final WorldObject target = npc.getTarget(); + npc.setTarget(healTarget); + npc.doCast(sk); + npc.setTarget(target); + // LOGGER.debug(this + " used heal skill " + sk + " on " + healTarget); + return; + } + } + + if (isParty(sk)) + { + clientStopMoving(null); + npc.doCast(sk); + return; + } + } + } + + // Res Skill Condition + final List aiResSkills = template.getAISkills(AISkillScope.RES); + if (!aiResSkills.isEmpty()) + { + if (npc.isMinion()) + { + final Creature leader = npc.getLeader(); + if ((leader != null) && leader.isDead()) + { + for (Skill sk : aiResSkills) + { + if (sk.getTargetType() == TargetType.SELF) + { + continue; + } + + if (!checkSkillCastConditions(npc, sk)) + { + continue; + } + + if (!LocationUtil.checkIfInRange((sk.getCastRange() + collision + leader.getTemplate().getCollisionRadius()), npc, leader, false) && !isParty(sk) && !npc.isMovementDisabled()) + { + moveToPawn(leader, sk.getCastRange() + collision + leader.getTemplate().getCollisionRadius()); + return; + } + + if (GeoEngine.getInstance().canSeeTarget(npc, leader)) + { + clientStopMoving(null); + + final WorldObject target = npc.getTarget(); + npc.setTarget(leader); + npc.doCast(sk); + npc.setTarget(target); + // LOGGER.debug(this + " used resurrection skill " + sk + " on leader " + leader); + return; + } + } + } + } + + for (Skill sk : aiResSkills) + { + if (!checkSkillCastConditions(npc, sk)) + { + continue; + } + + if (sk.getTargetType() == TargetType.ONE) + { + final Attackable resTarget = World.getFirstVisibleObjectInRange(npc, Attackable.class, sk.getCastRange() + collision, obj -> obj.isDead() && npc.isInMyClan(obj) && (Rnd.get(100) < 10) && GeoEngine.getInstance().canSeeTarget(npc, obj)); + if (resTarget != null) + { + clientStopMoving(null); + + final WorldObject target = npc.getTarget(); + npc.setTarget(resTarget); + npc.doCast(sk); + npc.setTarget(target); + // LOGGER.debug(this + " used heal skill " + sk + " on clan member " + resTarget); + return; + } + } + + if (isParty(sk)) + { + clientStopMoving(null); + + final WorldObject target = npc.getTarget(); + npc.setTarget(npc); + npc.doCast(sk); + npc.setTarget(target); + // LOGGER.debug(this + " used heal skill " + sk + " on party"); + return; + } + } + } + } + + // Long/Short Range skill usage. + final List shortRangeSkills = npc.getShortRangeSkills(); + if (!shortRangeSkills.isEmpty() && npc.hasSkillChance() && (npc.calculateDistance2D(mostHate) <= 150)) + { + final Skill shortRangeSkill = shortRangeSkills.get(Rnd.get(shortRangeSkills.size())); + final int castRange = shortRangeSkill.getCastRange(); + if (((castRange < 1) || (npc.calculateDistance3D(mostHate) < castRange)) && checkSkillCastConditions(npc, shortRangeSkill) && allowsParameterizedCast(npc, shortRangeSkill, mostHate)) + { + clientStopMoving(null); + npc.setTarget(mostHate); + npc.doCast(shortRangeSkill); + // LOGGER.debug(this + " used short range skill " + shortRangeSkill + " on " + npc.getTarget()); + return; + } + } + + final List longRangeSkills = npc.getLongRangeSkills(); + if (!longRangeSkills.isEmpty() && npc.hasSkillChance()) + { + final Skill longRangeSkill = longRangeSkills.get(Rnd.get(longRangeSkills.size())); + final int castRange = longRangeSkill.getCastRange(); + if (((castRange < 1) || (npc.calculateDistance3D(mostHate) < castRange)) && checkSkillCastConditions(npc, longRangeSkill) && allowsParameterizedCast(npc, longRangeSkill, mostHate)) + { + clientStopMoving(null); + npc.setTarget(mostHate); + npc.doCast(longRangeSkill); + // LOGGER.debug(this + " used long range skill " + longRangeSkill + " on " + npc.getTarget()); + return; + } + } + } + + // Check if target is within range or move. + int range = npc.getPhysicalAttackRange() + combinedCollision; + if (npc.isMoving()) + { + range *= 2; + } + + if (npc.getAiType() == AIType.ARCHER) + { + range = 850 + combinedCollision; // Base bow range for NPCs. + } + + if (npc.calculateDistance2D(mostHate) > range) + { + if (checkTarget(mostHate)) + { + moveToPawn(mostHate, range); + return; + } + + mostHate = targetReconsider(false); + if (mostHate == null) + { + return; + } + + setAttackTarget(mostHate); + setTarget(mostHate); + } + + // Attacks target + _actor.doAttack(getAttackTarget()); + } + + private boolean checkTarget(WorldObject target) + { + if (target == null) + { + return false; + } + + final Attackable npc = getActiveChar(); + if (target.isCreature()) + { + if (target.asCreature().isDead()) + { + return false; + } + + if (npc.isMovementDisabled()) + { + if (!npc.isInsideRadius2D(target, npc.getPhysicalAttackRange() + npc.getTemplate().getCollisionRadius() + target.asCreature().getTemplate().getCollisionRadius())) + { + return false; + } + + if (!GeoEngine.getInstance().canSeeTarget(npc, target)) + { + return false; + } + } + + if (!target.isAutoAttackable(npc)) + { + return false; + } + } + + // Fixes monsters not avoiding obstacles. + return true; // GeoEngine.getInstance().canMoveToTarget(npc.getX(), npc.getY(), npc.getZ(), target.getX(), target.getY(), target.getZ(), npc.getInstanceWorld()); + } + + private Creature targetReconsider(boolean randomTarget) + { + final Attackable npc = getActiveChar(); + if (randomTarget) + { + final List result = new ArrayList<>(); + for (AggroInfo aggro : npc.getAggroList().values()) + { + if (checkTarget(aggro.getAttacker())) + { + result.add(aggro.getAttacker()); + } + } + + // If npc is aggressive, add characters within aggro range too. + if (npc.isAggressive()) + { + World.forEachVisibleObjectInRange(npc, Creature.class, npc.getAggroRange(), creature -> + { + if (checkTarget(creature)) + { + result.add(creature); + } + }); + } + + if (!result.isEmpty()) + { + return result.get(Rnd.get(result.size())); + } + } + + long searchValue = Long.MIN_VALUE; + Creature creature = null; + for (AggroInfo aggro : npc.getAggroList().values()) + { + if (checkTarget(aggro.getAttacker()) && (aggro.getHate() > searchValue)) + { + searchValue = aggro.getHate(); + creature = aggro.getAttacker(); + } + } + + if ((creature == null) && npc.isAggressive()) + { + final Creature _match = World.getFirstVisibleObjectInRange(npc, Creature.class, npc.getAggroRange(), nearby -> checkTarget(nearby)); + if (_match != null) + { + return _match; + } + } + + return null; + } + + private boolean cast(Skill sk) + { + if (sk == null) + { + return false; + } + + final Attackable caster = getActiveChar(); + if (!checkSkillCastConditions(caster, sk)) + { + return false; + } + + if (!allowsParameterizedCast(caster, sk, getAttackTarget())) + { + return false; + } + + if ((getAttackTarget() == null) && (caster.getMostHated() != null)) + { + setAttackTarget(caster.getMostHated()); + } + + final Creature attackTarget = getAttackTarget(); + if (attackTarget == null) + { + return false; + } + + final double dist = caster.calculateDistance2D(attackTarget); + double dist2 = dist - attackTarget.getTemplate().getCollisionRadius(); + final double srange = sk.getCastRange() + caster.getTemplate().getCollisionRadius(); + if (attackTarget.isMoving()) + { + dist2 -= 30; + } + + if (sk.isContinuous()) + { + if (!sk.isDebuff()) + { + if (!caster.isAffectedBySkill(sk.getId())) + { + clientStopMoving(null); + caster.setTarget(caster); + caster.doCast(sk); + _actor.setTarget(attackTarget); + return true; + } + + // If actor already have buff, start looking at others same faction mob to cast. + if (sk.getTargetType() == TargetType.SELF) + { + return false; + } + + if (sk.getTargetType() == TargetType.ONE) + { + final Creature target = effectTargetReconsider(sk, true); + if (target != null) + { + clientStopMoving(null); + caster.setTarget(target); + caster.doCast(sk); + caster.setTarget(attackTarget); + return true; + } + } + + if (canParty(sk)) + { + clientStopMoving(null); + caster.setTarget(caster); + caster.doCast(sk); + caster.setTarget(attackTarget); + return true; + } + } + else + { + if (GeoEngine.getInstance().canSeeTarget(caster, attackTarget) && !canAOE(sk) && !attackTarget.isDead() && (dist2 <= srange)) + { + if (!attackTarget.isAffectedBySkill(sk.getId())) + { + clientStopMoving(null); + caster.doCast(sk); + return true; + } + } + else if (canAOE(sk)) + { + if ((sk.getTargetType() == TargetType.AURA) || (sk.getTargetType() == TargetType.BEHIND_AURA) || (sk.getTargetType() == TargetType.FRONT_AURA) || (sk.getTargetType() == TargetType.AURA_CORPSE_MOB)) + { + clientStopMoving(null); + caster.doCast(sk); + return true; + } + + if (((sk.getTargetType() == TargetType.AREA) || (sk.getTargetType() == TargetType.BEHIND_AREA) || (sk.getTargetType() == TargetType.FRONT_AREA)) && GeoEngine.getInstance().canSeeTarget(caster, attackTarget) && !attackTarget.isDead() && (dist2 <= srange)) + { + clientStopMoving(null); + caster.doCast(sk); + return true; + } + } + else if (sk.getTargetType() == TargetType.ONE) + { + final Creature target = effectTargetReconsider(sk, false); + if (target != null) + { + clientStopMoving(null); + caster.doCast(sk); + return true; + } + } + } + } + + if (sk.hasEffectType(EffectType.DISPEL, EffectType.DISPEL_BY_SLOT)) + { + if (sk.getTargetType() == TargetType.ONE) + { + if ((attackTarget.getEffectList().getFirstEffect(EffectType.BUFF) != null) && GeoEngine.getInstance().canSeeTarget(caster, attackTarget) && !attackTarget.isDead() && (dist2 <= srange)) + { + clientStopMoving(null); + caster.doCast(sk); + return true; + } + + final Creature target = effectTargetReconsider(sk, false); + if (target != null) + { + clientStopMoving(null); + caster.setTarget(target); + caster.doCast(sk); + caster.setTarget(attackTarget); + return true; + } + } + else if (canAOE(sk)) + { + if (((sk.getTargetType() == TargetType.AURA) || (sk.getTargetType() == TargetType.BEHIND_AURA) || (sk.getTargetType() == TargetType.FRONT_AURA)) && GeoEngine.getInstance().canSeeTarget(caster, attackTarget)) + { + clientStopMoving(null); + caster.doCast(sk); + return true; + } + else if (((sk.getTargetType() == TargetType.AREA) || (sk.getTargetType() == TargetType.BEHIND_AREA) || (sk.getTargetType() == TargetType.FRONT_AREA)) && GeoEngine.getInstance().canSeeTarget(caster, attackTarget) && !attackTarget.isDead() && (dist2 <= srange)) + { + clientStopMoving(null); + caster.doCast(sk); + return true; + } + } + } + + if (sk.hasEffectType(EffectType.HEAL)) + { + if (caster.isMinion() && (sk.getTargetType() != TargetType.SELF)) + { + final Creature leader = caster.getLeader(); + if ((leader != null) && !leader.isDead() && (Rnd.get(100) > ((leader.getCurrentHp() / leader.getMaxHp()) * 100))) + { + if (!LocationUtil.checkIfInRange((sk.getCastRange() + caster.getTemplate().getCollisionRadius() + leader.getTemplate().getCollisionRadius()), caster, leader, false) && !isParty(sk) && !caster.isMovementDisabled()) + { + moveToPawn(leader, sk.getCastRange() + caster.getTemplate().getCollisionRadius() + leader.getTemplate().getCollisionRadius()); + } + + if (GeoEngine.getInstance().canSeeTarget(caster, leader)) + { + clientStopMoving(null); + caster.setTarget(leader); + caster.doCast(sk); + caster.setTarget(attackTarget); + return true; + } + } + } + + double percentage = (caster.getCurrentHp() / caster.getMaxHp()) * 100; + if (Rnd.get(100) < ((100 - percentage) / 3)) + { + clientStopMoving(null); + caster.setTarget(caster); + caster.doCast(sk); + caster.setTarget(attackTarget); + return true; + } + + if (sk.getTargetType() == TargetType.ONE) + { + final Attackable healTarget = World.getFirstVisibleObjectInRange(caster, Attackable.class, sk.getCastRange() + caster.getTemplate().getCollisionRadius(), obj -> !obj.isDead() && caster.isInMyClan(obj) && (Rnd.get(100) < ((100 - ((obj.getCurrentHp() / obj.getMaxHp()) * 100)) / 10)) && GeoEngine.getInstance().canSeeTarget(caster, obj)); + if (healTarget != null) + { + clientStopMoving(null); + caster.setTarget(healTarget); + caster.doCast(sk); + caster.setTarget(attackTarget); + return true; + } + } + + if (isParty(sk)) + { + if (World.getFirstVisibleObjectInRange(caster, Attackable.class, sk.getAffectRange() + caster.getTemplate().getCollisionRadius(), obj -> obj.isInMyClan(caster) && (obj.getCurrentHp() < obj.getMaxHp()) && (Rnd.get(100) <= 20)) != null) + { + clientStopMoving(null); + caster.setTarget(caster); + caster.doCast(sk); + caster.setTarget(attackTarget); + return true; + } + } + } + + if (sk.hasEffectType(EffectType.PHYSICAL_ATTACK, EffectType.PHYSICAL_ATTACK_HP_LINK, EffectType.MAGICAL_ATTACK, EffectType.DEATH_LINK, EffectType.HP_DRAIN)) + { + if (!canAura(sk)) + { + if (GeoEngine.getInstance().canSeeTarget(caster, attackTarget) && !attackTarget.isDead() && (dist2 <= srange)) + { + clientStopMoving(null); + caster.doCast(sk); + return true; + } + + final Creature target = skillTargetReconsider(sk); + if (target != null) + { + clientStopMoving(null); + caster.setTarget(target); + caster.doCast(sk); + caster.setTarget(attackTarget); + return true; + } + } + else + { + clientStopMoving(null); + caster.doCast(sk); + return true; + } + } + + if (sk.hasEffectType(EffectType.SLEEP)) + { + if (sk.getTargetType() == TargetType.ONE) + { + final double range = caster.getPhysicalAttackRange() + caster.getTemplate().getCollisionRadius() + attackTarget.getTemplate().getCollisionRadius(); + if (!attackTarget.isDead() && (dist2 <= srange) && ((dist2 > range) || attackTarget.isMoving()) && !attackTarget.isAffectedBySkill(sk.getId())) + { + clientStopMoving(null); + caster.doCast(sk); + return true; + } + + final Creature target = effectTargetReconsider(sk, false); + if (target != null) + { + clientStopMoving(null); + caster.doCast(sk); + return true; + } + } + else if (canAOE(sk)) + { + if ((sk.getTargetType() == TargetType.AURA) || (sk.getTargetType() == TargetType.BEHIND_AURA) || (sk.getTargetType() == TargetType.FRONT_AURA)) + { + clientStopMoving(null); + caster.doCast(sk); + return true; + } + + if (((sk.getTargetType() == TargetType.AREA) || (sk.getTargetType() == TargetType.BEHIND_AREA) || (sk.getTargetType() == TargetType.FRONT_AREA)) && GeoEngine.getInstance().canSeeTarget(caster, attackTarget) && !attackTarget.isDead() && (dist2 <= srange)) + { + clientStopMoving(null); + caster.doCast(sk); + return true; + } + } + } + + if (sk.hasEffectType(EffectType.STUN, EffectType.ROOT, EffectType.PARALYZE, EffectType.MUTE, EffectType.FEAR)) + { + if (GeoEngine.getInstance().canSeeTarget(caster, attackTarget) && !canAOE(sk) && (dist2 <= srange)) + { + if (!attackTarget.isAffectedBySkill(sk.getId())) + { + clientStopMoving(null); + caster.doCast(sk); + return true; + } + } + else if (canAOE(sk)) + { + if ((sk.getTargetType() == TargetType.AURA) || (sk.getTargetType() == TargetType.BEHIND_AURA) || (sk.getTargetType() == TargetType.FRONT_AURA)) + { + clientStopMoving(null); + caster.doCast(sk); + return true; + } + + if (((sk.getTargetType() == TargetType.AREA) || (sk.getTargetType() == TargetType.BEHIND_AREA) || (sk.getTargetType() == TargetType.FRONT_AREA)) && GeoEngine.getInstance().canSeeTarget(caster, attackTarget) && !attackTarget.isDead() && (dist2 <= srange)) + { + clientStopMoving(null); + caster.doCast(sk); + return true; + } + } + else if (sk.getTargetType() == TargetType.ONE) + { + final Creature target = effectTargetReconsider(sk, false); + if (target != null) + { + clientStopMoving(null); + caster.doCast(sk); + return true; + } + } + } + + if (sk.hasEffectType(EffectType.DMG_OVER_TIME, EffectType.DMG_OVER_TIME_PERCENT)) + { + if (GeoEngine.getInstance().canSeeTarget(caster, attackTarget) && !canAOE(sk) && !attackTarget.isDead() && (dist2 <= srange)) + { + if (!attackTarget.isAffectedBySkill(sk.getId())) + { + clientStopMoving(null); + caster.doCast(sk); + return true; + } + } + else if (canAOE(sk)) + { + if ((sk.getTargetType() == TargetType.AURA) || (sk.getTargetType() == TargetType.BEHIND_AURA) || (sk.getTargetType() == TargetType.FRONT_AURA) || (sk.getTargetType() == TargetType.AURA_CORPSE_MOB)) + { + clientStopMoving(null); + caster.doCast(sk); + return true; + } + + if (((sk.getTargetType() == TargetType.AREA) || (sk.getTargetType() == TargetType.BEHIND_AREA) || (sk.getTargetType() == TargetType.FRONT_AREA)) && GeoEngine.getInstance().canSeeTarget(caster, attackTarget) && !attackTarget.isDead() && (dist2 <= srange)) + { + clientStopMoving(null); + caster.doCast(sk); + return true; + } + } + else if (sk.getTargetType() == TargetType.ONE) + { + final Creature target = effectTargetReconsider(sk, false); + if (target != null) + { + clientStopMoving(null); + caster.doCast(sk); + return true; + } + } + } + + if (sk.hasEffectType(EffectType.RESURRECTION)) + { + if (!isParty(sk)) + { + if (caster.isMinion() && (sk.getTargetType() != TargetType.SELF)) + { + final Creature leader = caster.getLeader(); + if (leader != null) + { + if (leader.isDead() && !LocationUtil.checkIfInRange((sk.getCastRange() + caster.getTemplate().getCollisionRadius() + leader.getTemplate().getCollisionRadius()), caster, leader, false) && !isParty(sk) && !caster.isMovementDisabled()) + { + moveToPawn(leader, sk.getCastRange() + caster.getTemplate().getCollisionRadius() + leader.getTemplate().getCollisionRadius()); + } + + if (GeoEngine.getInstance().canSeeTarget(caster, leader)) + { + clientStopMoving(null); + caster.setTarget(leader); + caster.doCast(sk); + caster.setTarget(attackTarget); + return true; + } + } + } + + final Attackable resTarget = World.getFirstVisibleObjectInRange(caster, Attackable.class, sk.getCastRange() + caster.getTemplate().getCollisionRadius(), obj -> obj.isDead() && caster.isInMyClan(obj) && (Rnd.get(100) < 10) && GeoEngine.getInstance().canSeeTarget(caster, obj)); + if (resTarget != null) + { + clientStopMoving(null); + caster.setTarget(resTarget); + caster.doCast(sk); + caster.setTarget(attackTarget); + return true; + } + } + else if (isParty(sk)) + { + if (World.getFirstVisibleObjectInRange(caster, Npc.class, sk.getAffectRange() + caster.getTemplate().getCollisionRadius(), obj -> caster.isInMyClan(obj) && (obj.getCurrentHp() < obj.getMaxHp()) && (Rnd.get(100) <= 20)) != null) + { + clientStopMoving(null); + caster.setTarget(caster); + caster.doCast(sk); + caster.setTarget(attackTarget); + return true; + } + } + } + + if (!canAura(sk)) + { + if (GeoEngine.getInstance().canSeeTarget(caster, attackTarget) && !attackTarget.isDead() && (dist2 <= srange)) + { + clientStopMoving(null); + caster.doCast(sk); + return true; + } + + final Creature target = skillTargetReconsider(sk); + if (target != null) + { + clientStopMoving(null); + caster.setTarget(target); + caster.doCast(sk); + caster.setTarget(attackTarget); + return true; + } + } + else + { + clientStopMoving(null); + caster.doCast(sk); + return true; + } + + return false; + } + + private void movementDisable() + { + final Creature target = getAttackTarget(); + if (target == null) + { + return; + } + + final Attackable npc = getActiveChar(); + if (npc.getTarget() == null) + { + npc.setTarget(target); + } + + final double dist = npc.calculateDistance2D(target); + + // TODO(Zoey76): Review this "magic changes". + final int random = Rnd.get(100); + if (!target.isImmobilized() && (random < 15) && tryCast(npc, target, AISkillScope.IMMOBILIZE, dist)) + { + return; + } + + if ((random < 20) && tryCast(npc, target, AISkillScope.COT, dist)) + { + return; + } + + if ((random < 30) && tryCast(npc, target, AISkillScope.DEBUFF, dist)) + { + return; + } + + if ((random < 40) && tryCast(npc, target, AISkillScope.NEGATIVE, dist)) + { + return; + } + + if ((npc.isMovementDisabled() || (npc.getAiType() == AIType.MAGE) || (npc.getAiType() == AIType.HEALER)) && tryCast(npc, target, AISkillScope.ATTACK, dist)) + { + return; + } + + if (tryCast(npc, target, AISkillScope.UNIVERSAL, dist)) + { + return; + } + + // If cannot cast, try to attack. + final int range = npc.getPhysicalAttackRange() + npc.getTemplate().getCollisionRadius() + target.getTemplate().getCollisionRadius(); + if ((dist <= range) && GeoEngine.getInstance().canSeeTarget(npc, target)) + { + _actor.doAttack(target); + return; + } + + // If cannot cast nor attack, find a new target. + targetReconsider(); + } + + private boolean tryCast(Attackable npc, Creature target, AISkillScope aiSkillScope, double dist) + { + for (Skill sk : npc.getTemplate().getAISkills(aiSkillScope)) + { + if (!checkSkillCastConditions(npc, sk) || (((sk.getCastRange() + target.getTemplate().getCollisionRadius()) <= dist) && !canAura(sk))) + { + continue; + } + + if (!GeoEngine.getInstance().canSeeTarget(npc, target)) + { + continue; + } + + if (!allowsParameterizedCast(npc, sk, target)) + { + continue; + } + + clientStopMoving(null); + npc.doCast(sk); + return true; + } + + return false; + } + + /** + * @param caster the caster + * @param skill the skill to check. + * @return {@code true} if the skill is available for casting {@code false} otherwise. + */ + private static boolean checkSkillCastConditions(Attackable caster, Skill skill) + { + if (caster.isCastingNow() && !skill.isSimultaneousCast()) + { + return false; + } + + // Not enough MP. + if (skill.getMpConsume() >= caster.getCurrentMp()) + { + return false; + } + + // Character is in "skill disabled" mode. + if (caster.isSkillDisabled(skill)) + { + return false; + } + + // If is a static skill and magic skill and character is muted or is a physical skill muted and character is physically muted. + if (!skill.isStatic() && ((skill.isMagic() && caster.isMuted()) || caster.isPhysicalMuted())) + { + return false; + } + + return true; + } + + private Creature effectTargetReconsider(Skill sk, boolean positive) + { + if (sk == null) + { + return null; + } + + final Attackable actor = getActiveChar(); + final Creature attackTarget = getAttackTarget(); + if ((actor == null) || (attackTarget == null)) + { + return null; + } + + if (!sk.hasEffectType(EffectType.DISPEL, EffectType.DISPEL_BY_SLOT)) + { + if (!positive) + { + double dist = 0; + double dist2 = 0; + int range = 0; + for (Creature obj : actor.getAttackByList()) + { + if ((obj == null) || obj.isDead() || !GeoEngine.getInstance().canSeeTarget(actor, obj) || (obj == attackTarget)) + { + continue; + } + + try + { + actor.setTarget(attackTarget); + dist = actor.calculateDistance2D(obj); + dist2 = dist - actor.getTemplate().getCollisionRadius(); + range = sk.getCastRange() + actor.getTemplate().getCollisionRadius() + obj.getTemplate().getCollisionRadius(); + if (obj.isMoving()) + { + dist2 -= 70; + } + } + catch (NullPointerException e) + { + continue; + } + + if ((dist2 <= range) && !attackTarget.isAffectedBySkill(sk.getId())) + { + return obj; + } + } + + // ---------------------------------------------------------------------- + // If there is nearby Target with aggro, start going on random target that is attackable. + final Creature found = World.getFirstVisibleObjectInRange(actor, Creature.class, range, obj -> + { + if (obj.isDead() || !GeoEngine.getInstance().canSeeTarget(actor, obj)) + { + return false; + } + + try + { + actor.setTarget(attackTarget); + final double localDist = actor.calculateDistance2D(obj); + final int localRange = sk.getCastRange() + actor.getTemplate().getCollisionRadius() + obj.getTemplate().getCollisionRadius(); + final double localDist2 = obj.isMoving() ? localDist - 70 : localDist; + return (obj.isPlayer() || obj.isSummon()) && (localDist2 <= localRange) && !attackTarget.isAffectedBySkill(sk.getId()); + } + catch (NullPointerException e) + { + return false; + } + }); + + if (found != null) + { + return found; + } + } + else + { + final int range = sk.getCastRange() + actor.getTemplate().getCollisionRadius(); + final Attackable foundClan = World.getFirstVisibleObjectInRange(actor, Attackable.class, range, targets -> + { + if (targets.isDead() || !GeoEngine.getInstance().canSeeTarget(actor, targets) || !targets.isInMyClan(actor)) + { + return false; + } + + try + { + actor.setTarget(attackTarget); + final double localDist = actor.calculateDistance2D(targets); + final int localRange = sk.getCastRange() + actor.getTemplate().getCollisionRadius() + targets.getTemplate().getCollisionRadius(); + final double localDist2Base = localDist - actor.getTemplate().getCollisionRadius(); + final double localDist2 = targets.isMoving() ? localDist2Base - 70 : localDist2Base; + return (localDist2 <= localRange) && !targets.isAffectedBySkill(sk.getId()); + } + catch (NullPointerException e) + { + return false; + } + }); + + if (foundClan != null) + { + return foundClan; + } + } + } + else + { + final int range = sk.getCastRange() + actor.getTemplate().getCollisionRadius() + attackTarget.getTemplate().getCollisionRadius(); + final Creature foundBuff = World.getFirstVisibleObjectInRange(actor, Creature.class, range, obj -> + { + if (obj.isDead() || !GeoEngine.getInstance().canSeeTarget(actor, obj)) + { + return false; + } + + try + { + actor.setTarget(attackTarget); + final double localDist = actor.calculateDistance2D(obj); + final int localRange = sk.getCastRange() + actor.getTemplate().getCollisionRadius() + obj.getTemplate().getCollisionRadius(); + final double localDist2Base = localDist - actor.getTemplate().getCollisionRadius(); + final double localDist2 = obj.isMoving() ? localDist2Base - 70 : localDist2Base; + return (obj.isPlayer() || obj.isSummon()) && (localDist2 <= localRange) && (attackTarget.getEffectList().getFirstEffect(EffectType.BUFF) != null); + } + catch (NullPointerException e) + { + return false; + } + }); + + if (foundBuff != null) + { + return foundBuff; + } + } + + return null; + } + + private Creature skillTargetReconsider(Skill sk) + { + double dist = 0; + double dist2 = 0; + int range = 0; + final Attackable actor = getActiveChar(); + if (actor.getHateList() != null) + { + for (Creature obj : actor.getHateList()) + { + if ((obj == null) || !GeoEngine.getInstance().canSeeTarget(actor, obj) || obj.isDead()) + { + continue; + } + + try + { + actor.setTarget(getAttackTarget()); + dist = actor.calculateDistance2D(obj); + dist2 = dist - actor.getTemplate().getCollisionRadius(); + range = sk.getCastRange() + actor.getTemplate().getCollisionRadius() + getAttackTarget().getTemplate().getCollisionRadius(); + + // if(obj.isMoving()) + // dist2 = dist2 - 40; + } + catch (NullPointerException e) + { + continue; + } + + if (dist2 <= range) + { + return obj; + } + } + } + + if (!(actor instanceof Guard)) + { + final Creature foundWorld = World.getFirstVisibleObject(actor, Creature.class, obj -> + { + try + { + actor.setTarget(getAttackTarget()); + final double localDist = actor.calculateDistance2D(obj); + final int localRange = sk.getCastRange() + actor.getTemplate().getCollisionRadius() + getAttackTarget().getTemplate().getCollisionRadius(); + if (localDist > localRange) + { + return false; + } + } + catch (NullPointerException e) + { + return false; + } + + if (!GeoEngine.getInstance().canSeeTarget(actor, obj)) + { + return false; + } + + if (obj.isPlayer()) + { + return true; + } + + if (obj.isAttackable() && actor.isChaos()) + { + return !obj.asAttackable().isInMyClan(actor); + } + + return obj.isSummon(); + }); + + if (foundWorld != null) + { + return foundWorld; + } + } + + return null; + } + + private void targetReconsider() + { + double dist = 0; + double dist2 = 0; + int range = 0; + final Attackable actor = getActiveChar(); + final Creature mostHate = actor.getMostHated(); + if (actor.getHateList() != null) + { + for (Creature obj : actor.getHateList()) + { + if ((obj == null) || !GeoEngine.getInstance().canSeeTarget(actor, obj) || obj.isDead() || (obj != mostHate) || (obj == actor)) + { + continue; + } + + try + { + dist = actor.calculateDistance2D(obj); + dist2 = dist - actor.getTemplate().getCollisionRadius(); + range = actor.getPhysicalAttackRange() + actor.getTemplate().getCollisionRadius() + obj.getTemplate().getCollisionRadius(); + if (obj.isMoving()) + { + dist2 -= 70; + } + } + catch (NullPointerException e) + { + continue; + } + + if (dist2 <= range) + { + actor.addDamageHate(obj, 0, mostHate != null ? actor.getHating(mostHate) : 2000); + actor.setTarget(obj); + setAttackTarget(obj); + return; + } + } + } + + if (!(actor instanceof Guard)) + { + World.forEachVisibleObject(actor, Creature.class, obj -> + { + if ((obj == null) || !GeoEngine.getInstance().canSeeTarget(actor, obj) || obj.isDead() || (obj != mostHate) || (obj == actor) || (obj == getAttackTarget())) + { + return; + } + + if (obj.isPlayer()) + { + actor.addDamageHate(obj, 0, mostHate != null ? actor.getHating(mostHate) : 2000); + actor.setTarget(obj); + setAttackTarget(obj); + } + else if (obj.isAttackable()) + { + if (actor.isChaos()) + { + if (obj.asAttackable().isInMyClan(actor)) + { + return; + } + + actor.addDamageHate(obj, 0, mostHate != null ? actor.getHating(mostHate) : 2000); + actor.setTarget(obj); + setAttackTarget(obj); + } + } + else if (obj.isSummon()) + { + actor.addDamageHate(obj, 0, mostHate != null ? actor.getHating(mostHate) : 2000); + actor.setTarget(obj); + setAttackTarget(obj); + } + }); + } + } + + /** + * Manage AI thinking actions of a Attackable. + */ + @Override + public void notifyActionThink() + { + // Check if a thinking action is already in progress. + if (_thinking) + { + return; + } + + // Check if region and its neighbors are active. + final WorldRegion region = _actor.getWorldRegion(); + if ((region == null) || !region.areNeighborsActive()) + { + return; + } + + // Check if the actor is all skills disabled. + if (getActiveChar().isAllSkillsDisabled()) + { + return; + } + + // Start thinking action. + _thinking = true; + + try + { + // Send raid minions back to their master when they were dragged out of the master territory. + checkMinionLeash(); + + // Manage AI thinks of a Attackable. + switch (getIntention()) + { + case ACTIVE: + { + thinkActive(); + break; + } + case ATTACK: + { + thinkAttack(); + break; + } + case CAST: + { + thinkCast(); + break; + } + } + } + catch (Exception e) + { + // LOGGER.warning(getClass().getSimpleName() + ": " + getActor().getName() + " - onActionThink() failed!"); + } + finally + { + // Stop thinking action. + _thinking = false; + } + } + + /** + * Sends a raid minion back to its master when it was dragged out of the master territory.
    + * A minion that is outside of the territory, while its master is still alive and while it is standing still, is teleported next to its master and loses all aggro.
    + * The check runs every 120 seconds, the first one 120 seconds after the minion started to think. + */ + private void checkMinionLeash() + { + final Attackable npc = getActiveChar(); + if (!npc.isRaidMinion() || npc.isDead() || npc.isMoving()) + { + return; + } + + final long currentTime = System.currentTimeMillis(); + if (_minionLeashTime == 0) + { + _minionLeashTime = currentTime + MINION_LEASH_INTERVAL; + return; + } + + if (_minionLeashTime > currentTime) + { + return; + } + + _minionLeashTime = currentTime + MINION_LEASH_INTERVAL; + + final Attackable master = npc.getLeader(); + if ((master == null) || master.isDead() || isInsideMasterTerritory(npc, master)) + { + return; + } + + npc.clearAggroList(); + npc.getAttackByList().clear(); + npc.teleToLocation(master, MINION_LEASH_OFFSET); + } + + /** + * @param npc the raid minion. + * @param master the master of the raid minion. + * @return {@code true} if the minion is inside the territory of its master or if no territory could be resolved, {@code false} otherwise. + */ + private boolean isInsideMasterTerritory(Attackable npc, Attackable master) + { + final Spawn masterSpawn = master.getSpawn(); + if (masterSpawn != null) + { + final NpcSpawnTerritory territory = masterSpawn.getSpawnTerritory(); + if (territory != null) + { + return territory.isInsideZone(npc.getX(), npc.getY(), npc.getZ()); + } + } + + final BossZone bossZone = ZoneManager.getInstance().getZone(master, BossZone.class); + if (bossZone != null) + { + return bossZone.isInsideZone(npc.getX(), npc.getY(), npc.getZ()); + } + + return true; + } + + /** + * Launch actions corresponding to the Action Attacked.
    + *
    + * Actions: + *
      + *
    • Init the attack : Calculate the attack timeout, Set the _globalAggro to 0, Add the attacker to the actor _aggroList
    • + *
    • Set the Creature movement type to run and send Server->Client packet ChangeMoveType to all others Player
    • + *
    • Set the Intention to ATTACK
    • + *
    + * @param attackerObj The WorldObject that attacks the actor + */ + @Override + public void notifyActionAttacked(WorldObject attackerObj) + { + if (attackerObj == null) + { + return; + } + + final Creature attacker = attackerObj.asCreature(); + if (attacker == null) + { + return; + } + + final Attackable me = getActiveChar(); + + // Calculate the attack timeout. + _attackTimeout = MAX_ATTACK_TIMEOUT + GameTimeTaskManager.getInstance().getGameTicks(); + + // Set the _globalAggro to 0 to permit attack even just after spawn. + if (_globalAggro < 0) + { + _globalAggro = 0; + } + + // Add the attacker to the _aggroList of the actor if not present. + if (!me.isInAggroList(attacker)) + { + me.addDamageHate(attacker, 0, 1); + } + + // Set the Creature movement type to run and send Server->Client packet ChangeMoveType to all others Player. + if (!me.isRunning()) + { + me.setRunning(); + } + + if (!me.isCoreAIDisabled()) + { + // Set the Intention to ATTACK. + if (getIntention() != Intention.ATTACK) + { + setIntentionAttack(attacker); + } + else if (me.getMostHated() != getAttackTarget()) + { + setIntentionAttack(attacker); + } + } + + if (me.isMonster()) + { + Monster master = me.asMonster(); + if (master.hasMinions()) + { + master.getMinionList().onAssist(me, attacker); + } + + master = master.getLeader(); + if ((master != null) && master.hasMinions()) + { + master.getMinionList().onAssist(me, attacker); + } + } + + // Handle all WorldObject of its Faction inside the Faction Range. + final NpcTemplate template = me.getTemplate(); + final Set clans = template.getClans(); + if ((clans != null) && !clans.isEmpty()) + { + final int collision = template.getCollisionRadius(); + final int factionRange = template.getClanHelpRange() + collision; + + // Go through all WorldObject that belong to its faction. + try + { + // Call friendly npcs for help only if this NPC was attacked by the target creature. + final Creature finalTarget = attacker; + boolean targetExistsInAttackByList = false; + for (Creature reference : me.getAttackByList()) + { + if (reference == finalTarget) + { + targetExistsInAttackByList = true; + break; + } + } + + if (targetExistsInAttackByList) + { + World.forEachVisibleObjectInRange(me, Attackable.class, factionRange, nearby -> + { + // Don't call dead npcs, npcs without ai or npcs which are too far away. + if (nearby.isDead() || !nearby.hasAI() || (Math.abs(finalTarget.getZ() - nearby.getZ()) > 600)) + { + return; + } + + // Don't call npcs spawned within the last 7 seconds. + if ((System.currentTimeMillis() - nearby.getSpawnTime()) < 7000) + { + return; + } + + // Don't call npcs who are already doing some action (e.g. attacking, casting). + if ((nearby.getAI()._intention != Intention.IDLE) && (nearby.getAI()._intention != Intention.ACTIVE)) + { + return; + } + + // Don't call npcs who aren't in the same clan. + final NpcTemplate nearbytemplate = nearby.getTemplate(); + if (!template.isClan(nearbytemplate.getClans()) || (nearbytemplate.hasIgnoreClanNpcIds() && nearbytemplate.getIgnoreClanNpcIds().contains(me.getId()))) + { + return; + } + + if (finalTarget.isPlayable()) + { + // Dimensional Rift check. + if ((me instanceof RiftInvader) && finalTarget.isInParty()) + { + final Party party = finalTarget.getParty(); + if (party.isInDimensionalRift()) + { + final byte riftType = party.getDimensionalRift().getType(); + final byte riftRoom = party.getDimensionalRift().getCurrentRoom(); + if (!DimensionalRiftManager.getInstance().getRoom(riftType, riftRoom).checkIfInZone(me.getX(), me.getY(), me.getZ())) + { + return; + } + } + } + + // By default, when a faction member calls for help, attack the caller's attacker. + if (GeoEngine.getInstance().canSeeTarget(nearby, finalTarget)) + { + nearby.getAI().notifyActionAggression(finalTarget, 1); + } + + if (EventDispatcher.getInstance().hasListener(EventType.ON_ATTACKABLE_FACTION_CALL, nearby)) + { + EventDispatcher.getInstance().notifyEventAsync(new OnAttackableFactionCall(nearby, me, finalTarget.asPlayer(), finalTarget.isSummon()), nearby); + } + } + else if (nearby.getAI()._intention != Intention.ATTACK) + { + if (GeoEngine.getInstance().canSeeTarget(nearby, finalTarget)) + { + nearby.addDamageHate(finalTarget, 0, me.getHating(finalTarget)); + nearby.getAI().setIntentionAttack(finalTarget); + } + } + }); + } + } + catch (NullPointerException e) + { + // LOGGER.warning(getClass().getSimpleName() + ": There has been a problem trying to think the attack!", e); + } + } + + super.notifyActionAttacked(attacker); + } + + /** + * Launch actions corresponding to the Action Aggression.
    + *
    + * Actions: + *
      + *
    • Add the target to the actor _aggroList or update hate if already present
    • + *
    • Set the actor Intention to ATTACK (if actor is GuardInstance check if it isn't too far from its home location)
    • + *
    + * @param targetObj the WorldObject that attacks + * @param aggro The value of hate to add to the actor against the target + */ + @Override + public void notifyActionAggression(WorldObject targetObj, int aggro) + { + final Attackable me = getActiveChar(); + if ((targetObj == null) || me.isDead()) + { + return; + } + + final Creature target = targetObj.asCreature(); + if (target == null) + { + return; + } + + // Add the target to the actor _aggroList or update hate if already present. + me.addDamageHate(target, 0, aggro); + + // Set the actor AI Intention to ATTACK. + if (getIntention() != Intention.ATTACK) + { + // Set the Creature movement type to run and send Server->Client packet ChangeMoveType to all others Player. + if (!me.isRunning()) + { + me.setRunning(); + } + + setIntentionAttack(target); + } + + if (me.isMonster()) + { + Monster master = me.asMonster(); + if (master.hasMinions()) + { + master.getMinionList().onAssist(me, target); + } + + master = master.getLeader(); + if ((master != null) && master.hasMinions()) + { + master.getMinionList().onAssist(me, target); + } + } + } + + /** + * Rolls the timeout of ATTACK that follows a spawn, three to seven seconds by default. + * SetAggressiveTime overrides it per npc: zero acquires on sight, a positive value gates for that many seconds. + */ + public void rollGlobalAggro() + { + final int aggressiveTime = getActiveChar().getTemplate().getParameters().getInt("SetAggressiveTime", -1); + if (aggressiveTime == 0) + { + _globalAggro = 0; + } + else if (aggressiveTime > 0) + { + // The counter is tested on the tick that raises it, so it is set one past the wanted delay. + _globalAggro = -(aggressiveTime + 1); + } + else + { + _globalAggro = -(Rnd.get(5) + 4); + } + } + + public void setGlobalAggro(int value) + { + _globalAggro = value; + } + + public Attackable getActiveChar() + { + return _actor.asAttackable(); + } + + public int getFearTime() + { + return _fearTime; + } + + public void setFearTime(int fearTime) + { + _fearTime = fearTime; + } + + private boolean allowsParameterizedCast(Attackable caster, Skill skill, Creature target) + { + if ((caster == null) || (skill == null)) + { + return true; + } + + final StatSet params = caster.getTemplate() != null ? caster.getTemplate().getParameters() : null; + if ((params == null) || params.isEmpty()) + { + return true; + } + + final int slot = findParameterizedSkillSlot(params, skill.getId()); + if (slot == 0) + { + return true; + } + + return passesParameterizedGates(slot, params, caster, target); + } + + private int findParameterizedSkillSlot(StatSet params, int skillId) + { + for (int slot = 1; slot <= MAX_PARAMETERIZED_SKILL_SLOTS; slot++) + { + final SkillHolder declared = params.getObject("Skill0" + slot + "_ID", SkillHolder.class); + if ((declared != null) && (declared.getSkillId() == skillId)) + { + return slot; + } + } + return 0; + } + + private boolean passesParameterizedGates(int slot, StatSet params, Attackable caster, Creature target) + { + final String prefix = "Skill0" + slot + "_"; + // Retail AI classes spell this parameter three different ways, all present in npcdata. + final int probability = params.getInt(prefix + "Probablity", params.getInt(prefix + "Probability", params.getInt(prefix + "Prob", 0))); + final boolean checksDistance = params.getInt(prefix + "Check_Dist", 0) == 1; + final int highHp = params.getInt(prefix + "HighHP", 0); + + if (probability == 0) + { + final boolean hasAttackHint = (params.getInt(prefix + "AttackSplash", 0) == 1) || (params.getInt(prefix + "MainAttack", 0) == 1) || (params.getInt(prefix + "Target", 0) > 0); + if (!checksDistance && (highHp == 0) && !hasAttackHint) + { + return false; + } + } + else if (Rnd.get(SKILL_PROBABILITY_SCALE) >= probability) + { + return false; + } + + if (checksDistance && (target != null)) + { + final int distMin = params.getInt(prefix + "Dist_Min", 0); + final int distMax = params.getInt(prefix + "Dist_Max", Integer.MAX_VALUE); + final int distance = (int) caster.calculateDistance2D(target); + if ((distance < distMin) || (distance > distMax)) + { + return false; + } + } + + if (highHp > 0) + { + final int hpTarget = params.getInt(prefix + "HPTarget", 0); + final Creature hpSource = (hpTarget == 1) ? caster : target; + if (hpSource == null) + { + return false; + } + final double hpPercent = (hpSource.getCurrentHp() * 100.0) / hpSource.getMaxHp(); + if (hpPercent <= highHp) + { + return false; + } + } + + return true; + } +} \ No newline at end of file diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/ai/BoatAI.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/ai/BoatAI.java new file mode 100644 index 0000000..8627496 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/ai/BoatAI.java @@ -0,0 +1,183 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.ai; + +import org.l2jmobius.gameserver.entity.Location; +import org.l2jmobius.gameserver.entity.WorldObject; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.instance.Boat; +import org.l2jmobius.gameserver.mechanics.skill.Skill; +import org.l2jmobius.gameserver.network.serverpackets.VehicleDeparture; +import org.l2jmobius.gameserver.network.serverpackets.VehicleInfo; +import org.l2jmobius.gameserver.network.serverpackets.VehicleStarted; + +/** + * @author DS, Mobius + */ +public class BoatAI extends CreatureAI +{ + public BoatAI(Boat boat) + { + super(boat); + } + + @Override + protected void moveTo(int x, int y, int z) + { + if (_actor.isMovementDisabled()) + { + return; + } + + if (!_actor.isMoving()) + { + _actor.broadcastPacket(new VehicleStarted(getActor(), 1)); + } + + _actor.moveToLocation(x, y, z, 0); + _actor.broadcastPacket(new VehicleDeparture(getActor())); + } + + @Override + public void clientStopMoving(Location loc) + { + if (_actor.isMoving()) + { + _actor.stopMove(loc); + _actor.broadcastPacket(new VehicleStarted(getActor(), 0)); + _actor.broadcastPacket(new VehicleInfo(getActor())); + return; + } + + if (loc != null) + { + _actor.broadcastPacket(new VehicleStarted(getActor(), 0)); + _actor.broadcastPacket(new VehicleInfo(getActor())); + } + } + + @Override + public void describeStateToPlayer(Player player) + { + if (!_actor.isMoving()) + { + return; + } + + player.sendPacket(new VehicleDeparture(getActor())); + } + + @Override + public void setIntentionAttack(WorldObject target) + { + } + + @Override + public void setIntentionCast(Skill skill, WorldObject target) + { + } + + @Override + public void setIntentionFollow(WorldObject target) + { + } + + @Override + public void setIntentionPickUp(WorldObject item) + { + } + + @Override + public void setIntentionInteract(WorldObject object) + { + } + + @Override + public void notifyActionAttacked(WorldObject attacker) + { + } + + @Override + public void notifyActionAggression(WorldObject target, int aggro) + { + } + + @Override + public void notifyActionStunned() + { + } + + @Override + public void notifyActionSleeping() + { + } + + @Override + public void notifyActionRooted() + { + } + + @Override + public void notifyActionForgetObject(WorldObject object) + { + } + + @Override + public void notifyActionCancel() + { + } + + @Override + public void notifyActionDeath() + { + } + + @Override + public void notifyActionFakeDeath() + { + } + + @Override + public void notifyActionFinishCasting() + { + } + + @Override + protected void clientActionFailed() + { + } + + @Override + public void moveToPawn(WorldObject pawn, int offset) + { + } + + @Override + protected void clientStoppedMoving() + { + } + + @Override + public Boat getActor() + { + return (Boat) _actor; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/ai/CreatureAI.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/ai/CreatureAI.java new file mode 100644 index 0000000..6fcf1ab --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/ai/CreatureAI.java @@ -0,0 +1,1828 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.ai; + +import java.util.ArrayList; +import java.util.List; + +import org.l2jmobius.commons.threads.ThreadPool; +import org.l2jmobius.commons.util.Rnd; +import org.l2jmobius.gameserver.config.GeoEngineConfig; +import org.l2jmobius.gameserver.entity.Location; +import org.l2jmobius.gameserver.entity.World; +import org.l2jmobius.gameserver.entity.WorldObject; +import org.l2jmobius.gameserver.entity.WorldRegion; +import org.l2jmobius.gameserver.entity.actor.Attackable; +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.Npc; +import org.l2jmobius.gameserver.entity.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.entity.item.Weapon; +import org.l2jmobius.gameserver.entity.item.enums.ItemLocation; +import org.l2jmobius.gameserver.entity.item.instance.Item; +import org.l2jmobius.gameserver.entity.item.type.WeaponType; +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.interfaces.ILocational; +import org.l2jmobius.gameserver.managers.WalkingManager; +import org.l2jmobius.gameserver.mechanics.effects.EffectType; +import org.l2jmobius.gameserver.mechanics.events.EventDispatcher; +import org.l2jmobius.gameserver.mechanics.events.EventType; +import org.l2jmobius.gameserver.mechanics.events.holders.actor.npc.OnNpcMoveFinished; +import org.l2jmobius.gameserver.mechanics.skill.Skill; +import org.l2jmobius.gameserver.mechanics.skill.targets.TargetType; +import org.l2jmobius.gameserver.network.serverpackets.AutoAttackStop; +import org.l2jmobius.gameserver.taskmanagers.AttackStanceTaskManager; +import org.l2jmobius.gameserver.taskmanagers.GameTimeTaskManager; +import org.l2jmobius.gameserver.util.LocationUtil; + +/** + * This class manages AI of Creature.
    + * CreatureAI: + *
      + *
    • AttackableAI
    • + *
    • DoorAI
    • + *
    • PlayerAI
    • + *
    • SummonAI
    • + *
    + */ +public class CreatureAI extends AbstractAI +{ + private OnNpcMoveFinished _onNpcMoveFinished = null; + + protected static final int FEAR_RANGE = 500; + + /** + * Constructor of CreatureAI. + * @param creature the creature + */ + public CreatureAI(Creature creature) + { + super(creature); + } + + /** + * Helper used internally to check whether the AI may handle the next action. + * @return {@code true} if the actor is spawned (or teleporting) and currently has an AI bound to it. + */ + private boolean canHandleAction() + { + return (_actor.isSpawned() || _actor.isTeleporting()) && _actor.hasAI(); + } + + @Override + public void notifyActionAttacked(WorldObject attacker) + { + if (!canHandleAction()) + { + return; + } + + clientStartAutoAttack(); + + final NextAction nextAction = getNextAction(); + if ((nextAction != null) && nextAction.isTriggeredBy(Action.ATTACKED)) + { + nextAction.doAction(); + } + } + + /** + * Manage the Idle Intention : Stop Attack, Movement and Stand Up the actor.
    + *
    + * Actions: + *
      + *
    • Set the AI Intention to IDLE
    • + *
    • Init cast and attack target
    • + *
    • Stop the actor auto-attack client side by sending Server->Client packet AutoAttackStop (broadcast)
    • + *
    • Stop the actor movement server side AND client side by sending Server->Client packet StopMove/StopRotation (broadcast)
    • + *
    • Stand up the actor server side AND client side by sending Server->Client packet ChangeWaitType (broadcast)
    • + *
    + */ + @Override + public void setIntentionIdle() + { + stopFollow(); + // Set the AI Intention to IDLE. + _intention = Intention.IDLE; + + // Init cast and attack target. + setCastTarget(null); + setAttackTarget(null); + + // Stop the actor movement server side AND client side by sending Server->Client packet StopMove/StopRotation (broadcast). + clientStopMoving(null); + + // Stop the actor auto-attack client side by sending Server->Client packet AutoAttackStop (broadcast). + clientStopAutoAttack(); + + final NextAction nextAction = getNextAction(); + if ((nextAction != null) && nextAction.isRemovedBy(Intention.IDLE)) + { + setNextAction(null); + } + } + + /** + * Manage the Active Intention : Stop Attack, Movement and Launch Think Action.
    + *
    + * Actions : if the Intention is not already Active + *
      + *
    • Set the AI Intention to ACTIVE
    • + *
    • Init cast and attack target
    • + *
    • Stop the actor auto-attack client side by sending Server->Client packet AutoAttackStop (broadcast)
    • + *
    • Stop the actor movement server side AND client side by sending Server->Client packet StopMove/StopRotation (broadcast)
    • + *
    • Launch the Think Action
    • + *
    + */ + @Override + public void setIntentionActive() + { + stopFollow(); + try + { + // Check if the Intention is not already Active. + if (getIntention() == Intention.ACTIVE) + { + return; + } + + // Set the AI Intention to ACTIVE. + _intention = Intention.ACTIVE; + + // Check if region and its neighbors are active. + final WorldRegion region = _actor.getWorldRegion(); + if ((region == null) || !region.areNeighborsActive()) + { + return; + } + + // Init cast and attack target. + setCastTarget(null); + setAttackTarget(null); + + // Stop the actor movement server side AND client side by sending Server->Client packet StopMove/StopRotation (broadcast). + clientStopMoving(null); + + // Stop the actor auto-attack client side by sending Server->Client packet AutoAttackStop (broadcast). + clientStopAutoAttack(); + + // Launch the Think Action. + notifyActionThink(); + } + finally + { + final NextAction nextAction = getNextAction(); + if ((nextAction != null) && nextAction.isRemovedBy(Intention.ACTIVE)) + { + setNextAction(null); + } + } + } + + /** + * Manage the Rest Intention.
    + *
    + * Actions : + *
      + *
    • Set the AI Intention to IDLE
    • + *
    + */ + @Override + public void setIntentionRest() + { + stopFollow(); + // Set the AI Intention to IDLE. + setIntentionIdle(); + + final NextAction nextAction = getNextAction(); + if ((nextAction != null) && nextAction.isRemovedBy(Intention.REST)) + { + setNextAction(null); + } + } + + /** + * Manage the Attack Intention : Stop current Attack (if necessary), Start a new Attack and Launch Think Action.
    + *
    + * Actions : + *
      + *
    • Stop the actor auto-attack client side by sending Server->Client packet AutoAttackStop (broadcast)
    • + *
    • Set the Intention of this AI to ATTACK
    • + *
    • Set or change the AI attack target
    • + *
    • Start the actor Auto Attack client side by sending Server->Client packet AutoAttackStart (broadcast)
    • + *
    • Launch the Think Action
    • + *
    + *
    + * Overridden in: + *
      + *
    • AttackableAI : Calculate attack timeout
    • + *
    + */ + @Override + public void setIntentionAttack(WorldObject target) + { + if (target == null) + { + clientActionFailed(); + return; + } + + final Creature attackTarget = target.asCreature(); + try + { + if ((attackTarget == null) || (getIntention() == Intention.REST) || _actor.isAllSkillsDisabled() || _actor.isCastingNow() || _actor.isControlBlocked()) + { + // Cancel action client side by sending Server->Client packet ActionFailed to the Player actor. + clientActionFailed(); + return; + } + + // Check if the Intention is already ATTACK. + if (getIntention() == Intention.ATTACK) + { + // Check if the AI already targets the Creature. + if (getAttackTarget() != attackTarget) + { + // Set the AI attack target (change target). + setAttackTarget(attackTarget); + + stopFollow(); + + // Launch the Think Action. + notifyActionThink(); + } + else + { + // A repeated attack on the same target revalidates a move that ended without arriving. + // Sending only ActionFailed leaves the intention waiting on an event that never comes. + stopFollow(); + notifyActionThink(); + } + } + else + { + // Set the Intention of this AbstractAI to ATTACK. + _intention = Intention.ATTACK; + + // Set the AI attack target. + setAttackTarget(attackTarget); + + stopFollow(); + + // Launch the Think Action. + notifyActionThink(); + } + } + finally + { + final NextAction nextAction = getNextAction(); + if ((nextAction != null) && nextAction.isRemovedBy(Intention.ATTACK)) + { + setNextAction(null); + } + } + } + + /** + * Manage the Cast Intention : Stop current Attack, Init the AI in order to cast and Launch Think Action.
    + *
    + * Actions : + *
      + *
    • Set the AI cast target
    • + *
    • Stop the actor auto-attack client side by sending Server->Client packet AutoAttackStop (broadcast)
    • + *
    • Cancel action client side by sending Server->Client packet ActionFailed to the Player actor
    • + *
    • Set the AI skill used by INTENTION_CAST
    • + *
    • Set the Intention of this AI to CAST
    • + *
    • Launch the Think Action
    • + *
    + */ + @Override + public void setIntentionCast(Skill skill, WorldObject target) + { + stopFollow(); + try + { + if ((getIntention() == Intention.REST) && skill.isMagic()) + { + clientActionFailed(); + _actor.setCastingNow(false); + return; + } + + final int gameTime = GameTimeTaskManager.getInstance().getGameTicks(); + final int bowAttackEndTime = _actor.getBowAttackEndTime(); + if (bowAttackEndTime > gameTime) + { + ThreadPool.schedule(() -> + { + if (_actor.isAttackingNow()) + { + _actor.abortAttack(); + } + changeIntentionToCast(skill, target); + }, (bowAttackEndTime - gameTime) * GameTimeTaskManager.MILLIS_IN_TICK); + } + else + { + changeIntentionToCast(skill, target); + } + } + finally + { + final NextAction nextAction = getNextAction(); + if ((nextAction != null) && nextAction.isRemovedBy(Intention.CAST)) + { + setNextAction(null); + } + } + } + + protected void changeIntentionToCast(Skill skill, WorldObject target) + { + // Set the AI cast target. + setCastTarget(target == null ? null : target.asCreature()); + + // Set the AI skill used by INTENTION_CAST. + _skill = skill; + + // Change the Intention of this AbstractAI to CAST. + _intention = Intention.CAST; + + // Launch the Think Action. + notifyActionThink(); + } + + /** + * Manage the Move To Intention : Stop current Attack and Launch a Move to Location Task.
    + *
    + * Actions : + *
      + *
    • Stop the actor auto-attack server side AND client side by sending Server->Client packet AutoAttackStop (broadcast)
    • + *
    • Set the Intention of this AI to MOVE_TO
    • + *
    • Move the actor to Location (x,y,z) server side AND client side by sending Server->Client packet MoveToLocation (broadcast)
    • + *
    + */ + @Override + public void setIntentionMoveTo(ILocational loc) + { + stopFollow(); + try + { + if ((getIntention() == Intention.REST) || _actor.isAllSkillsDisabled() || _actor.isCastingNow()) + { + // Cancel action client side by sending Server->Client packet ActionFailed to the Player actor. + clientActionFailed(); + return; + } + + // Set the Intention of this AbstractAI to MOVE_TO. + _intention = Intention.MOVE_TO; + + // Stop the actor auto-attack client side by sending Server->Client packet AutoAttackStop (broadcast). + clientStopAutoAttack(); + + // Abort the attack of the Creature and send Server->Client ActionFailed packet. + _actor.abortAttack(); + + // Move the actor to Location (x,y,z) server side AND client side by sending Server->Client packet MoveToLocation (broadcast). + moveTo(loc.getX(), loc.getY(), loc.getZ()); + } + finally + { + final NextAction nextAction = getNextAction(); + if ((nextAction != null) && nextAction.isRemovedBy(Intention.MOVE_TO)) + { + setNextAction(null); + } + } + } + + /** + * Manage the Follow Intention : Stop current Attack and Launch a Follow Task.
    + *
    + * Actions : + *
      + *
    • Stop the actor auto-attack server side AND client side by sending Server->Client packet AutoAttackStop (broadcast)
    • + *
    • Set the Intention of this AI to FOLLOW
    • + *
    • Create and Launch an AI Follow Task to execute every 1s
    • + *
    + */ + @Override + public void setIntentionFollow(WorldObject target) + { + if (target == null) + { + clientActionFailed(); + return; + } + + final Creature followTarget = target.asCreature(); + try + { + if ((followTarget == null) || (getIntention() == Intention.REST) || _actor.isAllSkillsDisabled() || _actor.isCastingNow() || _actor.isMovementDisabled() || _actor.isDead() || (_actor == followTarget)) + { + clientActionFailed(); + return; + } + + // Stop the actor auto-attack client side by sending Server->Client packet AutoAttackStop (broadcast). + clientStopAutoAttack(); + + // Set the Intention of this AbstractAI to FOLLOW. + _intention = Intention.FOLLOW; + + // Create and Launch an AI Follow Task to execute every 1s. + startFollow(followTarget); + } + finally + { + final NextAction nextAction = getNextAction(); + if ((nextAction != null) && nextAction.isRemovedBy(Intention.FOLLOW)) + { + setNextAction(null); + } + } + } + + /** + * Manage the PickUp Intention : Set the pick up target and Launch a Move To Pawn Task (offset=20).
    + *
    + * Actions : + *
      + *
    • Set the AI pick up target
    • + *
    • Set the Intention of this AI to PICK_UP
    • + *
    • Move the actor to Pawn server side AND client side by sending Server->Client packet MoveToPawn (broadcast)
    • + *
    + */ + @Override + public void setIntentionPickUp(WorldObject object) + { + stopFollow(); + try + { + if ((object == null) || (getIntention() == Intention.REST) || _actor.isAllSkillsDisabled() || _actor.isCastingNow()) + { + // Cancel action client side by sending Server->Client packet ActionFailed to the Player actor. + clientActionFailed(); + return; + } + + // Stop the actor auto-attack client side by sending Server->Client packet AutoAttackStop (broadcast). + clientStopAutoAttack(); + + if (object.isItem() && (((Item) object).getItemLocation() != ItemLocation.VOID)) + { + return; + } + + // Set the Intention of this AbstractAI to PICK_UP. + _intention = Intention.PICK_UP; + + // Set the AI pick up target. + setTarget(object); + + if ((object.getX() == 0) && (object.getY() == 0)) + { + // LOGGER.warning("Object in coords 0,0 - using a temporary fix"); + object.setXYZ(getActor().getX(), getActor().getY(), getActor().getZ() + 5); + } + + // Move the actor to Pawn server side AND client side by sending Server->Client packet MoveToPawn (broadcast). + moveToPawn(object, 20); + } + finally + { + final NextAction nextAction = getNextAction(); + if ((nextAction != null) && nextAction.isRemovedBy(Intention.PICK_UP)) + { + setNextAction(null); + } + } + } + + /** + * Manage the Interact Intention : Set the interact target and Launch a Move To Pawn Task (offset=60).
    + *
    + * Actions : + *
      + *
    • Stop the actor auto-attack client side by sending Server->Client packet AutoAttackStop (broadcast)
    • + *
    • Set the AI interact target
    • + *
    • Set the Intention of this AI to INTERACT
    • + *
    • Move the actor to Pawn server side AND client side by sending Server->Client packet MoveToPawn (broadcast)
    • + *
    + */ + @Override + public void setIntentionInteract(WorldObject object) + { + stopFollow(); + try + { + if ((object == null) || (getIntention() == Intention.REST) || _actor.isAllSkillsDisabled() || _actor.isCastingNow()) + { + // Cancel action client side by sending Server->Client packet ActionFailed to the Player actor. + clientActionFailed(); + return; + } + + // Stop the actor auto-attack client side by sending Server->Client packet AutoAttackStop (broadcast). + clientStopAutoAttack(); + + if (getIntention() == Intention.INTERACT) + { + return; + } + + // Set the Intention of this AbstractAI to INTERACT. + _intention = Intention.INTERACT; + + // Set the AI interact target. + setTarget(object); + + // Move the actor to Pawn server side AND client side by sending Server->Client packet MoveToPawn (broadcast). + moveToPawn(object, 60); + } + finally + { + final NextAction nextAction = getNextAction(); + if ((nextAction != null) && nextAction.isRemovedBy(Intention.INTERACT)) + { + setNextAction(null); + } + } + } + + /** + * Do nothing. + */ + @Override + public void notifyActionThink() + { + // Do nothing. + } + + /** + * Do nothing. + */ + @Override + public void notifyActionAggression(WorldObject target, int aggro) + { + // Do nothing. + } + + /** + * Launch actions corresponding to the Action Stunned.
    + *
    + * Actions: + *
      + *
    • Stop the actor auto-attack client side by sending Server->Client packet AutoAttackStop (broadcast)
    • + *
    • Stop the actor movement server side AND client side by sending Server->Client packet StopMove/StopRotation (broadcast)
    • + *
    + */ + @Override + public void notifyActionStunned() + { + if (!canHandleAction()) + { + return; + } + + // Stop the actor auto-attack client side by sending Server->Client packet AutoAttackStop (broadcast). + _actor.broadcastPacket(new AutoAttackStop(_actor.getObjectId())); + if (AttackStanceTaskManager.getInstance().hasAttackStanceTask(_actor)) + { + AttackStanceTaskManager.getInstance().removeAttackStanceTask(_actor); + } + + // Stop Server AutoAttack also. + setAutoAttacking(false); + + // Stop the actor movement server side AND client side by sending Server->Client packet StopMove/StopRotation (broadcast). + clientStopMoving(null); + + final NextAction nextAction = getNextAction(); + if ((nextAction != null) && nextAction.isTriggeredBy(Action.STUNNED)) + { + nextAction.doAction(); + } + } + + @Override + public void notifyActionParalyzed() + { + if (!canHandleAction()) + { + return; + } + + // Stop the actor auto-attack client side by sending Server->Client packet AutoAttackStop (broadcast). + _actor.broadcastPacket(new AutoAttackStop(_actor.getObjectId())); + if (AttackStanceTaskManager.getInstance().hasAttackStanceTask(_actor)) + { + AttackStanceTaskManager.getInstance().removeAttackStanceTask(_actor); + } + + // Stop Server AutoAttack also. + setAutoAttacking(false); + + // Stop the actor movement server side AND client side by sending Server->Client packet StopMove/StopRotation (broadcast). + clientStopMoving(null); + + final NextAction nextAction = getNextAction(); + if ((nextAction != null) && nextAction.isTriggeredBy(Action.PARALYZED)) + { + nextAction.doAction(); + } + } + + /** + * Launch actions corresponding to the Action Sleeping.
    + *
    + * Actions: + *
      + *
    • Stop the actor auto-attack client side by sending Server->Client packet AutoAttackStop (broadcast)
    • + *
    • Stop the actor movement server side AND client side by sending Server->Client packet StopMove/StopRotation (broadcast)
    • + *
    + */ + @Override + public void notifyActionSleeping() + { + if (!canHandleAction()) + { + return; + } + + // Stop the actor auto-attack client side by sending Server->Client packet AutoAttackStop (broadcast). + _actor.broadcastPacket(new AutoAttackStop(_actor.getObjectId())); + if (AttackStanceTaskManager.getInstance().hasAttackStanceTask(_actor)) + { + AttackStanceTaskManager.getInstance().removeAttackStanceTask(_actor); + } + + // Stop Server AutoAttack also. + setAutoAttacking(false); + + // Stop the actor movement server side AND client side by sending Server->Client packet StopMove/StopRotation (broadcast). + clientStopMoving(null); + + final NextAction nextAction = getNextAction(); + if ((nextAction != null) && nextAction.isTriggeredBy(Action.SLEEPING)) + { + nextAction.doAction(); + } + } + + /** + * Launch actions corresponding to the Action Rooted.
    + *
    + * Actions: + *
      + *
    • Stop the actor movement server side AND client side by sending Server->Client packet StopMove/StopRotation (broadcast)
    • + *
    + */ + @Override + public void notifyActionRooted() + { + if (!canHandleAction()) + { + return; + } + + // Stop the actor movement server side AND client side by sending Server->Client packet StopMove/StopRotation (broadcast). + clientStopMoving(null); + + final NextAction nextAction = getNextAction(); + if ((nextAction != null) && nextAction.isTriggeredBy(Action.ROOTED)) + { + nextAction.doAction(); + } + } + + /** + * Launch actions corresponding to the Action Confused.
    + *
    + * Actions: + *
      + *
    • Stop the actor movement server side AND client side by sending Server->Client packet StopMove/StopRotation (broadcast)
    • + *
    + */ + @Override + public void notifyActionConfused() + { + if (!canHandleAction()) + { + return; + } + + // Stop the actor movement server side AND client side by sending Server->Client packet StopMove/StopRotation (broadcast). + clientStopMoving(null); + + final NextAction nextAction = getNextAction(); + if ((nextAction != null) && nextAction.isTriggeredBy(Action.CONFUSED)) + { + nextAction.doAction(); + } + } + + /** + * Launch actions corresponding to the Action Muted.
    + *
    + * Actions: + *
      + *
    • Break a cast and send Server->Client ActionFailed packet and a System Message to the Creature
    • + *
    + */ + @Override + public void notifyActionMuted() + { + if (!canHandleAction()) + { + return; + } + + final NextAction nextAction = getNextAction(); + if ((nextAction != null) && nextAction.isTriggeredBy(Action.MUTED)) + { + nextAction.doAction(); + } + } + + /** + * Do nothing. + */ + @Override + public void notifyActionEvaded(WorldObject attacker) + { + // Do nothing. + } + + /** + * Launch actions corresponding to the Action ReadyToAct.
    + *
    + * Actions: + *
      + *
    • Launch actions corresponding to the Action Think
    • + *
    + */ + @Override + public void notifyActionReadyToAct() + { + if (!canHandleAction()) + { + return; + } + + if (!_actor.isCastingNow() && !_actor.isCastingSimultaneouslyNow()) + { + // Launch actions corresponding to the Action Think. + notifyActionThink(); + } + + final NextAction nextAction = getNextAction(); + if ((nextAction != null) && nextAction.isTriggeredBy(Action.READY_TO_ACT)) + { + nextAction.doAction(); + } + } + + /** + * Do nothing. + */ + @Override + public void notifyActionUserCmd(Object arg0, Object arg1) + { + // Do nothing. + } + + /** + * Launch actions corresponding to the Action Arrived.
    + *
    + * Actions: + *
      + *
    • If the Intention was MOVE_TO, set the Intention to ACTIVE
    • + *
    • Launch actions corresponding to the Action Think
    • + *
    + */ + @Override + public void notifyActionArrived() + { + if (!canHandleAction()) + { + return; + } + + try + { + if (_actor.isCastingNow() || _actor.isCastingSimultaneouslyNow()) + { + return; + } + + _actor.revalidateZone(true); + + if (_actor.moveToNextRoutePoint()) + { + return; + } + + clientStoppedMoving(); + + if (_actor.isNpc()) + { + final Npc npc = _actor.asNpc(); + WalkingManager.getInstance().onArrived(npc); // Walking Manager support. + + // Notify to scripts + if (EventDispatcher.getInstance().hasListener(EventType.ON_NPC_MOVE_FINISHED, npc)) + { + if (_onNpcMoveFinished == null) + { + _onNpcMoveFinished = new OnNpcMoveFinished(npc); + } + + EventDispatcher.getInstance().notifyEventAsync(_onNpcMoveFinished, npc); + } + } + + // If the Intention was MOVE_TO, set the Intention to ACTIVE. + if (getIntention() == Intention.MOVE_TO) + { + setIntentionActive(); + } + + // Launch actions corresponding to the Action Think. + notifyActionThink(); + } + finally + { + final NextAction nextAction = getNextAction(); + if ((nextAction != null) && nextAction.isTriggeredBy(Action.ARRIVED)) + { + setNextAction(null); + nextAction.doAction(); + } + } + } + + /** + * Launch actions corresponding to the Action ArrivedRevalidate.
    + *
    + * Actions: + *
      + *
    • Launch actions corresponding to the Action Think
    • + *
    + */ + @Override + public void notifyActionArrivedRevalidate() + { + if (!canHandleAction()) + { + return; + } + + // This is disregarded if the char is not moving any more. + if (_actor.isMoving()) + { + // Launch actions corresponding to the Action Think. + notifyActionThink(); + } + + final NextAction nextAction = getNextAction(); + if ((nextAction != null) && nextAction.isTriggeredBy(Action.ARRIVED_REVALIDATE)) + { + nextAction.doAction(); + } + } + + /** + * Launch actions corresponding to the Action ArrivedBlocked.
    + *
    + * Actions: + *
      + *
    • Stop the actor movement server side AND client side by sending Server->Client packet StopMove/StopRotation (broadcast)
    • + *
    • If the Intention was MOVE_TO, set the Intention to ACTIVE
    • + *
    • Launch actions corresponding to the Action Think
    • + *
    + */ + @Override + public void notifyActionArrivedBlocked(Location location) + { + if (!canHandleAction()) + { + return; + } + + // If the Intention was MOVE_TO, set the Intention to ACTIVE. + if ((getIntention() == Intention.MOVE_TO) || (getIntention() == Intention.CAST)) + { + setIntentionActive(); + } + + // Stop the actor movement server side AND client side by sending Server->Client packet StopMove/StopRotation (broadcast). + clientStopMoving(location); + + // Launch actions corresponding to the Action Think. + notifyActionThink(); + + final NextAction nextAction = getNextAction(); + if ((nextAction != null) && nextAction.isTriggeredBy(Action.ARRIVED_BLOCKED)) + { + nextAction.doAction(); + } + } + + /** + * Launch actions corresponding to the Action ForgetObject.
    + *
    + * Actions: + *
      + *
    • If the object was targeted and the Intention was INTERACT or PICK_UP, set the Intention to ACTIVE
    • + *
    • If the object was targeted to attack, stop the auto-attack, cancel target and set the Intention to ACTIVE
    • + *
    • If the object was targeted to cast, cancel target and set the Intention to ACTIVE
    • + *
    • If the object was targeted to follow, stop the movement, cancel AI Follow Task and set the Intention to ACTIVE
    • + *
    • If the targeted object was the actor , cancel AI target, stop AI Follow Task, stop the movement and set the Intention to IDLE
    • + *
    + */ + @Override + public void notifyActionForgetObject(WorldObject object) + { + if (!canHandleAction()) + { + return; + } + + try + { + _actor.removeSeenCreature(object); + + // If the object was targeted and the Intention was INTERACT or PICK_UP, set the Intention to ACTIVE. + if (getTarget() == object) + { + setTarget(null); + + if ((getIntention() == Intention.INTERACT) || (getIntention() == Intention.PICK_UP)) + { + setIntentionActive(); + } + } + + // Check if the object was targeted to attack. + if (getAttackTarget() == object) + { + // Cancel attack target + setAttackTarget(null); + + // Set the Intention of this AbstractAI to ACTIVE. + if ((object == null) || !object.isCreature() || !object.asCreature().isAlikeDead()) // Fixes stop move from cast target decay. + { + setIntentionActive(); + } + } + + // Check if the object was targeted to cast. + if (getCastTarget() == object) + { + // Cancel cast target + setCastTarget(null); + + // Set the Intention of this AbstractAI to ACTIVE. + if ((object == null) || !object.isCreature() || !object.asCreature().isAlikeDead()) // Fixes stop move from cast target decay. + { + setIntentionActive(); + } + } + + // Check if the object was targeted to follow. + if (getFollowTarget() == object) + { + // Stop the actor movement server side AND client side by sending Server->Client packet StopMove/StopRotation (broadcast). + clientStopMoving(null); + + // Stop an AI Follow Task. + stopFollow(); + + // Set the Intention of this AbstractAI to ACTIVE. + setIntentionActive(); + } + + // Check if the targeted object was the actor. + if (_actor != object) + { + return; + } + + // Cancel AI target + setTarget(null); + setAttackTarget(null); + setCastTarget(null); + + // Stop an AI Follow Task. + stopFollow(); + + // Stop the actor movement server side AND client side by sending Server->Client packet StopMove/StopRotation (broadcast). + clientStopMoving(null); + + // Set the Intention of this AbstractAI to IDLE. + _intention = Intention.IDLE; + } + finally + { + final NextAction nextAction = getNextAction(); + if ((nextAction != null) && nextAction.isTriggeredBy(Action.FORGET_OBJECT)) + { + nextAction.doAction(); + } + } + } + + /** + * Launch actions corresponding to the Action Cancel.
    + *
    + * Actions: + *
      + *
    • Stop an AI Follow Task
    • + *
    • Launch actions corresponding to the Action Think
    • + *
    + */ + @Override + public void notifyActionCancel() + { + if (!canHandleAction()) + { + return; + } + + _actor.abortCast(); + + // Stop an AI Follow Task. + stopFollow(); + + if (!AttackStanceTaskManager.getInstance().hasAttackStanceTask(_actor)) + { + _actor.broadcastPacket(new AutoAttackStop(_actor.getObjectId())); + } + + // Launch actions corresponding to the Action Think. + notifyActionThink(); + + final NextAction nextAction = getNextAction(); + if ((nextAction != null) && nextAction.isTriggeredBy(Action.CANCEL)) + { + nextAction.doAction(); + } + } + + /** + * Launch actions corresponding to the Action Dead.
    + *
    + * Actions: + *
      + *
    • Stop an AI Follow Task
    • + *
    • Kill the actor client side by sending Server->Client packet AutoAttackStop, StopMove/StopRotation, Die (broadcast)
    • + *
    + */ + @Override + public void notifyActionDeath() + { + if (!canHandleAction()) + { + return; + } + + // Stop an AI Tasks. + stopAITask(); + + // Kill the actor client side by sending Server->Client packet AutoAttackStop, StopMove/StopRotation, Die (broadcast). + clientNotifyDead(); + + if (!_actor.isPlayable() && !_actor.isFakePlayer()) + { + _actor.setWalking(); + } + + final NextAction nextAction = getNextAction(); + if ((nextAction != null) && nextAction.isTriggeredBy(Action.DEATH)) + { + nextAction.doAction(); + } + } + + /** + * Launch actions corresponding to the Action Fake Death.
    + *
    + * Actions: + *
      + *
    • Stop an AI Follow Task
    • + *
    + */ + @Override + public void notifyActionFakeDeath() + { + if (!canHandleAction()) + { + return; + } + + // Stop an AI Follow Task. + stopFollow(); + + // Stop the actor movement and send Server->Client packet StopMove/StopRotation (broadcast). + clientStopMoving(null); + + // Init AI + _intention = Intention.IDLE; + setTarget(null); + setCastTarget(null); + setAttackTarget(null); + + final NextAction nextAction = getNextAction(); + if ((nextAction != null) && nextAction.isTriggeredBy(Action.FAKE_DEATH)) + { + nextAction.doAction(); + } + } + + /** + * Do nothing. + */ + @Override + public void notifyActionFinishCasting() + { + if (!canHandleAction()) + { + return; + } + + final NextAction nextAction = getNextAction(); + if ((nextAction != null) && nextAction.isTriggeredBy(Action.FINISH_CASTING)) + { + nextAction.doAction(); + } + } + + @Override + public void notifyActionAfraid(WorldObject effector, boolean start) + { + if (!canHandleAction() || (effector == null)) + { + return; + } + + final Creature effectorCreature = effector.asCreature(); + final double radians = Math.toRadians(start && (effectorCreature != null) ? LocationUtil.calculateAngleFrom(effectorCreature, _actor) : LocationUtil.convertHeadingToDegree(_actor.getHeading())); + final int posX = (int) (_actor.getX() + (FEAR_RANGE * Math.cos(radians))); + final int posY = (int) (_actor.getY() + (FEAR_RANGE * Math.sin(radians))); + final int posZ = _actor.getZ(); + if (!_actor.isPet()) + { + _actor.setRunning(); + } + + // If pathfinding enabled the creature will go to the destination or it will go to the nearest obstacle. + setIntentionMoveTo(GeoEngineConfig.PATHFINDING > 0 ? GeoEngine.getInstance().getValidLocation(_actor.getX(), _actor.getY(), _actor.getZ(), posX, posY, posZ, _actor.getInstanceId()) : new Location(posX, posY, posZ)); + + final NextAction nextAction = getNextAction(); + if ((nextAction != null) && nextAction.isTriggeredBy(Action.AFRAID)) + { + nextAction.doAction(); + } + } + + protected boolean maybeMoveToPosition(ILocational worldPosition, int offset) + { + if (worldPosition == null) + { + // LOGGER.warning("maybeMoveToPosition: worldPosition == NULL!"); + return false; + } + + if (offset < 0) + { + return false; // skill radius -1 + } + + if (!_actor.isInsideRadius2D(worldPosition, offset + _actor.getTemplate().getCollisionRadius())) + { + if (_actor.isMovementDisabled()) + { + return true; + } + + if (!_actor.isRunning() && !(this instanceof PlayerAI) && !(this instanceof SummonAI)) + { + _actor.setRunning(); + } + + stopFollow(); + + int x = _actor.getX(); + int y = _actor.getY(); + + final double dx = worldPosition.getX() - x; + final double dy = worldPosition.getY() - y; + double dist = Math.sqrt((dx * dx) + (dy * dy)); + + final double sin = dy / dist; + final double cos = dx / dist; + dist -= offset - 5; + x += (int) (dist * cos); + y += (int) (dist * sin); + moveTo(x, y, worldPosition.getZ()); + return true; + } + + if (isFollowing()) + { + stopFollow(); + } + + return false; + } + + /** + * Manage the Move to Pawn action in function of the distance and of the Interact area.
    + *
    + * Actions: + *
      + *
    • Get the distance between the current position of the Creature and the target (x,y)
    • + *
    • If the distance > offset+20, move the actor (by running) to Pawn server side AND client side by sending Server->Client packet MoveToPawn (broadcast)
    • + *
    • If the distance <= offset+20, Stop the actor movement server side AND client side by sending Server->Client packet StopMove/StopRotation (broadcast)
    • + *
    + *
    + * Example of use: + *
      + *
    • PLayerAI, SummonAI
    • + *
    + * @param target The targeted WorldObject + * @param offsetValue The Interact area radius + * @return True if a movement must be done + */ + protected boolean maybeMoveToPawn(WorldObject target, int offsetValue) + { + // Get the distance between the current position of the Creature and the target (x,y). + if (target == null) + { + // LOGGER.warning("maybeMoveToPawn: target == NULL!"); + return false; + } + + if (offsetValue < 0) + { + return false; // skill radius -1 + } + + int offsetWithCollision = offsetValue + _actor.getTemplate().getCollisionRadius(); + if (target.isCreature()) + { + offsetWithCollision += target.asCreature().getTemplate().getCollisionRadius(); + } + + if (!_actor.isInsideRadius2D(target, offsetWithCollision)) + { + // Caller should be Playable and thinkAttack/thinkCast/thinkInteract/thinkPickUp. + if (isFollowing()) + { + // Allow larger hit range when the target is moving (check is run only once per second). + if (!_actor.isInsideRadius2D(target, offsetWithCollision + 100)) + { + return true; + } + + stopFollow(); + return false; + } + + if (_actor.isMovementDisabled() || (_actor.getMoveSpeed() <= 0)) + { + // If player is trying attack target but he cannot move to attack target. + // Change his intention to idle. + if (_actor.getAI().getIntention() == Intention.ATTACK) + { + _actor.getAI().setIntentionIdle(); + } + + return true; + } + + // If not running, set the Creature movement type to run and send Server->Client packet ChangeMoveType to all others Player. + if (!_actor.isRunning() && !(this instanceof PlayerAI) && !(this instanceof SummonAI)) + { + _actor.setRunning(); + } + + stopFollow(); + int offset = offsetValue; + if (target.isCreature() && !target.isDoor()) + { + if (target.asCreature().isMoving()) + { + offset -= 100; + } + + if (offset < 5) + { + offset = 5; + } + + startFollow(target.asCreature(), offset); + } + else + { + // Move the actor to Pawn server side AND client side by sending Server->Client packet MoveToPawn (broadcast). + moveToPawn(target, offset); + } + + return true; + } + + if (isFollowing()) + { + stopFollow(); + } + + // Stop the actor movement server side AND client side by sending Server->Client packet StopMove/StopRotation (broadcast). + // clientStopMoving(null); + return false; + } + + /** + * Modify current Intention and actions if the target is lost or dead.
    + *
    + * Actions : If the target is lost or dead + *
      + *
    • Stop the actor auto-attack client side by sending Server->Client packet AutoAttackStop (broadcast)
    • + *
    • Stop the actor movement server side AND client side by sending Server->Client packet StopMove/StopRotation (broadcast)
    • + *
    • Set the Intention of this AbstractAI to ACTIVE
    • + *
    + *
    + * Example of use: + *
      + *
    • PLayerAI, SummonAI
    • + *
    + * @param target The targeted WorldObject + * @return True if the target is lost or dead (false if fakedeath) + */ + protected boolean checkTargetLostOrDead(Creature target) + { + if ((target == null) || target.isDead()) + { + setIntentionActive(); + return true; + } + + return false; + } + + /** + * Modify current Intention and actions if the target is lost.
    + *
    + * Actions : If the target is lost + *
      + *
    • Stop the actor auto-attack client side by sending Server->Client packet AutoAttackStop (broadcast)
    • + *
    • Stop the actor movement server side AND client side by sending Server->Client packet StopMove/StopRotation (broadcast)
    • + *
    • Set the Intention of this AbstractAI to ACTIVE
    • + *
    + *
    + * Example of use: + *
      + *
    • PlayerAI, SummonAI
    • + *
    + * @param target The targeted WorldObject + * @return True if the target is lost + */ + protected boolean checkTargetLost(WorldObject target) + { + if (target == null) + { + setIntentionActive(); + return true; + } + + if (_actor != null) + { + if ((_skill != null) && _skill.hasNegativeEffect() && (_skill.getAffectRange() > 0)) + { + if (_actor.isPlayer() && _actor.isMoving()) + { + if (!GeoEngine.getInstance().canMoveToTarget(_actor, target)) + { + setIntentionActive(); + return true; + } + } + else + { + if (!GeoEngine.getInstance().canSeeTarget(_actor, target)) + { + setIntentionActive(); + return true; + } + } + } + + if (_actor.isSummon()) + { + if (GeoEngine.getInstance().canMoveToTarget(_actor, target)) + { + return false; + } + + setIntentionActive(); + return true; + } + } + + return false; + } + + protected class SelfAnalysis + { + public boolean isMage = false; + public boolean isBalanced; + public boolean isArcher = false; + public boolean isHealer = false; + public boolean isFighter = false; + public boolean cannotMoveOnLand = false; + public List generalSkills = new ArrayList<>(); + public List buffSkills = new ArrayList<>(); + public int lastBuffTick = 0; + public List debuffSkills = new ArrayList<>(); + public int lastDebuffTick = 0; + public List cancelSkills = new ArrayList<>(); + public List healSkills = new ArrayList<>(); + // public List trickSkills = new ArrayList<>(); + public List generalDisablers = new ArrayList<>(); + public List sleepSkills = new ArrayList<>(); + public List rootSkills = new ArrayList<>(); + public List muteSkills = new ArrayList<>(); + public List resurrectSkills = new ArrayList<>(); + public boolean hasHealOrResurrect = false; + public boolean hasLongRangeSkills = false; + public boolean hasLongRangeDamageSkills = false; + public int maxCastRange = 0; + + public SelfAnalysis() + { + } + + public void init() + { + switch (((NpcTemplate) _actor.getTemplate()).getAIType()) + { + case FIGHTER: + { + isFighter = true; + break; + } + case MAGE: + { + isMage = true; + break; + } + case CORPSE: + case BALANCED: + { + isBalanced = true; + break; + } + case ARCHER: + { + isArcher = true; + break; + } + case HEALER: + { + isHealer = true; + break; + } + default: + { + isFighter = true; + break; + } + } + + // Water movement analysis. + if (_actor.isNpc()) + { + switch (_actor.getId()) + { + case 20314: // Great White Shark + case 20849: // Light Worm + { + cannotMoveOnLand = true; + break; + } + default: + { + cannotMoveOnLand = false; + break; + } + } + } + + // Skill analysis. + for (Skill sk : _actor.getAllSkills()) + { + if (sk.isPassive()) + { + continue; + } + + final int castRange = sk.getCastRange(); + boolean hasLongRangeDamageSkill = false; + if (sk.isContinuous()) + { + if (!sk.isDebuff()) + { + buffSkills.add(sk); + } + else + { + debuffSkills.add(sk); + } + continue; + } + + if (sk.hasEffectType(EffectType.DISPEL, EffectType.DISPEL_BY_SLOT)) + { + cancelSkills.add(sk); + } + else if (sk.hasEffectType(EffectType.HEAL)) + { + healSkills.add(sk); + hasHealOrResurrect = true; + } + else if (sk.hasEffectType(EffectType.SLEEP)) + { + sleepSkills.add(sk); + } + else if (sk.hasEffectType(EffectType.STUN, EffectType.PARALYZE)) + { + // Hardcoding petrification until improvements are made to EffectTemplate... petrification is totally different for AI than paralyze. + switch (sk.getId()) + { + case 367: + case 4111: + case 4383: + case 4616: + case 4578: + { + sleepSkills.add(sk); + break; + } + default: + { + generalDisablers.add(sk); + break; + } + } + } + else if (sk.hasEffectType(EffectType.ROOT)) + { + rootSkills.add(sk); + } + else if (sk.hasEffectType(EffectType.FEAR)) + { + debuffSkills.add(sk); + } + else if (sk.hasEffectType(EffectType.MUTE)) + { + muteSkills.add(sk); + } + else if (sk.hasEffectType(EffectType.RESURRECTION)) + { + resurrectSkills.add(sk); + hasHealOrResurrect = true; + } + else + { + generalSkills.add(sk); + hasLongRangeDamageSkill = true; + } + + if (castRange > 150) + { + hasLongRangeSkills = true; + if (hasLongRangeDamageSkill) + { + hasLongRangeDamageSkills = true; + } + } + + if (castRange > maxCastRange) + { + maxCastRange = castRange; + } + } + + // Because of missing skills, some mages/balanced cannot play like mages. + if (!hasLongRangeDamageSkills && isMage) + { + isBalanced = true; + isMage = false; + isFighter = false; + } + + if (!hasLongRangeSkills && (isMage || isBalanced)) + { + isBalanced = false; + isMage = false; + isFighter = true; + } + + if (generalSkills.isEmpty() && isMage) + { + isBalanced = true; + isMage = false; + } + } + } + + protected class TargetAnalysis + { + public Creature creature; + public boolean isMage; + public boolean isBalanced; + public boolean isArcher; + public boolean isFighter; + public boolean isCanceled; + public boolean isSlower; + public boolean isMagicResistant; + + public TargetAnalysis() + { + } + + public void update(Creature target) + { + // Update status once in 4 seconds. + if ((target == creature) && (Rnd.get(100) > 25)) + { + return; + } + + creature = target; + if (target == null) + { + return; + } + + isMage = false; + isBalanced = false; + isArcher = false; + isFighter = false; + isCanceled = false; + if (target.getMAtk(null, null) > (1.5 * target.getPAtk(null))) + { + isMage = true; + } + else if (((target.getPAtk(null) * 0.8) < target.getMAtk(null, null)) || ((target.getMAtk(null, null) * 0.8) > target.getPAtk(null))) + { + isBalanced = true; + } + else + { + final Weapon weapon = target.getActiveWeaponItem(); + if ((weapon != null) && (weapon.getItemType() == WeaponType.BOW)) + { + isArcher = true; + } + else + { + isFighter = true; + } + } + + isSlower = target.getRunSpeed() < (_actor.getRunSpeed() - 3); + isMagicResistant = (target.getMDef(null, null) * 1.2) > _actor.getMAtk(null, null); + if (target.getBuffCount() < 4) + { + isCanceled = true; + } + } + } + + public boolean canAura(Skill sk) + { + if ((sk.getTargetType() == TargetType.AURA) || (sk.getTargetType() == TargetType.BEHIND_AURA) || (sk.getTargetType() == TargetType.FRONT_AURA) || (sk.getTargetType() == TargetType.AURA_CORPSE_MOB)) + { + if (World.getFirstVisibleObjectInRange(_actor, Creature.class, sk.getAffectRange(), target -> target == getAttackTarget()) != null) + { + return true; + } + } + + return false; + } + + public boolean canAOE(Skill sk) + { + if (sk.hasEffectType(EffectType.DISPEL, EffectType.DISPEL_BY_SLOT)) + { + if ((sk.getTargetType() == TargetType.AURA) || (sk.getTargetType() == TargetType.BEHIND_AURA) || (sk.getTargetType() == TargetType.FRONT_AURA) || (sk.getTargetType() == TargetType.AURA_CORPSE_MOB)) + { + final boolean cancast = World.getFirstVisibleObjectInRange(_actor, Creature.class, sk.getAffectRange(), target -> GeoEngine.getInstance().canSeeTarget(_actor, target) && !(target.isAttackable() && !_actor.asNpc().isChaos()) && target.isAffectedBySkill(sk.getId())) == null; + + if (cancast) + { + return true; + } + } + else if ((sk.getTargetType() == TargetType.AREA) || (sk.getTargetType() == TargetType.BEHIND_AREA) || (sk.getTargetType() == TargetType.FRONT_AREA)) + { + boolean cancast = true; + for (Creature target : World.getVisibleObjectsInRange(getAttackTarget(), Creature.class, sk.getAffectRange())) + { + if (!GeoEngine.getInstance().canSeeTarget(_actor, target) || (target == null) || (target.isAttackable() && !_actor.asNpc().isChaos())) + { + continue; + } + + if (!target.getEffectList().isEmpty()) + { + cancast = true; + } + } + + if (cancast) + { + return true; + } + } + } + else if ((sk.getTargetType() == TargetType.AURA) || (sk.getTargetType() == TargetType.BEHIND_AURA) || (sk.getTargetType() == TargetType.FRONT_AURA) || (sk.getTargetType() == TargetType.AURA_CORPSE_MOB)) + { + final boolean cancast = World.getFirstVisibleObjectInRange(_actor, Creature.class, sk.getAffectRange(), target -> GeoEngine.getInstance().canSeeTarget(_actor, target) && !(target.isAttackable() && !_actor.asNpc().isChaos()) && !target.getEffectList().isEmpty()) != null; + + if (cancast) + { + return true; + } + } + else if ((sk.getTargetType() == TargetType.AREA) || (sk.getTargetType() == TargetType.BEHIND_AREA) || (sk.getTargetType() == TargetType.FRONT_AREA)) + { + final boolean cancast = World.getFirstVisibleObjectInRange(getAttackTarget(), Creature.class, sk.getAffectRange(), target -> GeoEngine.getInstance().canSeeTarget(_actor, target) && !(target.isAttackable() && !_actor.asNpc().isChaos()) && target.isAffectedBySkill(sk.getId())) == null; + + if (cancast) + { + return true; + } + } + + return false; + } + + public boolean canParty(Skill sk) + { + if (isParty(sk)) + { + int count = 0; + int ccount = 0; + for (Attackable target : World.getVisibleObjectsInRange(_actor, Attackable.class, sk.getAffectRange())) + { + if (!GeoEngine.getInstance().canSeeTarget(_actor, target)) + { + continue; + } + + if (target.isInMyClan(_actor.asNpc())) + { + count++; + if (target.isAffectedBySkill(sk.getId())) + { + ccount++; + } + } + } + + if (ccount < count) + { + return true; + } + } + + return false; + } + + public boolean isParty(Skill sk) + { + return sk.getTargetType() == TargetType.PARTY; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/ai/DistrustAI.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/ai/DistrustAI.java new file mode 100644 index 0000000..9b40aec --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/ai/DistrustAI.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.ai; + +import org.l2jmobius.gameserver.entity.actor.Attackable; +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.geoengine.GeoEngine; + +/** + * @author Naker + */ +public class DistrustAI extends AttackableAI +{ + private final Creature _forcedTarget; + + public DistrustAI(Attackable actor, Creature forcedTarget) + { + super(actor); + _forcedTarget = forcedTarget; + } + + @Override + public void thinkAttack() + { + if ((_forcedTarget == null) || _forcedTarget.isDead()) + { + _actor.setTarget(null); + setIntentionActive(); + return; + } + + _actor.setTarget(_forcedTarget); + setIntentionAttack(_forcedTarget); + + final int range = _actor.getPhysicalAttackRange() + _forcedTarget.getTemplate().getCollisionRadius(); + if (_actor.calculateDistance3D(_forcedTarget) > range) + { + moveToPawn(_forcedTarget, range); + return; + } + + if (!GeoEngine.getInstance().canSeeTarget(_actor, _forcedTarget)) + { + return; + } + + _actor.doAttack(_forcedTarget); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/ai/DoorAI.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/ai/DoorAI.java new file mode 100644 index 0000000..9cffc91 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/ai/DoorAI.java @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.ai; + +import org.l2jmobius.commons.threads.ThreadPool; +import org.l2jmobius.gameserver.entity.Location; +import org.l2jmobius.gameserver.entity.World; +import org.l2jmobius.gameserver.entity.WorldObject; +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.instance.Defender; +import org.l2jmobius.gameserver.entity.actor.instance.Door; +import org.l2jmobius.gameserver.interfaces.ILocational; +import org.l2jmobius.gameserver.mechanics.skill.Skill; + +/** + * @author mkizub, Mobius + */ +public class DoorAI extends CreatureAI +{ + public DoorAI(Door door) + { + super(door); + } + + @Override + public void setIntentionIdle() + { + } + + @Override + public void setIntentionActive() + { + } + + @Override + public void setIntentionRest() + { + } + + @Override + public void setIntentionAttack(WorldObject target) + { + } + + @Override + public void setIntentionCast(Skill skill, WorldObject target) + { + } + + @Override + public void setIntentionMoveTo(ILocational destination) + { + } + + @Override + public void setIntentionFollow(WorldObject target) + { + } + + @Override + public void setIntentionPickUp(WorldObject item) + { + } + + @Override + public void setIntentionInteract(WorldObject object) + { + } + + @Override + public void notifyActionThink() + { + } + + @Override + public void notifyActionAttacked(WorldObject attacker) + { + if (attacker == null) + { + return; + } + + final Creature attackerCreature = attacker.asCreature(); + if (attackerCreature == null) + { + return; + } + + ThreadPool.execute(() -> World.forEachVisibleObject(_actor.asDoor(), Defender.class, guard -> + { + if (_actor.isInsideRadius3D(guard, guard.getTemplate().getClanHelpRange())) + { + guard.getAI().notifyActionAggression(attackerCreature, 15); + } + })); + } + + @Override + public void notifyActionAggression(WorldObject target, int aggro) + { + } + + @Override + public void notifyActionStunned() + { + } + + @Override + public void notifyActionSleeping() + { + } + + @Override + public void notifyActionRooted() + { + } + + @Override + public void notifyActionReadyToAct() + { + } + + @Override + public void notifyActionUserCmd(Object arg0, Object arg1) + { + } + + @Override + public void notifyActionArrived() + { + } + + @Override + public void notifyActionArrivedRevalidate() + { + } + + @Override + public void notifyActionArrivedBlocked(Location blockedAtLoc) + { + } + + @Override + public void notifyActionForgetObject(WorldObject object) + { + } + + @Override + public void notifyActionCancel() + { + } + + @Override + public void notifyActionDeath() + { + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/ai/Intention.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/ai/Intention.java new file mode 100644 index 0000000..49daf64 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/ai/Intention.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.ai; + +/** + * Enumeration of generic intentions of an NPC/PC, an intention may require several steps to be completed. + */ +public enum Intention +{ + /** Do nothing; disconnect AI if no players are around. */ + IDLE, + + /** Alerted state without a goal: scan targets, random walk, etc. */ + ACTIVE, + + /** Rest (sit until attacked). */ + REST, + + /** Attack target (cast combat magic, go to target, combat). */ + ATTACK, + + /** Cast a spell; may start or stop attacking depending on the spell. */ + CAST, + + /** Move to another location. */ + MOVE_TO, + + /** Follow a target, adjusting movement based on the target's actions. */ + FOLLOW, + + /** Pick up an item (go to item, pick it up, then become idle). */ + PICK_UP, + + /** Move to target and then interact with it. */ + INTERACT; +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/ai/NextAction.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/ai/NextAction.java new file mode 100644 index 0000000..b8cced7 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/ai/NextAction.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.ai; + +/** + * Represents a queued action that can be triggered by a specific {@link Action} and removed by a specific {@link Intention}.
    + * When triggered, it executes a callback provided by the {@link Callback} interface. + * @author Mobius + */ +public class NextAction +{ + /** + * A callback interface that defines the behavior to execute when the next action is triggered. + */ + public interface Callback + { + void doAction(); + } + + private final Action _action; + private final Intention _intention; + private final Callback _callback; + + /** + * Constructs a new NextAction with the specified action, intention and callback. + * @param action The {@link Action} that will trigger this next action. + * @param intention The {@link Intention} that can remove this next action. + * @param callback The {@link Callback} that will be executed when this next action is triggered. + */ + public NextAction(Action action, Intention intention, Callback callback) + { + _action = action; + _intention = intention; + _callback = callback; + } + + /** + * Checks if this next action can be triggered by the specified {@link Action}. + * @param action The {@link Action} to check. + * @return if the provided action matches the action associated with this next action. + */ + public boolean isTriggeredBy(Action action) + { + return _action == action; + } + + /** + * Checks if this next action can be removed by the specified {@link Intention}. + * @param intention The {@link Intention} to check. + * @return if the provided intention matches the intention associated with this next action. + */ + public boolean isRemovedBy(Intention intention) + { + return _intention == intention; + } + + /** + * Executes the next action by invoking the {@link Callback#doAction()} method. + */ + public void doAction() + { + _callback.doAction(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/ai/PlayableAI.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/ai/PlayableAI.java new file mode 100644 index 0000000..0ec2ad7 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/ai/PlayableAI.java @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.ai; + +import org.l2jmobius.gameserver.entity.WorldObject; +import org.l2jmobius.gameserver.entity.actor.Playable; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.zone.ZoneId; +import org.l2jmobius.gameserver.mechanics.skill.Skill; +import org.l2jmobius.gameserver.network.SystemMessageId; + +/** + * This class manages AI of Playable.
    + * PlayableAI: + *
  • SummonAI
  • + *
  • PlayerAI
  • + * @author JIV, Mobius + */ +public abstract class PlayableAI extends CreatureAI +{ + protected PlayableAI(Playable playable) + { + super(playable); + } + + @Override + public void setIntentionAttack(WorldObject target) + { + if ((target != null) && target.isPlayable()) + { + final Player player = _actor.asPlayer(); + final Player targetPlayer = target.asPlayer(); + if ((player != null) && (targetPlayer != null)) + { + if (targetPlayer.isProtectionBlessingAffected() && ((player.getLevel() - targetPlayer.getLevel()) >= 10) && (player.getKarma() > 0) && !(target.isInsideZone(ZoneId.PVP))) + { + // If attacker have karma and have level >= 10 than his target and target have Newbie Protection Buff. + player.sendPacket(SystemMessageId.THAT_IS_THE_INCORRECT_TARGET); + clientActionFailed(); + return; + } + + if (player.isProtectionBlessingAffected() && ((targetPlayer.getLevel() - player.getLevel()) >= 10) && (targetPlayer.getKarma() > 0) && !(target.isInsideZone(ZoneId.PVP))) + { + // If target have karma and have level >= 10 than his target and actor have Newbie Protection Buff. + player.sendPacket(SystemMessageId.THAT_IS_THE_INCORRECT_TARGET); + clientActionFailed(); + return; + } + + if (targetPlayer.isCursedWeaponEquipped() && (player.getLevel() <= 20)) + { + player.sendPacket(SystemMessageId.THAT_IS_THE_INCORRECT_TARGET); + clientActionFailed(); + return; + } + + if (player.isCursedWeaponEquipped() && (targetPlayer.getLevel() <= 20)) + { + player.sendPacket(SystemMessageId.THAT_IS_THE_INCORRECT_TARGET); + clientActionFailed(); + return; + } + } + } + + super.setIntentionAttack(target); + } + + @Override + public void setIntentionCast(Skill skill, WorldObject target) + { + if ((target != null) && (target.isPlayable()) && (skill != null) && skill.hasNegativeEffect()) + { + final Player player = _actor.asPlayer(); + final Player targetPlayer = target.asPlayer(); + if ((player != null) && (targetPlayer != null)) + { + if (targetPlayer.isProtectionBlessingAffected() && ((player.getLevel() - targetPlayer.getLevel()) >= 10) && (player.getKarma() > 0) && !target.isInsideZone(ZoneId.PVP)) + { + // If attacker have karma and have level >= 10 than his target and target have Newbie Protection Buff. + player.sendPacket(SystemMessageId.THAT_IS_THE_INCORRECT_TARGET); + clientActionFailed(); + return; + } + + if (player.isProtectionBlessingAffected() && ((targetPlayer.getLevel() - player.getLevel()) >= 10) && (targetPlayer.getKarma() > 0) && !target.isInsideZone(ZoneId.PVP)) + { + // If target have karma and have level >= 10 than his target and actor have Newbie Protection Buff. + player.sendPacket(SystemMessageId.THAT_IS_THE_INCORRECT_TARGET); + clientActionFailed(); + return; + } + + if (targetPlayer.isCursedWeaponEquipped() && ((player.getLevel() <= 20) || (targetPlayer.getLevel() <= 20))) + { + player.sendPacket(SystemMessageId.THAT_IS_THE_INCORRECT_TARGET); + clientActionFailed(); + return; + } + } + } + + super.setIntentionCast(skill, target); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/ai/PlayerAI.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/ai/PlayerAI.java new file mode 100644 index 0000000..073d246 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/ai/PlayerAI.java @@ -0,0 +1,486 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.ai; + +import org.l2jmobius.gameserver.entity.WorldObject; +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.holders.player.Duel; +import org.l2jmobius.gameserver.entity.actor.instance.StaticObject; +import org.l2jmobius.gameserver.interfaces.ILocational; +import org.l2jmobius.gameserver.mechanics.skill.Skill; +import org.l2jmobius.gameserver.mechanics.skill.targets.TargetType; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; + +public class PlayerAI extends PlayableAI +{ + private boolean _thinking; // To prevent recursive thinking. + + // Saved intention to replay after a CAST completes (typed fields — no IntentionCommand). + private Intention _savedIntention = null; + private WorldObject _savedAttackTarget = null; + private ILocational _savedMoveTo = null; + private WorldObject _savedFollowTarget = null; + private WorldObject _savedPickUpTarget = null; + private WorldObject _savedInteractTarget = null; + private Skill _savedCastSkill = null; + private WorldObject _savedCastTarget = null; + + public PlayerAI(Player player) + { + super(player); + } + + @Override + public Intention getNextIntention() + { + return _savedIntention; + } + + private void clearSavedIntention() + { + _savedIntention = null; + _savedAttackTarget = null; + _savedMoveTo = null; + _savedFollowTarget = null; + _savedPickUpTarget = null; + _savedInteractTarget = null; + _savedCastSkill = null; + _savedCastTarget = null; + } + + /** + * Saves the current intention so it can be replayed once the upcoming CAST resolves. + */ + private void saveCurrentIntentionForCast() + { + _savedIntention = _intention; + _savedAttackTarget = getAttackTarget(); + _savedCastSkill = _skill; + _savedCastTarget = getCastTarget(); + // Other typed targets are restored on replay via the same getters when possible. + _savedMoveTo = null; + _savedFollowTarget = getFollowTarget(); + _savedPickUpTarget = null; + _savedInteractTarget = null; + } + + private void replaySavedIntention() + { + if (_savedIntention == null) + { + return; + } + + final Intention intention = _savedIntention; + final WorldObject attackTarget = _savedAttackTarget; + final ILocational moveToLoc = _savedMoveTo; + final WorldObject followTarget = _savedFollowTarget; + final WorldObject pickUpTarget = _savedPickUpTarget; + final WorldObject interactTarget = _savedInteractTarget; + final Skill castSkill = _savedCastSkill; + final WorldObject castTarget = _savedCastTarget; + clearSavedIntention(); + + switch (intention) + { + case IDLE: + { + setIntentionIdle(); + break; + } + case ACTIVE: + { + setIntentionActive(); + break; + } + case REST: + { + setIntentionRest(); + break; + } + case ATTACK: + { + setIntentionAttack(attackTarget); + break; + } + case CAST: + { + if (castSkill != null) + { + setIntentionCast(castSkill, castTarget); + } + break; + } + case MOVE_TO: + { + if (moveToLoc != null) + { + setIntentionMoveTo(moveToLoc); + } + break; + } + case FOLLOW: + { + setIntentionFollow(followTarget); + break; + } + case PICK_UP: + { + setIntentionPickUp(pickUpTarget); + break; + } + case INTERACT: + { + setIntentionInteract(interactTarget); + break; + } + } + } + + /** + * Launch actions corresponding to the Action ReadyToAct.
    + *
    + * Actions: + *
      + *
    • Launch actions corresponding to the Action Think
    • + *
    + */ + @Override + public void notifyActionReadyToAct() + { + // Replay any saved intention from before a CAST. + if (_savedIntention != null) + { + replaySavedIntention(); + } + + super.notifyActionReadyToAct(); + } + + @Override + public void notifyActionForgetObject(WorldObject object) + { + if ((object != null) && object.isPlayer()) + { + getActor().getKnownRelations().remove(object.getObjectId()); + } + + super.notifyActionForgetObject(object); + } + + /** + * Launch actions corresponding to the Action Cancel.
    + *
    + * Actions: + *
      + *
    • Stop an AI Follow Task
    • + *
    • Launch actions corresponding to the Action Think
    • + *
    + */ + @Override + public void notifyActionCancel() + { + clearSavedIntention(); + super.notifyActionCancel(); + } + + /** + * Finalize the casting of a skill. This method overrides CreatureAI method.
    + * What it does:
    + * Check if actual intention is set to CAST and, if so, retrieves latest intention before the actual CAST and set it as the current intention for the player. + */ + @Override + public void notifyActionFinishCasting() + { + if (getIntention() == Intention.CAST) + { + // Run interrupted or next intention. + if (_savedIntention != null) + { + if (_savedIntention != Intention.CAST) + { + replaySavedIntention(); + } + else + { + clearSavedIntention(); + setIntentionIdle(); + } + } + else + { + // Set intention to idle if skill doesn't change intention. + setIntentionIdle(); + } + } + + super.notifyActionFinishCasting(); + } + + @Override + public void setIntentionRest() + { + if (getIntention() == Intention.REST) + { + return; + } + + clearSavedIntention(); + _intention = Intention.REST; + setTarget(null); + if (getAttackTarget() != null) + { + setAttackTarget(null); + } + + clientStopMoving(null); + } + + @Override + public void setIntentionActive() + { + setIntentionIdle(); + } + + /** + * Manage the Move To Intention : Stop current Attack and Launch a Move to Location Task.
    + *
    + * Actions : + *
      + *
    • Stop the actor auto-attack server side AND client side by sending Server->Client packet AutoAttackStop (broadcast)
    • + *
    • Set the Intention of this AI to MOVE_TO
    • + *
    • Move the actor to Location (x,y,z) server side AND client side by sending Server->Client packet MoveToLocation (broadcast)
    • + *
    + */ + @Override + public void setIntentionMoveTo(ILocational loc) + { + if (getIntention() == Intention.REST) + { + // Cancel action client side by sending Server->Client packet ActionFailed to the Player actor. + clientActionFailed(); + return; + } + + final Player player = _actor.asPlayer(); + if (player.getDuelState() == Duel.DUELSTATE_DEAD) + { + clientActionFailed(); + player.sendPacket(new SystemMessage(SystemMessageId.YOU_CANNOT_MOVE_WHILE_FROZEN_PLEASE_WAIT)); + return; + } + + if (_actor.isAllSkillsDisabled() || _actor.isCastingNow() || _actor.isAttackingNow()) + { + clientActionFailed(); + // Save the move-to as the next intention to replay once ready. + _savedIntention = Intention.MOVE_TO; + _savedMoveTo = loc; + return; + } + + stopFollow(); + + // Set the Intention of this AbstractAI to MOVE_TO. + clearSavedIntention(); + _intention = Intention.MOVE_TO; + + // Stop the actor auto-attack client side by sending Server->Client packet AutoAttackStop (broadcast). + clientStopAutoAttack(); + + // Abort the attack of the Creature and send Server->Client ActionFailed packet. + _actor.abortAttack(); + + // Move the actor to Location (x,y,z) server side AND client side by sending Server->Client packet MoveToLocation (broadcast). + moveTo(loc.getX(), loc.getY(), loc.getZ()); + } + + @Override + public void setIntentionCast(Skill skill, WorldObject target) + { + // Forget next if it's not cast or it's cast and skill is toggle. + if ((skill == null) || !skill.isToggle()) + { + // New non-toggle cast: clear any stale saved intention and remember the current one if it differs. + if (_intention != Intention.CAST) + { + saveCurrentIntentionForCast(); + } + } + + super.setIntentionCast(skill, target); + } + + @Override + protected void clientNotifyDead() + { + _clientMovingToPawnOffset = 0; + super.clientNotifyDead(); + } + + private void thinkAttack() + { + final Creature target = getAttackTarget(); + if (target == null) + { + return; + } + + if (checkTargetLostOrDead(target)) + { + // Notify the target + setAttackTarget(null); + return; + } + + if (maybeMoveToPawn(target, _actor.getPhysicalAttackRange())) + { + return; + } + + clientStopMoving(null); + _actor.doAttack(target); + } + + private void thinkCast() + { + final Creature target = getCastTarget(); + if ((_skill.getTargetType() == TargetType.GROUND) && _actor.isPlayer()) + { + if (maybeMoveToPosition(_actor.asPlayer().getCurrentSkillWorldPosition(), _actor.getMagicalAttackRange(_skill))) + { + _actor.setCastingNow(false); + return; + } + } + else + { + if (checkTargetLost(target)) + { + if (_skill.hasNegativeEffect() && (getAttackTarget() != null)) + { + // Notify the target + setCastTarget(null); + } + + _actor.setCastingNow(false); + return; + } + + if ((target != null) && maybeMoveToPawn(target, _actor.getMagicalAttackRange(_skill))) + { + _actor.setCastingNow(false); + return; + } + } + + if ((_skill.getHitTime() > 50) && !_skill.isSimultaneousCast()) + { + clientStopMoving(null); + } + + // Check if target has changed. + final WorldObject currentTarget = _actor.getTarget(); + if ((currentTarget != target) && (currentTarget != null) && (target != null)) + { + _actor.setTarget(target); + _actor.doCast(_skill); + _actor.setTarget(currentTarget); + return; + } + + _actor.doCast(_skill); + } + + private void thinkPickUp() + { + if (_actor.isAllSkillsDisabled() || _actor.isCastingNow()) + { + return; + } + + final WorldObject target = getTarget(); + if (checkTargetLost(target) || maybeMoveToPawn(target, 36)) + { + return; + } + + setIntentionIdle(); + _actor.asPlayer().doPickupItem(target); + } + + private void thinkInteract() + { + if (_actor.isAllSkillsDisabled() || _actor.isCastingNow()) + { + return; + } + + final WorldObject target = getTarget(); + if (checkTargetLost(target) || maybeMoveToPawn(target, 36)) + { + return; + } + + if (!(target instanceof StaticObject)) + { + _actor.asPlayer().doInteract(target.asCreature()); + } + + setIntentionIdle(); + } + + @Override + public void notifyActionThink() + { + if (_thinking && (getIntention() != Intention.CAST)) + { + return; + } + + _thinking = true; + try + { + if (getIntention() == Intention.ATTACK) + { + thinkAttack(); + } + else if (getIntention() == Intention.CAST) + { + thinkCast(); + } + else if (getIntention() == Intention.PICK_UP) + { + thinkPickUp(); + } + else if (getIntention() == Intention.INTERACT) + { + thinkInteract(); + } + } + finally + { + _thinking = false; + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/ai/SiegeGuardAI.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/ai/SiegeGuardAI.java new file mode 100644 index 0000000..9208a26 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/ai/SiegeGuardAI.java @@ -0,0 +1,866 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.ai; + +import java.util.Collection; +import java.util.concurrent.Future; + +import org.l2jmobius.commons.threads.ThreadPool; +import org.l2jmobius.commons.util.Rnd; +import org.l2jmobius.gameserver.entity.World; +import org.l2jmobius.gameserver.entity.WorldObject; +import org.l2jmobius.gameserver.entity.actor.Attackable; +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.Npc; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.instance.Defender; +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.interfaces.ILocational; +import org.l2jmobius.gameserver.mechanics.effects.EffectType; +import org.l2jmobius.gameserver.mechanics.skill.Skill; +import org.l2jmobius.gameserver.taskmanagers.GameTimeTaskManager; +import org.l2jmobius.gameserver.util.LocationUtil; + +/** + * This class manages AI of Attackable. + */ +public class SiegeGuardAI extends CreatureAI implements Runnable +{ + private static final int MAX_ATTACK_TIMEOUT = 300; // int ticks, i.e. 30 seconds + + /** The Attackable AI task executed every 1s (call onActionThink method) */ + private Future _aiTask; + + /** For attack AI, analysis of mob and its targets */ + private final SelfAnalysis _selfAnalysis = new SelfAnalysis(); + // private TargetAnalysis _mostHatedAnalysis = new TargetAnalysis(); + + /** The delay after which the attacked is stopped */ + private int _attackTimeout; + + /** The Attackable aggro counter */ + private int _globalAggro; + + /** The flag used to indicate that a thinking action is in progress */ + private boolean _thinking; // to prevent recursive thinking + + private final int _attackRange; + + /** + * Constructor of AttackableAI. + * @param creature the creature + */ + public SiegeGuardAI(Defender creature) + { + super(creature); + _selfAnalysis.init(); + _attackTimeout = Integer.MAX_VALUE; + _globalAggro = -10; // 10 seconds timeout of ATTACK after respawn. + _attackRange = _actor.getPhysicalAttackRange(); + } + + @Override + public void run() + { + // Launch actions corresponding to the Action Think. + notifyActionThink(); + } + + /** + * Actor is a GuardInstance: + *
      + *
    • The target isn't a Folk or a Door
    • + *
    • The target isn't dead, isn't invulnerable, isn't in silent moving mode AND too far (>100)
    • + *
    • The target is in the actor Aggro range and is at the same height
    • + *
    • The Player target has karma (=PK)
    • + *
    • The Monster target is aggressive
    • + *
    + *
    + * Actor is a SiegeGuard: + *
      + *
    • The target isn't a Folk or a Door
    • + *
    • The target isn't dead, isn't invulnerable, isn't in silent moving mode AND too far (>100)
    • + *
    • The target is in the actor Aggro range and is at the same height
    • + *
    • A siege is in progress
    • + *
    • The Player target isn't a Defender
    • + *
    + *
    + * Actor is a FriendlyMob: + *
      + *
    • The target isn't a Folk, a Door or another Npc
    • + *
    • The target isn't dead, isn't invulnerable, isn't in silent moving mode AND too far (>100)
    • + *
    • The target is in the actor Aggro range and is at the same height
    • + *
    • The Player target has karma (=PK)
    • + *
    + *
    + * Actor is a Monster: + *
      + *
    • The target isn't a Folk, a Door or another Npc
    • + *
    • The target isn't dead, isn't invulnerable, isn't in silent moving mode AND too far (>100)
    • + *
    • The target is in the actor Aggro range and is at the same height
    • + *
    • The actor is Aggressive
    • + *
    + * @param target The targeted WorldObject + * @return True if the target is autoattackable (depends on the actor type). + */ + protected boolean autoAttackCondition(Creature target) + { + // Check if the target isn't another guard, folk or a door. + if ((target == null) || (target instanceof Defender) || target.isNpc() || target.isDoor() || target.isAlikeDead()) + { + return false; + } + + // Check if the target isn't invulnerable. + if (target.isInvul() && ((target.isPlayer() && target.isGM()) || (target.isSummon() && target.asSummon().getOwner().isGM()))) + { + return false; + } + + // Get the owner if the target is a summon. + Creature currentTarget = target; + if (currentTarget.isSummon()) + { + final Player owner = currentTarget.asSummon().getOwner(); + if (_actor.isInsideRadius3D(owner, 1000)) + { + currentTarget = owner; + } + } + + // Check if the target isn't in silent move mode AND too far (>100). + if (currentTarget.isPlayable() && currentTarget.asPlayable().isSilentMovingAffected() && !_actor.isInsideRadius2D(currentTarget, 250)) + { + return false; + } + + // Los Check Here + return (_actor.isAutoAttackable(currentTarget) && GeoEngine.getInstance().canSeeTarget(_actor, currentTarget)); + } + + private void startAITask() + { + if (_aiTask == null) + { + _aiTask = ThreadPool.scheduleAtFixedRate(this, 1000, 1000); + } + } + + private boolean shouldPromoteIdleToActive() + { + if (_actor.isAlikeDead()) + { + return false; + } + + return World.getFirstVisibleObject(_actor.asAttackable(), Player.class) != null; + } + + @Override + public synchronized void setIntentionIdle() + { + if (shouldPromoteIdleToActive()) + { + setIntentionActive(); + return; + } + + super.setIntentionIdle(); + + // Stop AI task and detach AI from NPC. + if (_aiTask != null) + { + _aiTask.cancel(true); + _aiTask = null; + } + + // Cancel the AI + _actor.detachAI(); + } + + @Override + public synchronized void setIntentionActive() + { + super.setIntentionActive(); + startAITask(); + } + + @Override + public synchronized void setIntentionRest() + { + super.setIntentionRest(); + startAITask(); + } + + /** + * Manage the Attack Intention : Stop current Attack (if necessary), Calculate attack timeout, Start a new Attack and Launch Think Action. + * @param target The WorldObject to attack + */ + @Override + public synchronized void setIntentionAttack(WorldObject target) + { + // Calculate the attack timeout. + _attackTimeout = MAX_ATTACK_TIMEOUT + GameTimeTaskManager.getInstance().getGameTicks(); + + // Manage the Attack Intention : Stop current Attack (if necessary), Start a new Attack and Launch Think Action. + super.setIntentionAttack(target); + startAITask(); + } + + @Override + public synchronized void setIntentionCast(Skill skill, WorldObject target) + { + super.setIntentionCast(skill, target); + startAITask(); + } + + @Override + public synchronized void setIntentionMoveTo(ILocational destination) + { + super.setIntentionMoveTo(destination); + startAITask(); + } + + @Override + public synchronized void setIntentionFollow(WorldObject target) + { + super.setIntentionFollow(target); + startAITask(); + } + + @Override + public synchronized void setIntentionPickUp(WorldObject item) + { + super.setIntentionPickUp(item); + startAITask(); + } + + @Override + public synchronized void setIntentionInteract(WorldObject object) + { + super.setIntentionInteract(object); + startAITask(); + } + + /** + * Manage AI standard thinks of a Attackable (called by onActionThink).
    + *
    + * Actions: + *
      + *
    • Update every 1s the _globalAggro counter to come close to 0
    • + *
    • If the actor is Aggressive and can attack, add all autoAttackable Creature in its Aggro Range to its _aggroList, chose a target and order to attack it
    • + *
    • If the actor can't attack, order to it to return to its home location
    • + *
    + */ + private void thinkActive() + { + final Attackable npc = _actor.asAttackable(); + + // Update every 1s the _globalAggro counter to come close to 0. + if (_globalAggro != 0) + { + if (_globalAggro < 0) + { + _globalAggro++; + } + else + { + _globalAggro--; + } + } + + // Add all autoAttackable Creature in Attackable Aggro Range to its _aggroList with 0 damage and 1 hate. + // A Attackable isn't aggressive during 10s after its spawn because _globalAggro is set to -10. + if (_globalAggro >= 0) + { + World.forEachVisibleObjectInRange(npc, Creature.class, _attackRange, target -> + { + if (autoAttackCondition(target) && (npc.getHating(target) == 0)) // check aggression + { + npc.addDamageHate(target, 0, 1); + } + }); + + // Chose a target from its aggroList. + final Creature hated = _actor.isConfused() ? getAttackTarget() : npc.getMostHated(); + + // Order to the Attackable to attack the target. + if (hated != null) + { + // Get the hate level of the Attackable against this Creature target contained in _aggroList. + final long aggro = npc.getHating(hated); + if ((aggro + _globalAggro) > 0) + { + // Set the Creature movement type to run and send Server->Client packet ChangeMoveType to all others Player. + if (!_actor.isRunning()) + { + _actor.setRunning(); + } + + // Set the AI Intention to ATTACK. + setIntentionAttack(hated); + } + + return; + } + } + + // Order to the Defender to return to its home location because there's no target to attack. + ((Defender) _actor).returnHome(); + } + + /** + * Manage AI attack thinks of a Attackable (called by onActionThink).
    + *
    + * Actions: + *
      + *
    • Update the attack timeout if actor is running
    • + *
    • If target is dead or timeout is expired, stop this attack and set the Intention to ACTIVE
    • + *
    • Call all WorldObject of its Faction inside the Faction Range
    • + *
    • Chose a target and order to attack it with magic skill or physical attack
    • + *
    + * TODO: Manage casting rules to healer mobs (like Ant Nurses) + */ + private void thinkAttack() + { + if ((_attackTimeout < GameTimeTaskManager.getInstance().getGameTicks()) && _actor.isRunning()) + { + // Set the actor movement type to walk and send Server->Client packet ChangeMoveType to all others Player. + _actor.setWalking(); + + // Calculate a new attack timeout. + _attackTimeout = MAX_ATTACK_TIMEOUT + GameTimeTaskManager.getInstance().getGameTicks(); + } + + final Creature attackTarget = getAttackTarget(); + + // Check if target is dead or if timeout is expired to stop this attack. + if ((attackTarget == null) || attackTarget.isAlikeDead() || (_attackTimeout < GameTimeTaskManager.getInstance().getGameTicks())) + { + // Stop hating this target after the attack timeout or if target is dead. + if (attackTarget != null) + { + _actor.asAttackable().stopHating(attackTarget); + } + + // Cancel target and timeout. + _attackTimeout = Integer.MAX_VALUE; + setAttackTarget(null); + + // Set the AI Intention to ACTIVE. + setIntentionActive(); + _actor.setWalking(); + return; + } + + factionNotifyAndSupport(); + attackPrepare(); + } + + private void factionNotifyAndSupport() + { + final Creature target = getAttackTarget(); + + // Call all WorldObject of its Faction inside the Faction Range. + if ((_actor.asNpc().getTemplate().getClans() == null) || (target == null) || target.isInvul()) + { + return; + } + + // Go through all Creature that belong to its faction. + // for (Creature creature : _actor.getKnownList().getKnownCharactersInRadius(_actor.asNpc().getFactionRange()+_actor.getTemplate().collisionRadius)) + for (Creature creature : World.getVisibleObjectsInRange(_actor, Creature.class, 1000)) + { + if (!(creature instanceof Npc)) + { + if (_selfAnalysis.hasHealOrResurrect && creature.isPlayer() && (_actor.asNpc().getCastle().getSiege().checkIsDefender(creature.asPlayer().getClan()))// + && !_actor.isAttackDisabled() && (creature.getCurrentHp() < (creature.getMaxHp() * 0.6)) && (_actor.getCurrentHp() > (_actor.getMaxHp() / 2)) && (_actor.getCurrentMp() > (_actor.getMaxMp() / 2)) && creature.isInCombat()) + { + for (Skill sk : _selfAnalysis.healSkills) + { + if ((_actor.getCurrentMp() < sk.getMpConsume()) || _actor.isSkillDisabled(sk) || !LocationUtil.checkIfInRange(sk.getCastRange(), _actor, creature, true)) + { + continue; + } + + final int chance = 5; + if (chance >= Rnd.get(100)) + { + continue; + } + + if (!GeoEngine.getInstance().canSeeTarget(_actor, creature)) + { + break; + } + + final WorldObject oldTarget = _actor.getTarget(); + _actor.setTarget(creature); + clientStopMoving(null); + _actor.doCast(sk); + _actor.setTarget(oldTarget); + return; + } + } + continue; + } + + final Npc npc = creature.asNpc(); + if (!npc.isInMyClan(_actor.asNpc())) + { + continue; + } + + if (npc.getAI() != null) // TODO: possibly check not needed + { + if (!npc.isDead() && (Math.abs(target.getZ() - npc.getZ()) < 600) && ((npc.getAI()._intention == Intention.IDLE) || (npc.getAI()._intention == Intention.ACTIVE)) && target.isInsideRadius3D(npc, 1500) && GeoEngine.getInstance().canSeeTarget(npc, target)) + { + // Notify the WorldObject AI with AGGRESSION. + npc.getAI().notifyActionAggression(getAttackTarget(), 1); + return; + } + + // heal friends + if (_selfAnalysis.hasHealOrResurrect && !_actor.isAttackDisabled() && (npc.getCurrentHp() < (npc.getMaxHp() * 0.6)) && (_actor.getCurrentHp() > (_actor.getMaxHp() / 2)) && (_actor.getCurrentMp() > (_actor.getMaxMp() / 2)) && npc.isInCombat()) + { + for (Skill sk : _selfAnalysis.healSkills) + { + if ((_actor.getCurrentMp() < sk.getMpConsume()) || _actor.isSkillDisabled(sk) || !LocationUtil.checkIfInRange(sk.getCastRange(), _actor, npc, true)) + { + continue; + } + + final int chance = 4; + if (chance >= Rnd.get(100)) + { + continue; + } + + if (!GeoEngine.getInstance().canSeeTarget(_actor, npc)) + { + break; + } + + final WorldObject oldTarget = _actor.getTarget(); + _actor.setTarget(npc); + clientStopMoving(null); + _actor.doCast(sk); + _actor.setTarget(oldTarget); + return; + } + } + } + } + } + + private void attackPrepare() + { + // Get all information needed to choose between physical or magical attack. + Collection skills = null; + double distance = 0; + int range = 0; + Creature attackTarget = getAttackTarget(); + + try + { + _actor.setTarget(attackTarget); + skills = _actor.getAllSkills(); + distance = _actor.calculateDistance2D(attackTarget); + range = _actor.getPhysicalAttackRange() + _actor.getTemplate().getCollisionRadius() + attackTarget.getTemplate().getCollisionRadius(); + if (attackTarget.isMoving()) + { + range += 50; + } + } + catch (NullPointerException e) + { + _actor.setTarget(null); + setIntentionIdle(); + return; + } + + // never attack defenders + final Defender sGuard = (Defender) _actor; + if (attackTarget.isPlayer() && (sGuard.getConquerableHall() == null) && sGuard.getCastle().getSiege().checkIsDefender(attackTarget.asPlayer().getClan())) + { + // Cancel the target + sGuard.stopHating(attackTarget); + _actor.setTarget(null); + setIntentionIdle(); + return; + } + + if (!GeoEngine.getInstance().canSeeTarget(_actor, attackTarget)) + { + // Siege guards differ from normal mobs currently: + // If target cannot seen, don't attack any more. + sGuard.stopHating(attackTarget); + _actor.setTarget(null); + setIntentionIdle(); + return; + } + + // Check if the actor isn't muted and if it is far from target. + if (!_actor.isMuted() && (distance > range)) + { + // Check for long ranged skills and heal/buff skills. + for (Skill sk : skills) + { + final int castRange = sk.getCastRange(); + if ((distance <= castRange) && (castRange > 70) && !_actor.isSkillDisabled(sk) && (_actor.getCurrentMp() >= _actor.getStat().getMpConsume(sk)) && !sk.isPassive()) + { + final WorldObject oldTarget = _actor.getTarget(); + if ((sk.isContinuous() && !sk.isDebuff()) || sk.hasEffectType(EffectType.HEAL)) + { + if (sk.hasEffectType(EffectType.HEAL) && (_actor.getCurrentHp() > (int) (_actor.getMaxHp() / 1.5))) + { + break; + } + + boolean useSkillSelf = true; + if (sk.isContinuous() && !sk.isDebuff() && _actor.isAffectedBySkill(sk.getId())) + { + useSkillSelf = false; + } + + if (useSkillSelf) + { + _actor.setTarget(_actor); + } + } + + clientStopMoving(null); + _actor.doCast(sk); + _actor.setTarget(oldTarget); + return; + } + } + + // Check if the SiegeGuard is attacking, knows the target and can't run. + if (!(_actor.isAttackingNow()) && (_actor.getRunSpeed() == 0) && (_actor.isInSurroundingRegion(attackTarget))) + { + // Cancel the target + _actor.setTarget(null); + setIntentionIdle(); + } + else if (sGuard.getSpawn() != null) + { + final double dx = _actor.getX() - attackTarget.getX(); + final double dy = _actor.getY() - attackTarget.getY(); + final double dz = _actor.getZ() - attackTarget.getZ(); + final double homeX = attackTarget.getX() - sGuard.getSpawn().getX(); + final double homeY = attackTarget.getY() - sGuard.getSpawn().getY(); + + // Check if the SiegeGuard isn't too far from it's home location. + if ((((dx * dx) + (dy * dy)) > 10000) && (((homeX * homeX) + (homeY * homeY)) > 3240000) // 1800 * 1800 + && (_actor.isInSurroundingRegion(attackTarget))) + { + // Cancel the target + _actor.setTarget(null); + setIntentionIdle(); + } + + // Temporary hack for preventing guards jumping off towers, + // before replacing this with effective geodata checks and AI modification + else if ((dz * dz) < (170 * 170)) // normally 130 if guard z coordinates correct + { + if (_selfAnalysis.isHealer) + { + return; + } + + if (_selfAnalysis.isMage) + { + range = _selfAnalysis.maxCastRange - 50; + } + + moveToPawn(attackTarget, attackTarget.isMoving() ? range - 70 : range); + } + } + } + else + { + if (_actor.isMuted() && (distance > range) && !_selfAnalysis.isHealer) + { + // Temporary hack for preventing guards jumping off towers, + // before replacing this with effective geodata checks and AI modification + final double dz = _actor.getZ() - attackTarget.getZ(); + if ((dz * dz) < (170 * 170)) // normally 130 if guard z coordinates correct + { + if (_selfAnalysis.isMage) + { + range = _selfAnalysis.maxCastRange - 50; + } + + moveToPawn(attackTarget, attackTarget.isMoving() ? range - 70 : range); + } + return; + } + + if (distance <= range) + { + final Creature hated = _actor.isConfused() ? attackTarget : _actor.asAttackable().getMostHated(); + if (hated == null) + { + setIntentionActive(); + return; + } + + if (hated != attackTarget) + { + attackTarget = hated; + } + + _attackTimeout = MAX_ATTACK_TIMEOUT + GameTimeTaskManager.getInstance().getGameTicks(); + + // check for close combat skills && heal/buff skills + if (!_actor.isMuted() && (Rnd.get(100) <= 5)) + { + for (Skill sk : skills) + { + final int castRange = sk.getCastRange(); + if ((castRange >= distance) && !sk.isPassive() && (_actor.getCurrentMp() >= _actor.getStat().getMpConsume(sk)) && !_actor.isSkillDisabled(sk)) + { + final WorldObject oldTarget = _actor.getTarget(); + if ((sk.isContinuous() && !sk.isDebuff()) || sk.hasEffectType(EffectType.HEAL)) + { + if (sk.hasEffectType(EffectType.HEAL) && (_actor.getCurrentHp() > (int) (_actor.getMaxHp() / 1.5))) + { + break; + } + + boolean useSkillSelf = true; + if (sk.isContinuous() && !sk.isDebuff() && _actor.isAffectedBySkill(sk.getId())) + { + useSkillSelf = false; + } + + if (useSkillSelf) + { + _actor.setTarget(_actor); + } + } + + clientStopMoving(null); + _actor.doCast(sk); + _actor.setTarget(oldTarget); + return; + } + } + } + + // Finally, do the physical attack itself. + if (!_selfAnalysis.isHealer) + { + _actor.doAttack(attackTarget); + } + } + } + } + + /** + * Manage AI thinking actions of a Attackable. + */ + @Override + public void notifyActionThink() + { + // if(getIntention() != Intention.IDLE && (!_actor.isSpawned() || !_actor.hasAI() || !_actor.isKnownPlayers())) + // setIntentionIdle(); + + // Check if the thinking action is already in progress. + if (_thinking || _actor.isCastingNow() || _actor.isAllSkillsDisabled()) + { + return; + } + + // Start thinking action + _thinking = true; + + try + { + // Manage AI thinks of a Attackable. + if (getIntention() == Intention.ACTIVE) + { + thinkActive(); + } + else if (getIntention() == Intention.ATTACK) + { + thinkAttack(); + } + } + finally + { + // Stop thinking action + _thinking = false; + } + } + + /** + * Launch actions corresponding to the Action Attacked.
    + *
    + * Actions: + *
      + *
    • Init the attack : Calculate the attack timeout, Set the _globalAggro to 0, Add the attacker to the actor _aggroList
    • + *
    • Set the Creature movement type to run and send Server->Client packet ChangeMoveType to all others Player
    • + *
    • Set the Intention to ATTACK
    • + *
    + * @param attackerObj The WorldObject that attacks the actor + */ + @Override + public void notifyActionAttacked(WorldObject attackerObj) + { + if (attackerObj == null) + { + return; + } + + final Creature attacker = attackerObj.asCreature(); + if (attacker == null) + { + return; + } + + // Calculate the attack timeout. + _attackTimeout = MAX_ATTACK_TIMEOUT + GameTimeTaskManager.getInstance().getGameTicks(); + + // Set the _globalAggro to 0 to permit attack even just after spawn. + if (_globalAggro < 0) + { + _globalAggro = 0; + } + + // Add the attacker to the _aggroList of the actor. + _actor.asAttackable().addDamageHate(attacker, 0, 1); + + // Set the Creature movement type to run and send Server->Client packet ChangeMoveType to all others Player. + if (!_actor.isRunning()) + { + _actor.setRunning(); + } + + // Set the Intention to ATTACK. + if (getIntention() != Intention.ATTACK) + { + setIntentionAttack(attacker); + } + + super.notifyActionAttacked(attacker); + } + + /** + * Launch actions corresponding to the Action Aggression.
    + *
    + * Actions: + *
      + *
    • Add the target to the actor _aggroList or update hate if already present
    • + *
    • Set the actor Intention to ATTACK (if actor is GuardInstance check if it isn't too far from its home location)
    • + *
    + * @param aggro The value of hate to add to the actor against the target + */ + @Override + public void notifyActionAggression(WorldObject targetObj, int aggro) + { + if (_actor == null) + { + return; + } + + final Creature target = targetObj == null ? null : targetObj.asCreature(); + final Attackable me = _actor.asAttackable(); + if (target != null) + { + // Add the target to the actor _aggroList or update hate if already present. + me.addDamageHate(target, 0, aggro); + + // Get the hate of the actor against the target. + if (me.getHating(target) <= 0) + { + if (me.getMostHated() == null) + { + _globalAggro = -25; + me.clearAggroList(); + setIntentionIdle(); + } + return; + } + + // Set the actor AI Intention to ATTACK. + if (getIntention() != Intention.ATTACK) + { + // Set the Creature movement type to run and send Server->Client packet ChangeMoveType to all others Player. + if (!_actor.isRunning()) + { + _actor.setRunning(); + } + + final Defender sGuard = (Defender) _actor; + final double homeX = target.getX() - sGuard.getSpawn().getX(); + final double homeY = target.getY() - sGuard.getSpawn().getY(); + + // Check if the SiegeGuard is not too far from its home location. + if (((homeX * homeX) + (homeY * homeY)) < 3240000) + { + setIntentionAttack(target); + } + } + } + else + { + // Currently only for setting lower general aggro. + if (aggro >= 0) + { + return; + } + + final Creature mostHated = me.getMostHated(); + if (mostHated == null) + { + _globalAggro = -25; + return; + } + + for (Creature aggroed : me.getAggroList().keySet()) + { + me.addDamageHate(aggroed, 0, aggro); + } + + if (me.getHating(mostHated) <= 0) + { + _globalAggro = -25; + me.clearAggroList(); + setIntentionIdle(); + } + } + } + + @Override + public void stopAITask() + { + if (_aiTask != null) + { + _aiTask.cancel(false); + _aiTask = null; + } + + _actor.detachAI(); + super.stopAITask(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/ai/SpecialSiegeGuardAI.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/ai/SpecialSiegeGuardAI.java new file mode 100644 index 0000000..e871212 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/ai/SpecialSiegeGuardAI.java @@ -0,0 +1,55 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.ai; + +import java.util.ArrayList; +import java.util.List; + +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.instance.Defender; + +/** + * @author BiggBoss + */ +public class SpecialSiegeGuardAI extends SiegeGuardAI +{ + private final List _allied = new ArrayList<>(); + + /** + * @param creature + */ + public SpecialSiegeGuardAI(Defender creature) + { + super(creature); + } + + public List getAlly() + { + return _allied; + } + + @Override + protected boolean autoAttackCondition(Creature target) + { + if (_allied.contains(target.getObjectId())) + { + return false; + } + + return super.autoAttackCondition(target); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/ai/SummonAI.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/ai/SummonAI.java new file mode 100644 index 0000000..f399736 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/ai/SummonAI.java @@ -0,0 +1,374 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.ai; + +import java.util.concurrent.Future; + +import org.l2jmobius.commons.threads.ThreadPool; +import org.l2jmobius.commons.util.Rnd; +import org.l2jmobius.gameserver.config.GeoEngineConfig; +import org.l2jmobius.gameserver.entity.WorldObject; +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.Summon; +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; +import org.l2jmobius.gameserver.mechanics.skill.Skill; + +public class SummonAI extends PlayableAI implements Runnable +{ + private static final int AVOID_RADIUS = 70; + + private volatile boolean _thinking; // To prevent recursive thinking. + private volatile boolean _startFollow = _actor.asSummon().getFollowStatus(); + private Creature _lastAttack = null; + + private volatile boolean _startAvoid = false; + private Future _avoidTask = null; + + public SummonAI(Summon creature) + { + super(creature); + } + + @Override + public Intention getNextIntention() + { + return _lastAttack != null ? Intention.ATTACK : null; + } + + @Override + public void setIntentionAttack(WorldObject target) + { + if (target == null) + { + return; + } + + final Creature creatureTarget = target.asCreature(); + if (creatureTarget == null) + { + return; + } + + if ((GeoEngineConfig.PATHFINDING > 0) && (PathFinding.getInstance().findPath(_actor.getX(), _actor.getY(), _actor.getZ(), creatureTarget.getX(), creatureTarget.getY(), creatureTarget.getZ(), _actor.getInstanceId(), false) == null)) + { + return; + } + + stopAvoidTask(); + super.setIntentionAttack(target); + } + + @Override + public void setIntentionIdle() + { + stopFollow(); + _startFollow = false; + stopAvoidTask(); + setIntentionActive(); + } + + @Override + public void setIntentionActive() + { + if (_startFollow) + { + startAvoidTask(); + setIntentionFollow(_actor.asSummon().getOwner()); + } + else + { + startAvoidTask(); + super.setIntentionActive(); + } + } + + @Override + public void setIntentionFollow(WorldObject target) + { + if (target == null) + { + clientActionFailed(); + return; + } + + final Creature creatureTarget = target.asCreature(); + if (creatureTarget == null) + { + clientActionFailed(); + return; + } + + if ((GeoEngineConfig.PATHFINDING > 0) && (PathFinding.getInstance().findPath(_actor.getX(), _actor.getY(), _actor.getZ(), creatureTarget.getX(), creatureTarget.getY(), creatureTarget.getZ(), _actor.getInstanceId(), false) == null)) + { + clientActionFailed(); + return; + } + + startAvoidTask(); + super.setIntentionFollow(target); + } + + private void thinkAttack() + { + if (checkTargetLostOrDead(getAttackTarget())) + { + setAttackTarget(null); + return; + } + + if (maybeMoveToPawn(getAttackTarget(), _actor.getPhysicalAttackRange())) + { + return; + } + + clientStopMoving(null); + _actor.doAttack(getAttackTarget()); + } + + private void thinkCast() + { + if (checkTargetLost(getCastTarget())) + { + setCastTarget(null); + return; + } + + final boolean val = _startFollow; + if (maybeMoveToPawn(getCastTarget(), _actor.getMagicalAttackRange(_skill))) + { + return; + } + + clientStopMoving(null); + final Summon summon = _actor.asSummon(); + summon.setFollowStatus(false); + setIntentionIdle(); + _startFollow = val; + _actor.doCast(_skill); + } + + private void thinkPickUp() + { + if (checkTargetLost(getTarget()) || maybeMoveToPawn(getTarget(), 36)) + { + return; + } + + setIntentionIdle(); + _actor.asSummon().doPickupItem(getTarget()); + } + + private void thinkInteract() + { + if (checkTargetLost(getTarget()) || maybeMoveToPawn(getTarget(), 36)) + { + return; + } + + setIntentionIdle(); + } + + @Override + public void notifyActionThink() + { + if (_thinking || _actor.isCastingNow() || _actor.isAllSkillsDisabled()) + { + return; + } + + _thinking = true; + + try + { + switch (getIntention()) + { + case ATTACK: + { + thinkAttack(); + break; + } + case CAST: + { + thinkCast(); + break; + } + case PICK_UP: + { + thinkPickUp(); + break; + } + case INTERACT: + { + thinkInteract(); + break; + } + } + } + finally + { + _thinking = false; + } + } + + @Override + public void notifyActionFinishCasting() + { + if (_lastAttack == null) + { + _actor.asSummon().setFollowStatus(_startFollow); + } + else + { + final Creature replayTarget = _lastAttack; + _lastAttack = null; + setIntentionAttack(replayTarget); + } + + super.notifyActionFinishCasting(); + } + + @Override + public void notifyActionAttacked(WorldObject attacker) + { + super.notifyActionAttacked(attacker); + + if (attacker != null) + { + final Creature attackerCreature = attacker.asCreature(); + if (attackerCreature != null) + { + avoidAttack(attackerCreature); + } + } + } + + @Override + public void notifyActionEvaded(WorldObject attacker) + { + super.notifyActionEvaded(attacker); + + if (attacker != null) + { + final Creature attackerCreature = attacker.asCreature(); + if (attackerCreature != null) + { + avoidAttack(attackerCreature); + } + } + } + + private void avoidAttack(Creature attacker) + { + // Trying to avoid if summon near owner. + if ((_actor.asSummon().getOwner() != null) && (_actor.asSummon().getOwner() != attacker) && _actor.asSummon().getOwner().isInsideRadius3D(_actor, 2 * AVOID_RADIUS)) + { + _startAvoid = true; + } + } + + @Override + public void run() + { + if (!_startAvoid) + { + return; + } + + _startAvoid = false; + + if (_actor.isMoving() || _actor.isDead() || _actor.isMovementDisabled()) + { + return; + } + + final int ownerX = _actor.asSummon().getOwner().getX(); + final int ownerY = _actor.asSummon().getOwner().getY(); + final double angle = Math.toRadians(Rnd.get(-90, 90)) + Math.atan2(ownerY - _actor.getY(), ownerX - _actor.getX()); + final int targetX = ownerX + (int) (AVOID_RADIUS * Math.cos(angle)); + final int targetY = ownerY + (int) (AVOID_RADIUS * Math.sin(angle)); + if (GeoEngine.getInstance().canMoveToTarget(_actor.getX(), _actor.getY(), _actor.getZ(), targetX, targetY, _actor.getZ(), _actor.getInstanceId())) + { + moveTo(targetX, targetY, _actor.getZ()); + } + } + + public void notifyFollowStatusChange() + { + _startFollow = !_startFollow; + switch (getIntention()) + { + case ACTIVE: + case FOLLOW: + case IDLE: + case MOVE_TO: + case PICK_UP: + { + _actor.asSummon().setFollowStatus(_startFollow); + } + } + } + + public void setStartFollowController(boolean value) + { + _startFollow = value; + } + + @Override + public void setIntentionCast(Skill skill, WorldObject target) + { + if (getIntention() == Intention.ATTACK) + { + _lastAttack = getAttackTarget(); + } + else + { + _lastAttack = null; + } + + stopAvoidTask(); + super.setIntentionCast(skill, target); + } + + private void startAvoidTask() + { + if (_avoidTask == null) + { + _avoidTask = ThreadPool.scheduleAtFixedRate(this, 100, 100); + } + } + + private void stopAvoidTask() + { + if (_avoidTask != null) + { + _avoidTask.cancel(false); + _avoidTask = null; + } + } + + @Override + public void stopAITask() + { + stopAvoidTask(); + super.stopAITask(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/cache/HtmCache.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/cache/HtmCache.java new file mode 100644 index 0000000..6e254ef --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/cache/HtmCache.java @@ -0,0 +1,207 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.cache; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.gameserver.config.GeneralConfig; +import org.l2jmobius.gameserver.config.ServerConfig; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.network.enums.ChatType; +import org.l2jmobius.gameserver.network.serverpackets.CreatureSay; + +/** + * @author Layane, Mobius + */ +public class HtmCache +{ + private static final Logger LOGGER = Logger.getLogger(HtmCache.class.getName()); + + private static final Map HTML_CACHE = GeneralConfig.HTM_CACHE ? new HashMap<>() : new ConcurrentHashMap<>(); + + private int _loadedFiles; + private long _bytesBuffLen; + + protected HtmCache() + { + reload(); + } + + public void reload() + { + reload(ServerConfig.DATAPACK_ROOT); + } + + public void reload(File file) + { + if (GeneralConfig.HTM_CACHE) + { + LOGGER.info("Html cache start..."); + parseDir(file); + LOGGER.info("Cache[HTML]: " + String.format("%.3f", getMemoryUsage()) + " megabytes on " + _loadedFiles + " files loaded."); + } + else + { + HTML_CACHE.clear(); + _loadedFiles = 0; + _bytesBuffLen = 0; + LOGGER.info("Cache[HTML]: Running lazy cache."); + } + } + + public void reloadPath(File file) + { + parseDir(file); + LOGGER.info("Cache[HTML]: Reloaded specified path."); + } + + public double getMemoryUsage() + { + return (float) _bytesBuffLen / 1048576; + } + + public int getLoadedFiles() + { + return _loadedFiles; + } + + private void parseDir(File dir) + { + final File[] files = dir.listFiles(); + if (files != null) + { + for (File file : files) + { + if (!file.isDirectory()) + { + loadFile(file); + } + else + { + parseDir(file); + } + } + } + } + + public String loadFile(File file) + { + if ((file == null) || !file.isFile()) + { + return null; + } + + final String lowerCaseName = file.getName().toLowerCase(); + if (!(lowerCaseName.endsWith(".htm") || lowerCaseName.endsWith(".html"))) + { + return null; + } + + String filePath = null; + String content = null; + try (FileInputStream fis = new FileInputStream(file); + BufferedInputStream bis = new BufferedInputStream(fis)) + { + final int bytes = bis.available(); + final byte[] raw = new byte[bytes]; + + bis.read(raw); + content = new String(raw, StandardCharsets.UTF_8); + content = content.replaceAll("(?s)", ""); // Remove html comments. + content = content.replaceAll("[\\t\\n]", ""); // Remove tabs and new lines. + + filePath = file.toURI().getPath().substring(ServerConfig.DATAPACK_ROOT.toURI().getPath().length()); + if (GeneralConfig.CHECK_HTML_ENCODING && !filePath.startsWith("data/lang") && !StandardCharsets.US_ASCII.newEncoder().canEncode(content)) + { + LOGGER.warning("HTML encoding check: File " + filePath + " contains non ASCII content."); + } + + final String oldContent = HTML_CACHE.put(filePath, content); + if (oldContent == null) + { + _bytesBuffLen += bytes; + _loadedFiles++; + } + else + { + _bytesBuffLen = (_bytesBuffLen - oldContent.length()) + bytes; + } + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Problem with htm file:", e); + } + + return content; + } + + public String getHtm(Player player, String path) + { + final String prefix = player != null ? player.getHtmlPrefix() : ""; + String newPath = prefix + path; + String content = HTML_CACHE.get(newPath); + if (!GeneralConfig.HTM_CACHE && (content == null)) + { + content = loadFile(new File(ServerConfig.DATAPACK_ROOT, newPath)); + if (content == null) + { + content = loadFile(new File(ServerConfig.SCRIPT_ROOT, newPath)); + } + } + + // In case localisation does not exist try the default path. + if ((content == null) && !prefix.contentEquals("")) + { + content = HTML_CACHE.get(path); + newPath = path; + } + + if ((player != null) && player.isGM() && GeneralConfig.GM_DEBUG_HTML_PATHS) + { + player.sendPacket(new CreatureSay(null, ChatType.GENERAL, "HTML", newPath.substring(5))); + } + + return content; + } + + public boolean contains(String path) + { + return HTML_CACHE.containsKey(path); + } + + public static HtmCache getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final HtmCache INSTANCE = new HtmCache(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/cache/RelationCache.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/cache/RelationCache.java new file mode 100644 index 0000000..ce19c85 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/cache/RelationCache.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.cache; + +/** + * @author Sahar + */ +public class RelationCache +{ + private final int _relation; + private final boolean _isAutoAttackable; + + public RelationCache(int relation, boolean isAutoAttackable) + { + _relation = relation; + _isAutoAttackable = isAutoAttackable; + } + + public int getRelation() + { + return _relation; + } + + public boolean isAutoAttackable() + { + return _isAutoAttackable; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/communitybbs/BB/Forum.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/communitybbs/BB/Forum.java new file mode 100644 index 0000000..0018480 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/communitybbs/BB/Forum.java @@ -0,0 +1,260 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.communitybbs.BB; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.util.Collection; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.commons.database.DatabaseFactory; +import org.l2jmobius.gameserver.communitybbs.TopicConstructorType; +import org.l2jmobius.gameserver.communitybbs.Manager.ForumsBBSManager; +import org.l2jmobius.gameserver.communitybbs.Manager.TopicBBSManager; + +public class Forum +{ + private static final Logger LOGGER = Logger.getLogger(Forum.class.getName()); + + // type + public static final int ROOT = 0; + public static final int NORMAL = 1; + public static final int CLAN = 2; + public static final int MEMO = 3; + public static final int MAIL = 4; + + // perm + public static final int INVISIBLE = 0; + public static final int ALL = 1; + public static final int CLANMEMBERONLY = 2; + public static final int OWNERONLY = 3; + + private final Collection _children; + private final Map _topic = new ConcurrentHashMap<>(); + private final int _forumId; + private String _forumName; + private int _forumType; + private int _forumPost; + private int _forumPerm; + private final Forum _fParent; + private int _ownerID; + private boolean _loaded = false; + + /** + * Creates new instance of Forum. When you create new forum, use {@link org.l2jmobius.gameserver.communitybbs.Manager.ForumsBBSManager#addForum(org.l2jmobius.gameserver.communitybbs.BB.Forum)} to add forum to the forums manager. + * @param forumId + * @param fParent + */ + public Forum(int forumId, Forum fParent) + { + _forumId = forumId; + _fParent = fParent; + _children = ConcurrentHashMap.newKeySet(); + } + + /** + * @param name + * @param parent + * @param type + * @param perm + * @param ownerId + */ + public Forum(String name, Forum parent, int type, int perm, int ownerId) + { + _forumName = name; + _forumId = ForumsBBSManager.getInstance().getANewID(); + _forumType = type; + _forumPost = 0; + _forumPerm = perm; + _fParent = parent; + _ownerID = ownerId; + _children = ConcurrentHashMap.newKeySet(); + parent._children.add(this); + ForumsBBSManager.getInstance().addForum(this); + _loaded = true; + } + + private void load() + { + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement("SELECT * FROM forums WHERE forum_id=?")) + { + ps.setInt(1, _forumId); + try (ResultSet rs = ps.executeQuery()) + { + if (rs.next()) + { + _forumName = rs.getString("forum_name"); + _forumPost = rs.getInt("forum_post"); + _forumType = rs.getInt("forum_type"); + _forumPerm = rs.getInt("forum_perm"); + _ownerID = rs.getInt("forum_owner_id"); + } + } + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Data error on Forum " + _forumId + " : " + e.getMessage(), e); + } + + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement("SELECT * FROM topic WHERE topic_forum_id=? ORDER BY topic_id DESC")) + { + ps.setInt(1, _forumId); + try (ResultSet rs = ps.executeQuery()) + { + while (rs.next()) + { + final Topic t = new Topic(TopicConstructorType.RESTORE, rs.getInt("topic_id"), rs.getInt("topic_forum_id"), rs.getString("topic_name"), rs.getLong("topic_date"), rs.getString("topic_ownername"), rs.getInt("topic_ownerid"), rs.getInt("topic_type"), rs.getInt("topic_reply")); + _topic.put(t.getID(), t); + if (t.getID() > TopicBBSManager.getInstance().getMaxID(this)) + { + TopicBBSManager.getInstance().setMaxID(t.getID(), this); + } + } + } + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Data error on Forum " + _forumId + " : " + e.getMessage(), e); + } + } + + private void getChildren() + { + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement("SELECT forum_id FROM forums WHERE forum_parent=?")) + { + ps.setInt(1, _forumId); + try (ResultSet rs = ps.executeQuery()) + { + while (rs.next()) + { + final Forum f = new Forum(rs.getInt("forum_id"), this); + _children.add(f); + ForumsBBSManager.getInstance().addForum(f); + } + } + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Data error on Forum (children): " + e.getMessage(), e); + } + } + + public int getTopicSize() + { + vload(); + return _topic.size(); + } + + public Topic getTopic(int j) + { + vload(); + return _topic.get(j); + } + + public void addTopic(Topic t) + { + vload(); + _topic.put(t.getID(), t); + } + + /** + * @return the forum Id + */ + public int getID() + { + return _forumId; + } + + public String getName() + { + vload(); + return _forumName; + } + + public int getType() + { + vload(); + return _forumType; + } + + /** + * @param name the forum name + * @return the forum for the given name + */ + public Forum getChildByName(String name) + { + vload(); + for (Forum forum : _children) + { + if (forum.getName().equals(name)) + { + return forum; + } + } + + return null; + } + + /** + * @param id + */ + public void rmTopicByID(int id) + { + _topic.remove(id); + } + + public void insertIntoDb() + { + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement("INSERT INTO forums (forum_id,forum_name,forum_parent,forum_post,forum_type,forum_perm,forum_owner_id) VALUES (?,?,?,?,?,?,?)")) + { + ps.setInt(1, _forumId); + ps.setString(2, _forumName); + ps.setInt(3, _fParent.getID()); + ps.setInt(4, _forumPost); + ps.setInt(5, _forumType); + ps.setInt(6, _forumPerm); + ps.setInt(7, _ownerID); + ps.execute(); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Error while saving new Forum to db " + e.getMessage(), e); + } + } + + public void vload() + { + if (!_loaded) + { + load(); + getChildren(); + _loaded = true; + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/communitybbs/BB/Mail.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/communitybbs/BB/Mail.java new file mode 100644 index 0000000..193c5b4 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/communitybbs/BB/Mail.java @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.communitybbs.BB; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Timestamp; +import java.time.format.DateTimeFormatter; + +import org.l2jmobius.gameserver.network.enums.MailType; + +public class Mail +{ + private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); + + private final int _id; + private final int _receiverId; + private final int _senderId; + + private final String _recipients; + public final String _subject; + private final String _message; + + private final Timestamp _sentDate; + + private final String _formattedSentDate; + + private MailType _mailType; + + private boolean _isUnread; + + public Mail(ResultSet rs) throws SQLException + { + _id = rs.getInt("id"); + _receiverId = rs.getInt("receiver_id"); + _senderId = rs.getInt("sender_id"); + _mailType = Enum.valueOf(MailType.class, rs.getString("location").toUpperCase()); + _recipients = rs.getString("recipients"); + _subject = rs.getString("subject"); + _message = rs.getString("message"); + _sentDate = rs.getTimestamp("sent_date"); + _formattedSentDate = _sentDate.toLocalDateTime().format(DATE_FORMATTER); + _isUnread = rs.getInt("is_unread") != 0; + } + + public Mail(int id, int receiverId, int senderId, MailType location, String recipients, String subject, String message, Timestamp sentDate, String formattedSentDate, boolean isUnread) + { + _id = id; + _receiverId = receiverId; + _senderId = senderId; + _mailType = location; + _recipients = recipients; + _subject = subject; + _message = message; + _sentDate = sentDate; + _formattedSentDate = formattedSentDate; + _isUnread = isUnread; + } + + public int getId() + { + return _id; + } + + public int getReceiverId() + { + return _receiverId; + } + + public int getSenderId() + { + return _senderId; + } + + public MailType getMailType() + { + return _mailType; + } + + public void setMailType(MailType mailType) + { + _mailType = mailType; + } + + public String getRecipients() + { + return _recipients; + } + + public String getSubject() + { + return _subject; + } + + public String getMessage() + { + return _message; + } + + public Timestamp getSentDate() + { + return _sentDate; + } + + public String getFormattedSentDate() + { + return _formattedSentDate; + } + + public boolean isUnread() + { + return _isUnread; + } + + public void setAsRead() + { + _isUnread = false; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/communitybbs/BB/Post.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/communitybbs/BB/Post.java new file mode 100644 index 0000000..339c272 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/communitybbs/BB/Post.java @@ -0,0 +1,258 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.communitybbs.BB; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.util.Collection; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.commons.database.DatabaseFactory; +import org.l2jmobius.gameserver.communitybbs.Manager.PostBBSManager; + +/** + * @author Maktakien + */ +public class Post +{ + private static final Logger LOGGER = Logger.getLogger(Post.class.getName()); + + public static class CPost + { + private int _postId; + private String _postOwner; + private int _postOwnerId; + private long _postDate; + private int _postTopicId; + private int _postForumId; + private String _postText; + + public void setPostId(int postId) + { + _postId = postId; + } + + public int getPostId() + { + return _postId; + } + + public void setPostOwner(String postOwner) + { + _postOwner = postOwner; + } + + public String getPostOwner() + { + return _postOwner; + } + + public void setPostOwnerId(int postOwnerId) + { + _postOwnerId = postOwnerId; + } + + public int getPostOwnerId() + { + return _postOwnerId; + } + + public void setPostDate(long postDate) + { + _postDate = postDate; + } + + public long getPostDate() + { + return _postDate; + } + + public void setPostTopicId(int postTopicId) + { + _postTopicId = postTopicId; + } + + public int getPostTopicId() + { + return _postTopicId; + } + + public void setPostForumId(int postForumId) + { + _postForumId = postForumId; + } + + public int getPostForumId() + { + return _postForumId; + } + + public void setPostText(String postText) + { + _postText = postText; + } + + public String getPostText() + { + if (_postText == null) + { + return ""; + } + + // Bypass exploit check. + final String text = _postText.toLowerCase(); + if (text.contains("action") && text.contains("bypass")) + { + return ""; + } + + // Returns text without tags. + return _postText.replaceAll("<.*?>", ""); + } + } + + private final Collection _post; + + /** + * @param postOwner + * @param postOwnerId + * @param date + * @param tid + * @param postForumId + * @param txt + */ + public Post(String postOwner, int postOwnerId, long date, int tid, int postForumId, String txt) + { + _post = ConcurrentHashMap.newKeySet(); + final CPost cp = new CPost(); + cp.setPostId(0); + cp.setPostOwner(postOwner); + cp.setPostOwnerId(postOwnerId); + cp.setPostDate(date); + cp.setPostTopicId(tid); + cp.setPostForumId(postForumId); + cp.setPostText(txt); + _post.add(cp); + insertindb(cp); + } + + private void insertindb(CPost cp) + { + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement("INSERT INTO posts (post_id,post_owner_name,post_ownerid,post_date,post_topic_id,post_forum_id,post_txt) values (?,?,?,?,?,?,?)")) + { + ps.setInt(1, cp.getPostId()); + ps.setString(2, cp.getPostOwner()); + ps.setInt(3, cp.getPostOwnerId()); + ps.setLong(4, cp.getPostDate()); + ps.setInt(5, cp.getPostTopicId()); + ps.setInt(6, cp.getPostForumId()); + ps.setString(7, cp.getPostText()); + ps.execute(); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Error while saving new Post to db " + e.getMessage(), e); + } + } + + public Post(Topic t) + { + _post = ConcurrentHashMap.newKeySet(); + load(t); + } + + public CPost getCPost(int id) + { + int i = 0; + for (CPost cp : _post) + { + if (i++ == id) + { + return cp; + } + } + + return null; + } + + public void deleteMe(Topic t) + { + PostBBSManager.getInstance().delPostByTopic(t); + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement("DELETE FROM posts WHERE post_forum_id=? AND post_topic_id=?")) + { + ps.setInt(1, t.getForumID()); + ps.setInt(2, t.getID()); + ps.execute(); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Error while deleting post: " + e.getMessage(), e); + } + } + + private void load(Topic t) + { + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement("SELECT * FROM posts WHERE post_forum_id=? AND post_topic_id=? ORDER BY post_id ASC")) + { + ps.setInt(1, t.getForumID()); + ps.setInt(2, t.getID()); + try (ResultSet rs = ps.executeQuery()) + { + while (rs.next()) + { + final CPost cp = new CPost(); + cp.setPostId(rs.getInt("post_id")); + cp.setPostOwner(rs.getString("post_owner_name")); + cp.setPostOwnerId(rs.getInt("post_ownerid")); + cp.setPostDate(rs.getLong("post_date")); + cp.setPostTopicId(rs.getInt("post_topic_id")); + cp.setPostForumId(rs.getInt("post_forum_id")); + cp.setPostText(rs.getString("post_txt")); + _post.add(cp); + } + } + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Data error on Post " + t.getForumID() + "/" + t.getID() + " : " + e.getMessage(), e); + } + } + + public void updateText(int i) + { + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement("UPDATE posts SET post_txt=? WHERE post_id=? AND post_topic_id=? AND post_forum_id=?")) + { + final CPost cp = getCPost(i); + ps.setString(1, cp.getPostText()); + ps.setInt(2, cp.getPostId()); + ps.setInt(3, cp.getPostTopicId()); + ps.setInt(4, cp.getPostForumId()); + ps.execute(); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Error while saving new Post to db " + e.getMessage(), e); + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/communitybbs/BB/Topic.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/communitybbs/BB/Topic.java new file mode 100644 index 0000000..f4936d1 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/communitybbs/BB/Topic.java @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.communitybbs.BB; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.commons.database.DatabaseFactory; +import org.l2jmobius.gameserver.communitybbs.TopicConstructorType; +import org.l2jmobius.gameserver.communitybbs.Manager.TopicBBSManager; + +public class Topic +{ + private static final Logger LOGGER = Logger.getLogger(Topic.class.getName()); + + public static final int NORMAL = 0; + public static final int MEMO = 1; + + private final int _id; + private final int _forumId; + private final String _topicName; + private final long _date; + private final String _ownerName; + private final int _ownerId; + private final int _type; + private final int _cReply; + + /** + * @param ct + * @param id + * @param fid + * @param name + * @param date + * @param oname + * @param oid + * @param type + * @param cReply + */ + public Topic(TopicConstructorType ct, int id, int fid, String name, long date, String oname, int oid, int type, int cReply) + { + _id = id; + _forumId = fid; + _topicName = name; + _date = date; + _ownerName = oname; + _ownerId = oid; + _type = type; + _cReply = cReply; + TopicBBSManager.getInstance().addTopic(this); + + if (ct == TopicConstructorType.CREATE) + { + insertindb(); + } + } + + private void insertindb() + { + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement("INSERT INTO topic (topic_id,topic_forum_id,topic_name,topic_date,topic_ownername,topic_ownerid,topic_type,topic_reply) values (?,?,?,?,?,?,?,?)")) + { + ps.setInt(1, _id); + ps.setInt(2, _forumId); + ps.setString(3, _topicName); + ps.setLong(4, _date); + ps.setString(5, _ownerName); + ps.setInt(6, _ownerId); + ps.setInt(7, _type); + ps.setInt(8, _cReply); + ps.execute(); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Error while saving new Topic to db " + e.getMessage(), e); + } + } + + /** + * @return the topic Id + */ + public int getID() + { + return _id; + } + + public int getForumID() + { + return _forumId; + } + + /** + * @return the topic name + */ + public String getName() + { + return _topicName; + } + + public String getOwnerName() + { + return _ownerName; + } + + /** + * @param f + */ + public void deleteme(Forum f) + { + TopicBBSManager.getInstance().delTopic(this); + f.rmTopicByID(_id); + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement("DELETE FROM topic WHERE topic_id=? AND topic_forum_id=?")) + { + ps.setInt(1, _id); + ps.setInt(2, f.getID()); + ps.execute(); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Error while deleting topic: " + e.getMessage(), e); + } + } + + /** + * @return the topic date + */ + public long getDate() + { + return _date; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/communitybbs/Manager/BaseBBSManager.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/communitybbs/Manager/BaseBBSManager.java new file mode 100644 index 0000000..fecaf64 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/communitybbs/Manager/BaseBBSManager.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.communitybbs.Manager; + +import java.util.ArrayList; +import java.util.List; + +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.network.serverpackets.ShowBoard; + +public abstract class BaseBBSManager +{ + public abstract void parsecmd(String command, Player player); + + public abstract void parsewrite(String ar1, String ar2, String ar3, String ar4, String ar5, Player player); + + /** + * @param html + * @param acha + */ + protected void send1001(String html, Player acha) + { + if (html.length() < 8192) + { + acha.sendPacket(new ShowBoard(html, "1001")); + } + } + + /** + * @param acha + */ + protected void send1002(Player acha) + { + send1002(acha, " ", " ", "0"); + } + + /** + * @param player + * @param string + * @param string2 + * @param string3 + */ + protected void send1002(Player player, String string, String string2, String string3) + { + final List arg = new ArrayList<>(20); + arg.add("0"); + arg.add("0"); + arg.add("0"); + arg.add("0"); + arg.add("0"); + arg.add("0"); + arg.add(player.getName()); + arg.add(Integer.toString(player.getObjectId())); + arg.add(player.getAccountName()); + arg.add("9"); + arg.add(string2); // subject? + arg.add(string2); // subject? + arg.add(string); // text + arg.add(string3); // date? + arg.add(string3); // date? + arg.add("0"); + arg.add("0"); + player.sendPacket(new ShowBoard(arg)); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/communitybbs/Manager/ForumsBBSManager.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/communitybbs/Manager/ForumsBBSManager.java new file mode 100644 index 0000000..b17252f --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/communitybbs/Manager/ForumsBBSManager.java @@ -0,0 +1,174 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.communitybbs.Manager; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.Statement; +import java.util.Collection; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.commons.database.DatabaseFactory; +import org.l2jmobius.gameserver.communitybbs.BB.Forum; +import org.l2jmobius.gameserver.entity.actor.Player; + +public class ForumsBBSManager extends BaseBBSManager +{ + private static final Logger LOGGER = Logger.getLogger(ForumsBBSManager.class.getName()); + private final Collection _table; + private int _lastid = 1; + + /** + * Instantiates a new forums bbs manager. + */ + protected ForumsBBSManager() + { + _table = ConcurrentHashMap.newKeySet(); + try (Connection con = DatabaseFactory.getConnection(); + Statement s = con.createStatement(); + ResultSet rs = s.executeQuery("SELECT forum_id FROM forums WHERE forum_type = 0")) + { + while (rs.next()) + { + addForum(new Forum(rs.getInt("forum_id"), null)); + } + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Data error on Forum (root): " + e.getMessage(), e); + } + } + + /** + * Inits the root. + */ + public void initRoot() + { + _table.forEach(Forum::vload); + LOGGER.info(getClass().getSimpleName() + ": Loaded " + _table.size() + " forums. Last forum id used: " + _lastid); + } + + /** + * Adds the forum. + * @param ff the forum + */ + public void addForum(Forum ff) + { + if (ff == null) + { + return; + } + + _table.add(ff); + + if (ff.getID() > _lastid) + { + _lastid = ff.getID(); + } + } + + @Override + public void parsecmd(String command, Player player) + { + } + + /** + * Gets the forum by name. + * @param name the forum name + * @return the forum by name + */ + public Forum getForumByName(String name) + { + for (Forum forum : _table) + { + if (forum.getName().equals(name)) + { + return forum; + } + } + + return null; + } + + /** + * Creates the new forum. + * @param name the forum name + * @param parent the parent forum + * @param type the forum type + * @param perm the perm + * @param oid the oid + * @return the new forum + */ + public Forum createNewForum(String name, Forum parent, int type, int perm, int oid) + { + final Forum forum = new Forum(name, parent, type, perm, oid); + forum.insertIntoDb(); + return forum; + } + + /** + * Gets the a new Id. + * @return the a new Id + */ + public int getANewID() + { + return ++_lastid; + } + + /** + * Gets the forum by Id. + * @param idf the the forum Id + * @return the forum by Id + */ + public Forum getForumByID(int idf) + { + for (Forum f : _table) + { + if (f.getID() == idf) + { + return f; + } + } + + return null; + } + + @Override + public void parsewrite(String ar1, String ar2, String ar3, String ar4, String ar5, Player player) + { + } + + /** + * Gets the single instance of ForumsBBSManager. + * @return single instance of ForumsBBSManager + */ + public static ForumsBBSManager getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final ForumsBBSManager INSTANCE = new ForumsBBSManager(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/communitybbs/Manager/PostBBSManager.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/communitybbs/Manager/PostBBSManager.java new file mode 100644 index 0000000..f97f459 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/communitybbs/Manager/PostBBSManager.java @@ -0,0 +1,191 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.communitybbs.Manager; + +import java.text.DateFormat; +import java.util.Date; +import java.util.Locale; +import java.util.Map; +import java.util.StringTokenizer; +import java.util.concurrent.ConcurrentHashMap; + +import org.l2jmobius.gameserver.communitybbs.BB.Forum; +import org.l2jmobius.gameserver.communitybbs.BB.Post; +import org.l2jmobius.gameserver.communitybbs.BB.Topic; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.handler.CommunityBoardHandler; + +public class PostBBSManager extends BaseBBSManager +{ + private final Map _postByTopic = new ConcurrentHashMap<>(); + + public Post getGPosttByTopic(Topic t) + { + Post post = _postByTopic.get(t); + if (post == null) + { + post = new Post(t); + _postByTopic.put(t, post); + } + + return post; + } + + public void delPostByTopic(Topic t) + { + _postByTopic.remove(t); + } + + public void addPostByTopic(Post p, Topic t) + { + if (_postByTopic.get(t) == null) + { + _postByTopic.put(t, p); + } + } + + @Override + public void parsecmd(String command, Player player) + { + if (command.startsWith("_bbsposts;read;")) + { + final StringTokenizer st = new StringTokenizer(command, ";"); + st.nextToken(); + st.nextToken(); + final int idf = Integer.parseInt(st.nextToken()); + final int idp = Integer.parseInt(st.nextToken()); + final String index = st.hasMoreTokens() ? st.nextToken() : null; + final int ind = index == null ? 1 : Integer.parseInt(index); + showPost(TopicBBSManager.getInstance().getTopicByID(idp), ForumsBBSManager.getInstance().getForumByID(idf), player, ind); + } + else if (command.startsWith("_bbsposts;edit;")) + { + final StringTokenizer st = new StringTokenizer(command, ";"); + st.nextToken(); + st.nextToken(); + final int idf = Integer.parseInt(st.nextToken()); + final int idt = Integer.parseInt(st.nextToken()); + final int idp = Integer.parseInt(st.nextToken()); + showEditPost(TopicBBSManager.getInstance().getTopicByID(idt), ForumsBBSManager.getInstance().getForumByID(idf), player, idp); + } + else + { + CommunityBoardHandler.separateAndSend("

    the command: " + command + " is not implemented yet


    ", player); + } + } + + private void showEditPost(Topic topic, Forum forum, Player player, int idp) + { + final Post p = getGPosttByTopic(topic); + if ((forum == null) || (topic == null) || (p == null)) + { + CommunityBoardHandler.separateAndSend("

    Error, this forum, topic or post does not exist!


    ", player); + } + else + { + showHtmlEditPost(topic, player, forum, p); + } + } + + private void showPost(Topic topic, Forum forum, Player player, int ind) + { + if ((forum == null) || (topic == null)) + { + CommunityBoardHandler.separateAndSend("

    Error: This forum is not implemented yet!
    ", player); + } + else if (forum.getType() == Forum.MEMO) + { + showMemoPost(topic, player, forum); + } + else + { + CommunityBoardHandler.separateAndSend("

    The forum: " + forum.getName() + " is not implemented yet!
    ", player); + } + } + + private void showHtmlEditPost(Topic topic, Player player, Forum forum, Post p) + { + final String html = "

    HOME > " + forum.getName() + " Form
    &$413;" + topic.getName() + "
    &$427;
      
    "; + send1001(html, player); + send1002(player, p.getCPost(0).getPostText(), topic.getName(), DateFormat.getInstance().format(new Date(topic.getDate()))); + } + + private void showMemoPost(Topic topic, Player player, Forum forum) + { + final Post p = getGPosttByTopic(topic); + final Locale locale = Locale.getDefault(); + final DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.FULL, locale); + String mes = p.getCPost(0).getPostText().replace(">", ">"); + mes = mes.replace("<", "<"); + + final String html = "

    HOME > Memo Form
    &$413; :  " + topic.getName() + "
    &$417; :  " + topic.getOwnerName() + "&$418; :" + dateFormat.format(p.getCPost(0).getPostDate()) + "

    " + mes + "

       



    "; + CommunityBoardHandler.separateAndSend(html, player); + } + + @Override + public void parsewrite(String ar1, String ar2, String ar3, String ar4, String ar5, Player player) + { + final StringTokenizer st = new StringTokenizer(ar1, ";"); + final int idf = Integer.parseInt(st.nextToken()); + final int idt = Integer.parseInt(st.nextToken()); + final int idp = Integer.parseInt(st.nextToken()); + final Forum f = ForumsBBSManager.getInstance().getForumByID(idf); + if (f == null) + { + CommunityBoardHandler.separateAndSend("

    the forum: " + idf + " does not exist !


    ", player); + } + else + { + final Topic t = f.getTopic(idt); + if (t == null) + { + CommunityBoardHandler.separateAndSend("

    the topic: " + idt + " does not exist !


    ", player); + } + else + { + final Post p = getGPosttByTopic(t); + if (p != null) + { + if (p.getCPost(idp) == null) + { + CommunityBoardHandler.separateAndSend("

    the post: " + idp + " does not exist !


    ", player); + } + else + { + p.getCPost(idp).setPostText(ar4); + p.updateText(idp); + parsecmd("_bbsposts;read;" + f.getID() + ";" + t.getID(), player); + } + } + } + } + } + + public static PostBBSManager getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final PostBBSManager INSTANCE = new PostBBSManager(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/communitybbs/Manager/TopicBBSManager.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/communitybbs/Manager/TopicBBSManager.java new file mode 100644 index 0000000..56129be --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/communitybbs/Manager/TopicBBSManager.java @@ -0,0 +1,311 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.communitybbs.Manager; + +import java.text.DateFormat; +import java.util.Collection; +import java.util.Date; +import java.util.Map; +import java.util.StringTokenizer; +import java.util.concurrent.ConcurrentHashMap; + +import org.l2jmobius.gameserver.communitybbs.TopicConstructorType; +import org.l2jmobius.gameserver.communitybbs.BB.Forum; +import org.l2jmobius.gameserver.communitybbs.BB.Post; +import org.l2jmobius.gameserver.communitybbs.BB.Topic; +import org.l2jmobius.gameserver.data.sql.ClanTable; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.handler.CommunityBoardHandler; + +public class TopicBBSManager extends BaseBBSManager +{ + private final Collection _table = ConcurrentHashMap.newKeySet(); + private final Map _maxId = new ConcurrentHashMap<>(); + + protected TopicBBSManager() + { + // Prevent external initialization. + } + + public void addTopic(Topic tt) + { + _table.add(tt); + } + + public void delTopic(Topic topic) + { + _table.remove(topic); + } + + public void setMaxID(int id, Forum f) + { + _maxId.put(f, id); + } + + public int getMaxID(Forum f) + { + final Integer i = _maxId.get(f); + return i == null ? 0 : i; + } + + public Topic getTopicByID(int idf) + { + for (Topic t : _table) + { + if (t.getID() == idf) + { + return t; + } + } + + return null; + } + + @Override + public void parsewrite(String ar1, String ar2, String ar3, String ar4, String ar5, Player player) + { + if (ar1.equals("crea")) + { + final Forum f = ForumsBBSManager.getInstance().getForumByID(Integer.parseInt(ar2)); + if (f == null) + { + CommunityBoardHandler.separateAndSend("

    the forum: " + ar2 + " is not implemented yet


    ", player); + } + else + { + final long currentTime = System.currentTimeMillis(); + f.vload(); + final Topic t = new Topic(TopicConstructorType.CREATE, getInstance().getMaxID(f) + 1, Integer.parseInt(ar2), ar5, currentTime, player.getName(), player.getObjectId(), Topic.MEMO, 0); + f.addTopic(t); + getInstance().setMaxID(t.getID(), f); + final Post p = new Post(player.getName(), player.getObjectId(), currentTime, t.getID(), f.getID(), ar4); + PostBBSManager.getInstance().addPostByTopic(p, t); + parsecmd("_bbsmemo", player); + } + } + else if (ar1.equals("del")) + { + final Forum f = ForumsBBSManager.getInstance().getForumByID(Integer.parseInt(ar2)); + if (f == null) + { + CommunityBoardHandler.separateAndSend("

    the forum: " + ar2 + " does not exist !


    ", player); + } + else + { + final Topic t = f.getTopic(Integer.parseInt(ar3)); + if (t == null) + { + CommunityBoardHandler.separateAndSend("

    the topic: " + ar3 + " does not exist !


    ", player); + } + else + { + // CPost cp = null; + final Post p = PostBBSManager.getInstance().getGPosttByTopic(t); + if (p != null) + { + p.deleteMe(t); + } + + t.deleteme(f); + parsecmd("_bbsmemo", player); + } + } + } + else + { + CommunityBoardHandler.separateAndSend("

    the command: " + ar1 + " is not implemented yet


    ", player); + } + } + + @Override + public void parsecmd(String command, Player player) + { + if (command.equals("_bbsmemo")) + { + showTopics(player.getMemo(), player, 1, player.getMemo().getID()); + } + else if (command.startsWith("_bbstopics;read")) + { + final StringTokenizer st = new StringTokenizer(command, ";"); + st.nextToken(); + st.nextToken(); + final int idf = Integer.parseInt(st.nextToken()); + final String index = st.hasMoreTokens() ? st.nextToken() : null; + final int ind = index == null ? 1 : Integer.parseInt(index); + showTopics(ForumsBBSManager.getInstance().getForumByID(idf), player, ind, idf); + } + else if (command.startsWith("_bbstopics;crea")) + { + final StringTokenizer st = new StringTokenizer(command, ";"); + st.nextToken(); + st.nextToken(); + final int idf = Integer.parseInt(st.nextToken()); + showNewTopic(ForumsBBSManager.getInstance().getForumByID(idf), player, idf); + } + else if (command.startsWith("_bbstopics;del")) + { + final StringTokenizer st = new StringTokenizer(command, ";"); + st.nextToken(); + st.nextToken(); + final int idf = Integer.parseInt(st.nextToken()); + final int idt = Integer.parseInt(st.nextToken()); + final Forum f = ForumsBBSManager.getInstance().getForumByID(idf); + if (f == null) + { + CommunityBoardHandler.separateAndSend("

    the forum: " + idf + " does not exist !


    ", player); + } + else + { + final Topic t = f.getTopic(idt); + if (t == null) + { + CommunityBoardHandler.separateAndSend("

    the topic: " + idt + " does not exist !


    ", player); + } + else + { + // CPost cp = null; + final Post p = PostBBSManager.getInstance().getGPosttByTopic(t); + if (p != null) + { + p.deleteMe(t); + } + + t.deleteme(f); + parsecmd("_bbsmemo", player); + } + } + } + else + { + CommunityBoardHandler.separateAndSend("

    the command: " + command + " is not implemented yet


    ", player); + } + } + + private void showNewTopic(Forum forum, Player player, int idf) + { + if (forum == null) + { + CommunityBoardHandler.separateAndSend("

    the forum: " + idf + " is not implemented yet


    ", player); + } + else if (forum.getType() == Forum.MEMO) + { + showMemoNewTopics(forum, player); + } + else + { + CommunityBoardHandler.separateAndSend("

    the forum: " + forum.getName() + " is not implemented yet


    ", player); + } + } + + private void showMemoNewTopics(Forum forum, Player player) + { + final String html = "

    HOME > Memo Form
    &$413;
    &$427;
      
    "; + send1001(html, player); + send1002(player); + } + + private void showTopics(Forum forum, Player player, int index, int idf) + { + if (forum == null) + { + CommunityBoardHandler.separateAndSend("

    the forum: " + idf + " is not implemented yet


    ", player); + } + else if (forum.getType() == Forum.MEMO) + { + showMemoTopics(forum, player, index); + } + else + { + CommunityBoardHandler.separateAndSend("

    the forum: " + forum.getName() + " is not implemented yet


    ", player); + } + } + + private void showMemoTopics(Forum forum, Player player, int index) + { + forum.vload(); + final StringBuilder html = new StringBuilder(2000); + html.append("

    HOME > Memo Form
    &$413;&$418;
    "); + final DateFormat dateFormat = DateFormat.getInstance(); + for (int i = 0, j = getMaxID(forum) + 1; i < (12 * index); j--) + { + if (j < 0) + { + break; + } + + final Topic t = forum.getTopic(j); + if ((t != null) && (i++ >= (12 * (index - 1)))) + { + html.append("
    " + t.getName() + "" + dateFormat.format(new Date(t.getDate())) + "
    "); + } + } + + html.append("
    "); + if (index == 1) + { + html.append(""); + } + else + { + html.append(""); + } + + int nbp = forum.getTopicSize() / 8; + if ((nbp * 8) != ClanTable.getInstance().getClanCount()) + { + nbp++; + } + + for (int i = 1; i <= nbp; i++) + { + if (i == index) + { + html.append(""); + } + else + { + html.append(""); + } + } + + if (index == nbp) + { + html.append(""); + } + else + { + html.append(""); + } + + html.append("
    " + i + " " + i + "



    "); + CommunityBoardHandler.separateAndSend(html.toString(), player); + } + + public static TopicBBSManager getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final TopicBBSManager INSTANCE = new TopicBBSManager(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/communitybbs/TopicConstructorType.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/communitybbs/TopicConstructorType.java new file mode 100644 index 0000000..0e5bb6a --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/communitybbs/TopicConstructorType.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.communitybbs; + +public enum TopicConstructorType +{ + RESTORE, + CREATE +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/ConfigLoader.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/ConfigLoader.java new file mode 100644 index 0000000..bb3c20b --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/ConfigLoader.java @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.config; + +import org.l2jmobius.gameserver.config.custom.AllowedPlayerRacesConfig; +import org.l2jmobius.gameserver.config.custom.AutoPlayConfig; +import org.l2jmobius.gameserver.config.custom.AutoPotionsConfig; +import org.l2jmobius.gameserver.config.custom.BankingConfig; +import org.l2jmobius.gameserver.config.custom.BossAnnouncementsConfig; +import org.l2jmobius.gameserver.config.custom.CancelReturnConfig; +import org.l2jmobius.gameserver.config.custom.CaptchaConfig; +import org.l2jmobius.gameserver.config.custom.ChampionMonstersConfig; +import org.l2jmobius.gameserver.config.custom.ChatModerationConfig; +import org.l2jmobius.gameserver.config.custom.ClassBalanceConfig; +import org.l2jmobius.gameserver.config.custom.CommunityBoardConfig; +import org.l2jmobius.gameserver.config.custom.CustomMailManagerConfig; +import org.l2jmobius.gameserver.config.custom.DelevelManagerConfig; +import org.l2jmobius.gameserver.config.custom.DualboxCheckConfig; +import org.l2jmobius.gameserver.config.custom.FactionSystemConfig; +import org.l2jmobius.gameserver.config.custom.FakePlayersConfig; +import org.l2jmobius.gameserver.config.custom.FindPvpConfig; +import org.l2jmobius.gameserver.config.custom.FreeMountsConfig; +import org.l2jmobius.gameserver.config.custom.MerchantZeroSellPriceConfig; +import org.l2jmobius.gameserver.config.custom.MultilingualSupportConfig; +import org.l2jmobius.gameserver.config.custom.NoblessMasterConfig; +import org.l2jmobius.gameserver.config.custom.NpcStatMultipliersConfig; +import org.l2jmobius.gameserver.config.custom.OfflinePlayConfig; +import org.l2jmobius.gameserver.config.custom.OfflineTradeConfig; +import org.l2jmobius.gameserver.config.custom.OnlineInfoConfig; +import org.l2jmobius.gameserver.config.custom.PasswordChangeConfig; +import org.l2jmobius.gameserver.config.custom.PremiumSystemConfig; +import org.l2jmobius.gameserver.config.custom.PrivateStoreRangeConfig; +import org.l2jmobius.gameserver.config.custom.PvpAnnounceConfig; +import org.l2jmobius.gameserver.config.custom.PvpRewardItemConfig; +import org.l2jmobius.gameserver.config.custom.PvpTitleColorConfig; +import org.l2jmobius.gameserver.config.custom.RandomSpawnsConfig; +import org.l2jmobius.gameserver.config.custom.RebirthConfig; +import org.l2jmobius.gameserver.config.custom.SchemeBufferConfig; +import org.l2jmobius.gameserver.config.custom.ScreenWelcomeMessageConfig; +import org.l2jmobius.gameserver.config.custom.SellBuffsConfig; +import org.l2jmobius.gameserver.config.custom.ServerTimeConfig; +import org.l2jmobius.gameserver.config.custom.StartingLocationConfig; +import org.l2jmobius.gameserver.config.custom.StartingTitleConfig; +import org.l2jmobius.gameserver.config.custom.TransmogConfig; +import org.l2jmobius.gameserver.config.custom.WalkerBotProtectionConfig; +import org.l2jmobius.gameserver.config.custom.WarehouseSortingConfig; +import org.l2jmobius.gameserver.config.custom.WeddingConfig; + +/** + * Central configuration loader for initializing all server configuration components.
    + * This class serves as the entry point for loading all server configuration settings from various configuration files.
    + * The configurations are typically located in the config directory within the server root folder. + * @author Mobius + */ +public class ConfigLoader +{ + public static void init() + { + ServerConfig.load(); + + // Main configurations. + ConquerableHallSiegeConfig.load(); + DevelopmentConfig.load(); + FeatureConfig.load(); + FloodProtectorConfig.load(); + GeneralConfig.load(); + GeoEngineConfig.load(); + GrandBossConfig.load(); + IdManagerConfig.load(); + NpcConfig.load(); + OlympiadConfig.load(); + PlayerConfig.load(); + PvpConfig.load(); + RatesConfig.load(); + + // Custom configurations. + AllowedPlayerRacesConfig.load(); + AutoPlayConfig.load(); + AutoPotionsConfig.load(); + BankingConfig.load(); + BossAnnouncementsConfig.load(); + CancelReturnConfig.load(); + CaptchaConfig.load(); + ChampionMonstersConfig.load(); + ChatModerationConfig.load(); + ClassBalanceConfig.load(); + CommunityBoardConfig.load(); + CustomMailManagerConfig.load(); + DelevelManagerConfig.load(); + DualboxCheckConfig.load(); + FactionSystemConfig.load(); + FakePlayersConfig.load(); + FindPvpConfig.load(); + FreeMountsConfig.load(); + MerchantZeroSellPriceConfig.load(); + MultilingualSupportConfig.load(); + NoblessMasterConfig.load(); + NpcStatMultipliersConfig.load(); + OfflinePlayConfig.load(); + OfflineTradeConfig.load(); + OnlineInfoConfig.load(); + PasswordChangeConfig.load(); + PremiumSystemConfig.load(); + PrivateStoreRangeConfig.load(); + PvpAnnounceConfig.load(); + PvpRewardItemConfig.load(); + PvpTitleColorConfig.load(); + RandomSpawnsConfig.load(); + SchemeBufferConfig.load(); + ScreenWelcomeMessageConfig.load(); + SellBuffsConfig.load(); + ServerTimeConfig.load(); + StartingLocationConfig.load(); + StartingTitleConfig.load(); + TransmogConfig.load(); + WalkerBotProtectionConfig.load(); + WarehouseSortingConfig.load(); + WeddingConfig.load(); + RebirthConfig.load(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/ConquerableHallSiegeConfig.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/ConquerableHallSiegeConfig.java new file mode 100644 index 0000000..9f2e631 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/ConquerableHallSiegeConfig.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.config; + +import org.l2jmobius.commons.util.ConfigReader; + +/** + * This class loads all the conquerable hall siege related configurations. + * @author Mobius + */ +public class ConquerableHallSiegeConfig +{ + // File + private static final String CONQUERABLE_HALL_SIEGE_CONFIG_FILE = "./config/ConquerableHallSiege.ini"; + + // Constants + public static int CHS_MAX_ATTACKERS; + public static int CHS_CLAN_MINLEVEL; + public static int CHS_MAX_FLAGS_PER_CLAN; + public static boolean CHS_ENABLE_FAME; + public static int CHS_FAME_AMOUNT; + public static int CHS_FAME_FREQUENCY; + + public static void load() + { + final ConfigReader config = new ConfigReader(CONQUERABLE_HALL_SIEGE_CONFIG_FILE); + CHS_MAX_ATTACKERS = config.getInt("MaxAttackers", 500); + CHS_CLAN_MINLEVEL = config.getInt("MinClanLevel", 4); + CHS_MAX_FLAGS_PER_CLAN = config.getInt("MaxFlagsPerClan", 1); + CHS_ENABLE_FAME = config.getBoolean("EnableFame", false); + CHS_FAME_AMOUNT = config.getInt("FameAmount", 0); + CHS_FAME_FREQUENCY = config.getInt("FameFrequency", 0); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/DevelopmentConfig.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/DevelopmentConfig.java new file mode 100644 index 0000000..e4012de --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/DevelopmentConfig.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.config; + +import java.util.HashSet; +import java.util.Set; + +import org.l2jmobius.commons.util.ConfigReader; + +/** + * This class loads all the development related configurations. + * @author Mobius + */ +public class DevelopmentConfig +{ + // File + private static final String DEVELOPMENT_CONFIG_FILE = "./config/Development.ini"; + + // Constants + public static boolean LOG_SERVER_LOAD_TIMES; + public static boolean HTML_ACTION_CACHE_DEBUG; + public static boolean NO_QUESTS; + public static boolean NO_SPAWNS; + public static boolean SHOW_QUEST_LOAD_IN_LOGS; + public static boolean SHOW_SCRIPT_LOAD_IN_LOGS; + public static boolean DEBUG_CLIENT_PACKETS; + public static boolean DEBUG_EX_CLIENT_PACKETS; + public static boolean DEBUG_SERVER_PACKETS; + public static boolean DEBUG_UNKNOWN_PACKETS; + public static Set EXCLUDED_DEBUG_PACKETS; + + public static void load() + { + final ConfigReader config = new ConfigReader(DEVELOPMENT_CONFIG_FILE); + LOG_SERVER_LOAD_TIMES = config.getBoolean("LogServerLoadTimes", false); + HTML_ACTION_CACHE_DEBUG = config.getBoolean("HtmlActionCacheDebug", false); + NO_QUESTS = config.getBoolean("NoQuests", false); + NO_SPAWNS = config.getBoolean("NoSpawns", false); + SHOW_QUEST_LOAD_IN_LOGS = config.getBoolean("ShowQuestLoadInLogs", false); + SHOW_SCRIPT_LOAD_IN_LOGS = config.getBoolean("ShowScriptLoadInLogs", false); + DEBUG_CLIENT_PACKETS = config.getBoolean("DebugClientPackets", false); + DEBUG_EX_CLIENT_PACKETS = config.getBoolean("DebugExClientPackets", false); + DEBUG_SERVER_PACKETS = config.getBoolean("DebugServerPackets", false); + DEBUG_UNKNOWN_PACKETS = config.getBoolean("DebugUnknownPackets", true); + final String[] packets = config.getString("ExcludedPacketList", "").trim().split(","); + EXCLUDED_DEBUG_PACKETS = new HashSet<>(packets.length); + for (String packet : packets) + { + EXCLUDED_DEBUG_PACKETS.add(packet.trim()); + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/FeatureConfig.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/FeatureConfig.java new file mode 100644 index 0000000..a60aded --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/FeatureConfig.java @@ -0,0 +1,387 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.config; + +import java.util.ArrayList; +import java.util.List; + +import org.l2jmobius.commons.util.ConfigReader; +import org.l2jmobius.commons.util.StringUtil; + +/** + * This class loads all the feature related configurations. + * @author Mobius + */ +public class FeatureConfig +{ + // File + private static final String FEATURE_CONFIG_FILE = "./config/Feature.ini"; + + // Constants + public static long CH_TELE_FEE_RATIO; + public static int CH_TELE1_FEE; + public static int CH_TELE2_FEE; + public static long CH_SUPPORT_FEE_RATIO; + public static int CH_SUPPORT1_FEE; + public static int CH_SUPPORT2_FEE; + public static int CH_SUPPORT3_FEE; + public static int CH_SUPPORT4_FEE; + public static int CH_SUPPORT5_FEE; + public static int CH_SUPPORT6_FEE; + public static int CH_SUPPORT7_FEE; + public static int CH_SUPPORT8_FEE; + public static long CH_MPREG_FEE_RATIO; + public static int CH_MPREG1_FEE; + public static int CH_MPREG2_FEE; + public static int CH_MPREG3_FEE; + public static int CH_MPREG4_FEE; + public static int CH_MPREG5_FEE; + public static long CH_HPREG_FEE_RATIO; + public static int CH_HPREG1_FEE; + public static int CH_HPREG2_FEE; + public static int CH_HPREG3_FEE; + public static int CH_HPREG4_FEE; + public static int CH_HPREG5_FEE; + public static int CH_HPREG6_FEE; + public static int CH_HPREG7_FEE; + public static int CH_HPREG8_FEE; + public static int CH_HPREG9_FEE; + public static int CH_HPREG10_FEE; + public static int CH_HPREG11_FEE; + public static int CH_HPREG12_FEE; + public static int CH_HPREG13_FEE; + public static long CH_EXPREG_FEE_RATIO; + public static int CH_EXPREG1_FEE; + public static int CH_EXPREG2_FEE; + public static int CH_EXPREG3_FEE; + public static int CH_EXPREG4_FEE; + public static int CH_EXPREG5_FEE; + public static int CH_EXPREG6_FEE; + public static int CH_EXPREG7_FEE; + public static long CH_ITEM_FEE_RATIO; + public static int CH_ITEM1_FEE; + public static int CH_ITEM2_FEE; + public static int CH_ITEM3_FEE; + public static long CH_CURTAIN_FEE_RATIO; + public static int CH_CURTAIN1_FEE; + public static int CH_CURTAIN2_FEE; + public static long CH_FRONT_FEE_RATIO; + public static int CH_FRONT1_FEE; + public static int CH_FRONT2_FEE; + public static boolean CH_BUFF_FREE; + public static List SIEGE_HOUR_LIST; + public static long CS_TELE_FEE_RATIO; + public static int CS_TELE1_FEE; + public static int CS_TELE2_FEE; + public static long CS_SUPPORT_FEE_RATIO; + public static int CS_SUPPORT1_FEE; + public static int CS_SUPPORT2_FEE; + public static long CS_MPREG_FEE_RATIO; + public static int CS_MPREG1_FEE; + public static int CS_MPREG2_FEE; + public static long CS_HPREG_FEE_RATIO; + public static int CS_HPREG1_FEE; + public static int CS_HPREG2_FEE; + public static long CS_EXPREG_FEE_RATIO; + public static int CS_EXPREG1_FEE; + public static int CS_EXPREG2_FEE; + public static int OUTER_DOOR_UPGRADE_PRICE2; + public static int OUTER_DOOR_UPGRADE_PRICE3; + public static int OUTER_DOOR_UPGRADE_PRICE5; + public static int INNER_DOOR_UPGRADE_PRICE2; + public static int INNER_DOOR_UPGRADE_PRICE3; + public static int INNER_DOOR_UPGRADE_PRICE5; + public static int WALL_UPGRADE_PRICE2; + public static int WALL_UPGRADE_PRICE3; + public static int WALL_UPGRADE_PRICE5; + public static int TRAP_UPGRADE_PRICE1; + public static int TRAP_UPGRADE_PRICE2; + public static int TRAP_UPGRADE_PRICE3; + public static int TRAP_UPGRADE_PRICE4; + public static long FS_TELE_FEE_RATIO; + public static int FS_TELE1_FEE; + public static int FS_TELE2_FEE; + public static long FS_SUPPORT_FEE_RATIO; + public static int FS_SUPPORT1_FEE; + public static int FS_SUPPORT2_FEE; + public static long FS_MPREG_FEE_RATIO; + public static int FS_MPREG1_FEE; + public static int FS_MPREG2_FEE; + public static long FS_HPREG_FEE_RATIO; + public static int FS_HPREG1_FEE; + public static int FS_HPREG2_FEE; + public static long FS_EXPREG_FEE_RATIO; + public static int FS_EXPREG1_FEE; + public static int FS_EXPREG2_FEE; + public static int FS_UPDATE_FRQ; + public static int FS_BLOOD_OATH_COUNT; + public static int FS_MAX_SUPPLY_LEVEL; + public static int FS_FEE_FOR_CASTLE; + public static int FS_MAX_OWN_TIME; + public static boolean ALT_SEVENSIGNS_OPEN_CATACUMBS; + public static boolean ALT_SEVENSIGNS_OPEN_NECROPOLIS; + public static boolean ALT_GAME_CASTLE_DAWN; + public static boolean ALT_GAME_CASTLE_DUSK; + public static boolean ALT_GAME_REQUIRE_CLAN_CASTLE; + public static int ALT_FESTIVAL_MIN_PLAYER; + public static int ALT_MAXIMUM_PLAYER_CONTRIB; + public static long ALT_FESTIVAL_MANAGER_START; + public static long ALT_FESTIVAL_LENGTH; + public static long ALT_FESTIVAL_CYCLE_LENGTH; + public static long ALT_FESTIVAL_FIRST_SPAWN; + public static long ALT_FESTIVAL_FIRST_SWARM; + public static long ALT_FESTIVAL_SECOND_SPAWN; + public static long ALT_FESTIVAL_SECOND_SWARM; + public static long ALT_FESTIVAL_CHEST_SPAWN; + public static double ALT_SIEGE_DAWN_GATES_PDEF_MULT; + public static double ALT_SIEGE_DUSK_GATES_PDEF_MULT; + public static double ALT_SIEGE_DAWN_GATES_MDEF_MULT; + public static double ALT_SIEGE_DUSK_GATES_MDEF_MULT; + public static boolean ALT_STRICT_SEVENSIGNS; + public static boolean ALT_SEVENSIGNS_LAZY_UPDATE; + public static int SSQ_DAWN_TICKET_QUANTITY; + public static int SSQ_DAWN_TICKET_PRICE; + public static int SSQ_DAWN_TICKET_BUNDLE; + public static int SSQ_MANORS_AGREEMENT_ID; + public static int SSQ_JOIN_DAWN_ADENA_FEE; + public static int TAKE_FORT_POINTS; + public static int LOOSE_FORT_POINTS; + public static int TAKE_CASTLE_POINTS; + public static int LOOSE_CASTLE_POINTS; + public static int CASTLE_DEFENDED_POINTS; + public static int FESTIVAL_WIN_POINTS; + public static int HERO_POINTS; + public static int ROYAL_GUARD_COST; + public static int KNIGHT_UNIT_COST; + public static int KNIGHT_REINFORCE_COST; + public static int BALLISTA_POINTS; + public static int BLOODALLIANCE_POINTS; + public static int BLOODOATH_POINTS; + public static int KNIGHTSEPAULETTE_POINTS; + public static int REPUTATION_SCORE_PER_KILL; + public static int JOIN_ACADEMY_MIN_REP_SCORE; + public static int JOIN_ACADEMY_MAX_REP_SCORE; + public static int RAID_RANKING_1ST; + public static int RAID_RANKING_2ND; + public static int RAID_RANKING_3RD; + public static int RAID_RANKING_4TH; + public static int RAID_RANKING_5TH; + public static int RAID_RANKING_6TH; + public static int RAID_RANKING_7TH; + public static int RAID_RANKING_8TH; + public static int RAID_RANKING_9TH; + public static int RAID_RANKING_10TH; + public static int RAID_RANKING_UP_TO_50TH; + public static int RAID_RANKING_UP_TO_100TH; + public static int CLAN_LEVEL_6_COST; + public static int CLAN_LEVEL_7_COST; + public static int CLAN_LEVEL_8_COST; + public static int CLAN_LEVEL_9_COST; + public static int CLAN_LEVEL_10_COST; + public static int CLAN_LEVEL_6_REQUIREMENT; + public static int CLAN_LEVEL_7_REQUIREMENT; + public static int CLAN_LEVEL_8_REQUIREMENT; + public static int CLAN_LEVEL_9_REQUIREMENT; + public static int CLAN_LEVEL_10_REQUIREMENT; + public static boolean ALLOW_WYVERN_ALWAYS; + public static boolean ALLOW_WYVERN_DURING_SIEGE; + public static boolean ALLOW_MOUNTS_DURING_SIEGE; + + public static void load() + { + final ConfigReader config = new ConfigReader(FEATURE_CONFIG_FILE); + CH_TELE_FEE_RATIO = config.getLong("ClanHallTeleportFunctionFeeRatio", 604800000); + CH_TELE1_FEE = config.getInt("ClanHallTeleportFunctionFeeLvl1", 7000); + CH_TELE2_FEE = config.getInt("ClanHallTeleportFunctionFeeLvl2", 14000); + CH_SUPPORT_FEE_RATIO = config.getLong("ClanHallSupportFunctionFeeRatio", 86400000); + CH_SUPPORT1_FEE = config.getInt("ClanHallSupportFeeLvl1", 2500); + CH_SUPPORT2_FEE = config.getInt("ClanHallSupportFeeLvl2", 5000); + CH_SUPPORT3_FEE = config.getInt("ClanHallSupportFeeLvl3", 7000); + CH_SUPPORT4_FEE = config.getInt("ClanHallSupportFeeLvl4", 11000); + CH_SUPPORT5_FEE = config.getInt("ClanHallSupportFeeLvl5", 21000); + CH_SUPPORT6_FEE = config.getInt("ClanHallSupportFeeLvl6", 36000); + CH_SUPPORT7_FEE = config.getInt("ClanHallSupportFeeLvl7", 37000); + CH_SUPPORT8_FEE = config.getInt("ClanHallSupportFeeLvl8", 52000); + CH_MPREG_FEE_RATIO = config.getLong("ClanHallMpRegenerationFunctionFeeRatio", 86400000); + CH_MPREG1_FEE = config.getInt("ClanHallMpRegenerationFeeLvl1", 2000); + CH_MPREG2_FEE = config.getInt("ClanHallMpRegenerationFeeLvl2", 3750); + CH_MPREG3_FEE = config.getInt("ClanHallMpRegenerationFeeLvl3", 6500); + CH_MPREG4_FEE = config.getInt("ClanHallMpRegenerationFeeLvl4", 13750); + CH_MPREG5_FEE = config.getInt("ClanHallMpRegenerationFeeLvl5", 20000); + CH_HPREG_FEE_RATIO = config.getLong("ClanHallHpRegenerationFunctionFeeRatio", 86400000); + CH_HPREG1_FEE = config.getInt("ClanHallHpRegenerationFeeLvl1", 700); + CH_HPREG2_FEE = config.getInt("ClanHallHpRegenerationFeeLvl2", 800); + CH_HPREG3_FEE = config.getInt("ClanHallHpRegenerationFeeLvl3", 1000); + CH_HPREG4_FEE = config.getInt("ClanHallHpRegenerationFeeLvl4", 1166); + CH_HPREG5_FEE = config.getInt("ClanHallHpRegenerationFeeLvl5", 1500); + CH_HPREG6_FEE = config.getInt("ClanHallHpRegenerationFeeLvl6", 1750); + CH_HPREG7_FEE = config.getInt("ClanHallHpRegenerationFeeLvl7", 2000); + CH_HPREG8_FEE = config.getInt("ClanHallHpRegenerationFeeLvl8", 2250); + CH_HPREG9_FEE = config.getInt("ClanHallHpRegenerationFeeLvl9", 2500); + CH_HPREG10_FEE = config.getInt("ClanHallHpRegenerationFeeLvl10", 3250); + CH_HPREG11_FEE = config.getInt("ClanHallHpRegenerationFeeLvl11", 3270); + CH_HPREG12_FEE = config.getInt("ClanHallHpRegenerationFeeLvl12", 4250); + CH_HPREG13_FEE = config.getInt("ClanHallHpRegenerationFeeLvl13", 5166); + CH_EXPREG_FEE_RATIO = config.getLong("ClanHallExpRegenerationFunctionFeeRatio", 86400000); + CH_EXPREG1_FEE = config.getInt("ClanHallExpRegenerationFeeLvl1", 3000); + CH_EXPREG2_FEE = config.getInt("ClanHallExpRegenerationFeeLvl2", 6000); + CH_EXPREG3_FEE = config.getInt("ClanHallExpRegenerationFeeLvl3", 9000); + CH_EXPREG4_FEE = config.getInt("ClanHallExpRegenerationFeeLvl4", 15000); + CH_EXPREG5_FEE = config.getInt("ClanHallExpRegenerationFeeLvl5", 21000); + CH_EXPREG6_FEE = config.getInt("ClanHallExpRegenerationFeeLvl6", 23330); + CH_EXPREG7_FEE = config.getInt("ClanHallExpRegenerationFeeLvl7", 30000); + CH_ITEM_FEE_RATIO = config.getLong("ClanHallItemCreationFunctionFeeRatio", 86400000); + CH_ITEM1_FEE = config.getInt("ClanHallItemCreationFunctionFeeLvl1", 30000); + CH_ITEM2_FEE = config.getInt("ClanHallItemCreationFunctionFeeLvl2", 70000); + CH_ITEM3_FEE = config.getInt("ClanHallItemCreationFunctionFeeLvl3", 140000); + CH_CURTAIN_FEE_RATIO = config.getLong("ClanHallCurtainFunctionFeeRatio", 604800000); + CH_CURTAIN1_FEE = config.getInt("ClanHallCurtainFunctionFeeLvl1", 2000); + CH_CURTAIN2_FEE = config.getInt("ClanHallCurtainFunctionFeeLvl2", 2500); + CH_FRONT_FEE_RATIO = config.getLong("ClanHallFrontPlatformFunctionFeeRatio", 259200000); + CH_FRONT1_FEE = config.getInt("ClanHallFrontPlatformFunctionFeeLvl1", 1300); + CH_FRONT2_FEE = config.getInt("ClanHallFrontPlatformFunctionFeeLvl2", 4000); + CH_BUFF_FREE = config.getBoolean("AltClanHallMpBuffFree", false); + SIEGE_HOUR_LIST = new ArrayList<>(); + for (String hour : config.getString("SiegeHourList", "").split(",")) + { + if (StringUtil.isNumeric(hour)) + { + SIEGE_HOUR_LIST.add(Integer.parseInt(hour)); + } + } + CS_TELE_FEE_RATIO = config.getLong("CastleTeleportFunctionFeeRatio", 604800000); + CS_TELE1_FEE = config.getInt("CastleTeleportFunctionFeeLvl1", 1000); + CS_TELE2_FEE = config.getInt("CastleTeleportFunctionFeeLvl2", 10000); + CS_SUPPORT_FEE_RATIO = config.getLong("CastleSupportFunctionFeeRatio", 604800000); + CS_SUPPORT1_FEE = config.getInt("CastleSupportFeeLvl1", 49000); + CS_SUPPORT2_FEE = config.getInt("CastleSupportFeeLvl2", 120000); + CS_MPREG_FEE_RATIO = config.getLong("CastleMpRegenerationFunctionFeeRatio", 604800000); + CS_MPREG1_FEE = config.getInt("CastleMpRegenerationFeeLvl1", 45000); + CS_MPREG2_FEE = config.getInt("CastleMpRegenerationFeeLvl2", 65000); + CS_HPREG_FEE_RATIO = config.getLong("CastleHpRegenerationFunctionFeeRatio", 604800000); + CS_HPREG1_FEE = config.getInt("CastleHpRegenerationFeeLvl1", 12000); + CS_HPREG2_FEE = config.getInt("CastleHpRegenerationFeeLvl2", 20000); + CS_EXPREG_FEE_RATIO = config.getLong("CastleExpRegenerationFunctionFeeRatio", 604800000); + CS_EXPREG1_FEE = config.getInt("CastleExpRegenerationFeeLvl1", 63000); + CS_EXPREG2_FEE = config.getInt("CastleExpRegenerationFeeLvl2", 70000); + OUTER_DOOR_UPGRADE_PRICE2 = config.getInt("OuterDoorUpgradePriceLvl2", 3000000); + OUTER_DOOR_UPGRADE_PRICE3 = config.getInt("OuterDoorUpgradePriceLvl3", 4000000); + OUTER_DOOR_UPGRADE_PRICE5 = config.getInt("OuterDoorUpgradePriceLvl5", 5000000); + INNER_DOOR_UPGRADE_PRICE2 = config.getInt("InnerDoorUpgradePriceLvl2", 750000); + INNER_DOOR_UPGRADE_PRICE3 = config.getInt("InnerDoorUpgradePriceLvl3", 900000); + INNER_DOOR_UPGRADE_PRICE5 = config.getInt("InnerDoorUpgradePriceLvl5", 1000000); + WALL_UPGRADE_PRICE2 = config.getInt("WallUpgradePriceLvl2", 1600000); + WALL_UPGRADE_PRICE3 = config.getInt("WallUpgradePriceLvl3", 1800000); + WALL_UPGRADE_PRICE5 = config.getInt("WallUpgradePriceLvl5", 2000000); + TRAP_UPGRADE_PRICE1 = config.getInt("TrapUpgradePriceLvl1", 3000000); + TRAP_UPGRADE_PRICE2 = config.getInt("TrapUpgradePriceLvl2", 4000000); + TRAP_UPGRADE_PRICE3 = config.getInt("TrapUpgradePriceLvl3", 5000000); + TRAP_UPGRADE_PRICE4 = config.getInt("TrapUpgradePriceLvl4", 6000000); + FS_TELE_FEE_RATIO = config.getLong("FortressTeleportFunctionFeeRatio", 604800000); + FS_TELE1_FEE = config.getInt("FortressTeleportFunctionFeeLvl1", 1000); + FS_TELE2_FEE = config.getInt("FortressTeleportFunctionFeeLvl2", 10000); + FS_SUPPORT_FEE_RATIO = config.getLong("FortressSupportFunctionFeeRatio", 86400000); + FS_SUPPORT1_FEE = config.getInt("FortressSupportFeeLvl1", 7000); + FS_SUPPORT2_FEE = config.getInt("FortressSupportFeeLvl2", 17000); + FS_MPREG_FEE_RATIO = config.getLong("FortressMpRegenerationFunctionFeeRatio", 86400000); + FS_MPREG1_FEE = config.getInt("FortressMpRegenerationFeeLvl1", 6500); + FS_MPREG2_FEE = config.getInt("FortressMpRegenerationFeeLvl2", 9300); + FS_HPREG_FEE_RATIO = config.getLong("FortressHpRegenerationFunctionFeeRatio", 86400000); + FS_HPREG1_FEE = config.getInt("FortressHpRegenerationFeeLvl1", 2000); + FS_HPREG2_FEE = config.getInt("FortressHpRegenerationFeeLvl2", 3500); + FS_EXPREG_FEE_RATIO = config.getLong("FortressExpRegenerationFunctionFeeRatio", 86400000); + FS_EXPREG1_FEE = config.getInt("FortressExpRegenerationFeeLvl1", 9000); + FS_EXPREG2_FEE = config.getInt("FortressExpRegenerationFeeLvl2", 10000); + FS_UPDATE_FRQ = config.getInt("FortressPeriodicUpdateFrequency", 360); + FS_BLOOD_OATH_COUNT = config.getInt("FortressBloodOathCount", 1); + FS_MAX_SUPPLY_LEVEL = config.getInt("FortressMaxSupplyLevel", 6); + FS_FEE_FOR_CASTLE = config.getInt("FortressFeeForCastle", 25000); + FS_MAX_OWN_TIME = config.getInt("FortressMaximumOwnTime", 168); + ALT_SEVENSIGNS_OPEN_CATACUMBS = config.getBoolean("AltOpenCatacumbs", false); + ALT_SEVENSIGNS_OPEN_NECROPOLIS = config.getBoolean("AltOpenNecropolis", false); + ALT_GAME_CASTLE_DAWN = config.getBoolean("AltCastleForDawn", true); + ALT_GAME_CASTLE_DUSK = config.getBoolean("AltCastleForDusk", true); + ALT_GAME_REQUIRE_CLAN_CASTLE = config.getBoolean("AltRequireClanCastle", false); + ALT_FESTIVAL_MIN_PLAYER = config.getInt("AltFestivalMinPlayer", 5); + ALT_MAXIMUM_PLAYER_CONTRIB = config.getInt("AltMaxPlayerContrib", 1000000); + ALT_FESTIVAL_MANAGER_START = config.getLong("AltFestivalManagerStart", 120000); + ALT_FESTIVAL_LENGTH = config.getLong("AltFestivalLength", 1080000); + ALT_FESTIVAL_CYCLE_LENGTH = config.getLong("AltFestivalCycleLength", 2280000); + ALT_FESTIVAL_FIRST_SPAWN = config.getLong("AltFestivalFirstSpawn", 120000); + ALT_FESTIVAL_FIRST_SWARM = config.getLong("AltFestivalFirstSwarm", 300000); + ALT_FESTIVAL_SECOND_SPAWN = config.getLong("AltFestivalSecondSpawn", 540000); + ALT_FESTIVAL_SECOND_SWARM = config.getLong("AltFestivalSecondSwarm", 720000); + ALT_FESTIVAL_CHEST_SPAWN = config.getLong("AltFestivalChestSpawn", 900000); + ALT_SIEGE_DAWN_GATES_PDEF_MULT = config.getDouble("AltDawnGatesPdefMult", 1.1); + ALT_SIEGE_DUSK_GATES_PDEF_MULT = config.getDouble("AltDuskGatesPdefMult", 0.8); + ALT_SIEGE_DAWN_GATES_MDEF_MULT = config.getDouble("AltDawnGatesMdefMult", 1.1); + ALT_SIEGE_DUSK_GATES_MDEF_MULT = config.getDouble("AltDuskGatesMdefMult", 0.8); + ALT_STRICT_SEVENSIGNS = config.getBoolean("StrictSevenSigns", true); + ALT_SEVENSIGNS_LAZY_UPDATE = config.getBoolean("AltSevenSignsLazyUpdate", true); + SSQ_DAWN_TICKET_QUANTITY = config.getInt("SevenSignsDawnTicketQuantity", 300); + SSQ_DAWN_TICKET_PRICE = config.getInt("SevenSignsDawnTicketPrice", 1000); + SSQ_DAWN_TICKET_BUNDLE = config.getInt("SevenSignsDawnTicketBundle", 10); + SSQ_MANORS_AGREEMENT_ID = config.getInt("SevenSignsManorsAgreementId", 6388); + SSQ_JOIN_DAWN_ADENA_FEE = config.getInt("SevenSignsJoinDawnFee", 50000); + TAKE_FORT_POINTS = config.getInt("TakeFortPoints", 200); + LOOSE_FORT_POINTS = config.getInt("LooseFortPoints", 0); + TAKE_CASTLE_POINTS = config.getInt("TakeCastlePoints", 1500); + LOOSE_CASTLE_POINTS = config.getInt("LooseCastlePoints", 3000); + CASTLE_DEFENDED_POINTS = config.getInt("CastleDefendedPoints", 750); + FESTIVAL_WIN_POINTS = config.getInt("FestivalOfDarknessWin", 200); + HERO_POINTS = config.getInt("HeroPoints", 1000); + ROYAL_GUARD_COST = config.getInt("CreateRoyalGuardCost", 5000); + KNIGHT_UNIT_COST = config.getInt("CreateKnightUnitCost", 10000); + KNIGHT_REINFORCE_COST = config.getInt("ReinforceKnightUnitCost", 5000); + BALLISTA_POINTS = config.getInt("KillBallistaPoints", 30); + BLOODALLIANCE_POINTS = config.getInt("BloodAlliancePoints", 500); + BLOODOATH_POINTS = config.getInt("BloodOathPoints", 200); + KNIGHTSEPAULETTE_POINTS = config.getInt("KnightsEpaulettePoints", 20); + REPUTATION_SCORE_PER_KILL = config.getInt("ReputationScorePerKill", 1); + JOIN_ACADEMY_MIN_REP_SCORE = config.getInt("CompleteAcademyMinPoints", 190); + JOIN_ACADEMY_MAX_REP_SCORE = config.getInt("CompleteAcademyMaxPoints", 650); + RAID_RANKING_1ST = config.getInt("1stRaidRankingPoints", 1250); + RAID_RANKING_2ND = config.getInt("2ndRaidRankingPoints", 900); + RAID_RANKING_3RD = config.getInt("3rdRaidRankingPoints", 700); + RAID_RANKING_4TH = config.getInt("4thRaidRankingPoints", 600); + RAID_RANKING_5TH = config.getInt("5thRaidRankingPoints", 450); + RAID_RANKING_6TH = config.getInt("6thRaidRankingPoints", 350); + RAID_RANKING_7TH = config.getInt("7thRaidRankingPoints", 300); + RAID_RANKING_8TH = config.getInt("8thRaidRankingPoints", 200); + RAID_RANKING_9TH = config.getInt("9thRaidRankingPoints", 150); + RAID_RANKING_10TH = config.getInt("10thRaidRankingPoints", 100); + RAID_RANKING_UP_TO_50TH = config.getInt("UpTo50thRaidRankingPoints", 25); + RAID_RANKING_UP_TO_100TH = config.getInt("UpTo100thRaidRankingPoints", 12); + CLAN_LEVEL_6_COST = config.getInt("ClanLevel6Cost", 5000); + CLAN_LEVEL_7_COST = config.getInt("ClanLevel7Cost", 10000); + CLAN_LEVEL_8_COST = config.getInt("ClanLevel8Cost", 20000); + CLAN_LEVEL_9_COST = config.getInt("ClanLevel9Cost", 40000); + CLAN_LEVEL_10_COST = config.getInt("ClanLevel10Cost", 40000); + CLAN_LEVEL_6_REQUIREMENT = config.getInt("ClanLevel6Requirement", 30); + CLAN_LEVEL_7_REQUIREMENT = config.getInt("ClanLevel7Requirement", 50); + CLAN_LEVEL_8_REQUIREMENT = config.getInt("ClanLevel8Requirement", 80); + CLAN_LEVEL_9_REQUIREMENT = config.getInt("ClanLevel9Requirement", 120); + CLAN_LEVEL_10_REQUIREMENT = config.getInt("ClanLevel10Requirement", 140); + ALLOW_WYVERN_ALWAYS = config.getBoolean("AllowRideWyvernAlways", false); + ALLOW_WYVERN_DURING_SIEGE = config.getBoolean("AllowRideWyvernDuringSiege", true); + ALLOW_MOUNTS_DURING_SIEGE = config.getBoolean("AllowRideMountsDuringSiege", false); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/FloodProtectorConfig.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/FloodProtectorConfig.java new file mode 100644 index 0000000..4b155ab --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/FloodProtectorConfig.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.config; + +import org.l2jmobius.commons.util.ConfigReader; +import org.l2jmobius.gameserver.util.FloodProtectorSettings; + +/** + * This class loads all the flood protector related configurations. + * @author Mobius + */ +public class FloodProtectorConfig +{ + // File + private static final String FLOOD_PROTECTOR_CONFIG_FILE = "./config/FloodProtector.ini"; + + // Constants + public static FloodProtectorSettings FLOOD_PROTECTOR_USE_ITEM; + public static FloodProtectorSettings FLOOD_PROTECTOR_ROLL_DICE; + public static FloodProtectorSettings FLOOD_PROTECTOR_ITEM_PET_SUMMON; + public static FloodProtectorSettings FLOOD_PROTECTOR_HERO_VOICE; + public static FloodProtectorSettings FLOOD_PROTECTOR_GLOBAL_CHAT; + public static FloodProtectorSettings FLOOD_PROTECTOR_SUBCLASS; + public static FloodProtectorSettings FLOOD_PROTECTOR_DROP_ITEM; + public static FloodProtectorSettings FLOOD_PROTECTOR_ENCHANT_ITEM; + public static FloodProtectorSettings FLOOD_PROTECTOR_SERVER_BYPASS; + public static FloodProtectorSettings FLOOD_PROTECTOR_MULTISELL; + public static FloodProtectorSettings FLOOD_PROTECTOR_TRANSACTION; + public static FloodProtectorSettings FLOOD_PROTECTOR_MANUFACTURE; + public static FloodProtectorSettings FLOOD_PROTECTOR_SENDMAIL; + public static FloodProtectorSettings FLOOD_PROTECTOR_CHARACTER_SELECT; + public static FloodProtectorSettings FLOOD_PROTECTOR_ITEM_AUCTION; + public static FloodProtectorSettings FLOOD_PROTECTOR_PLAYER_ACTION; + + public static void load() + { + final ConfigReader config = new ConfigReader(FLOOD_PROTECTOR_CONFIG_FILE); + FLOOD_PROTECTOR_USE_ITEM = new FloodProtectorSettings("UseItemFloodProtector"); + FLOOD_PROTECTOR_ROLL_DICE = new FloodProtectorSettings("RollDiceFloodProtector"); + FLOOD_PROTECTOR_ITEM_PET_SUMMON = new FloodProtectorSettings("ItemPetSummonFloodProtector"); + FLOOD_PROTECTOR_HERO_VOICE = new FloodProtectorSettings("HeroVoiceFloodProtector"); + FLOOD_PROTECTOR_GLOBAL_CHAT = new FloodProtectorSettings("GlobalChatFloodProtector"); + FLOOD_PROTECTOR_SUBCLASS = new FloodProtectorSettings("SubclassFloodProtector"); + FLOOD_PROTECTOR_DROP_ITEM = new FloodProtectorSettings("DropItemFloodProtector"); + FLOOD_PROTECTOR_ENCHANT_ITEM = new FloodProtectorSettings("EnchantItemFloodProtector"); + FLOOD_PROTECTOR_SERVER_BYPASS = new FloodProtectorSettings("ServerBypassFloodProtector"); + FLOOD_PROTECTOR_MULTISELL = new FloodProtectorSettings("MultiSellFloodProtector"); + FLOOD_PROTECTOR_TRANSACTION = new FloodProtectorSettings("TransactionFloodProtector"); + FLOOD_PROTECTOR_MANUFACTURE = new FloodProtectorSettings("ManufactureFloodProtector"); + FLOOD_PROTECTOR_SENDMAIL = new FloodProtectorSettings("SendMailFloodProtector"); + FLOOD_PROTECTOR_CHARACTER_SELECT = new FloodProtectorSettings("CharacterSelectFloodProtector"); + FLOOD_PROTECTOR_ITEM_AUCTION = new FloodProtectorSettings("ItemAuctionFloodProtector"); + FLOOD_PROTECTOR_PLAYER_ACTION = new FloodProtectorSettings("PlayerActionFloodProtector"); + loadFloodProtectorConfigs(config); + } + + /** + * Loads flood protector configurations. + * @param configs the ConfigReader parser containing the actual values of the flood protector + */ + private static void loadFloodProtectorConfigs(ConfigReader configs) + { + loadFloodProtectorConfig(configs, FLOOD_PROTECTOR_USE_ITEM, "UseItem", 4); + loadFloodProtectorConfig(configs, FLOOD_PROTECTOR_ROLL_DICE, "RollDice", 42); + loadFloodProtectorConfig(configs, FLOOD_PROTECTOR_ITEM_PET_SUMMON, "ItemPetSummon", 16); + loadFloodProtectorConfig(configs, FLOOD_PROTECTOR_HERO_VOICE, "HeroVoice", 100); + loadFloodProtectorConfig(configs, FLOOD_PROTECTOR_GLOBAL_CHAT, "GlobalChat", 5); + loadFloodProtectorConfig(configs, FLOOD_PROTECTOR_SUBCLASS, "Subclass", 20); + loadFloodProtectorConfig(configs, FLOOD_PROTECTOR_DROP_ITEM, "DropItem", 10); + loadFloodProtectorConfig(configs, FLOOD_PROTECTOR_SERVER_BYPASS, "ServerBypass", 5); + loadFloodProtectorConfig(configs, FLOOD_PROTECTOR_MULTISELL, "MultiSell", 1); + loadFloodProtectorConfig(configs, FLOOD_PROTECTOR_TRANSACTION, "Transaction", 10); + loadFloodProtectorConfig(configs, FLOOD_PROTECTOR_MANUFACTURE, "Manufacture", 3); + loadFloodProtectorConfig(configs, FLOOD_PROTECTOR_SENDMAIL, "SendMail", 100); + loadFloodProtectorConfig(configs, FLOOD_PROTECTOR_CHARACTER_SELECT, "CharacterSelect", 30); + loadFloodProtectorConfig(configs, FLOOD_PROTECTOR_ITEM_AUCTION, "ItemAuction", 9); + loadFloodProtectorConfig(configs, FLOOD_PROTECTOR_PLAYER_ACTION, "PlayerAction", 3); + } + + /** + * Loads single flood protector configuration. + * @param configReader the ConfigReader parser + * @param floodConfig flood protector configuration instance + * @param configString flood protector configuration string that determines for which flood protector configuration should be read + * @param defaultInterval default flood protector interval + */ + private static void loadFloodProtectorConfig(ConfigReader configReader, FloodProtectorSettings floodConfig, String configString, int defaultInterval) + { + floodConfig.setProtectionInterval(configReader.getInt("FloodProtector" + configString + "Interval", defaultInterval)); + floodConfig.setLogFlooding(configReader.getBoolean("FloodProtector" + configString + "LogFlooding", false)); + floodConfig.setPunishmentLimit(configReader.getInt("FloodProtector" + configString + "PunishmentLimit", 0)); + floodConfig.setPunishmentType(configReader.getString("FloodProtector" + configString + "PunishmentType", "none")); + floodConfig.setPunishmentTime(configReader.getInt("FloodProtector" + configString + "PunishmentTime", 0) * 60000); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/GeneralConfig.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/GeneralConfig.java new file mode 100644 index 0000000..e37d512 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/GeneralConfig.java @@ -0,0 +1,350 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.config; + +import java.util.HashSet; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.commons.util.ConfigReader; +import org.l2jmobius.gameserver.entity.actor.enums.player.ChatBroadcastType; +import org.l2jmobius.gameserver.entity.actor.enums.player.IllegalActionPunishmentType; +import org.l2jmobius.gameserver.network.enums.ChatType; + +/** + * This class loads all the general related configurations. + * @author Mobius + */ +public class GeneralConfig +{ + private static final Logger LOGGER = Logger.getLogger(GeneralConfig.class.getName()); + + // File + private static final String GENERAL_CONFIG_FILE = "./config/General.ini"; + + // Constants + public static boolean EVERYBODY_HAS_ADMIN_RIGHTS; + public static boolean SERVER_GMONLY; + public static boolean GM_HERO_AURA; + public static boolean GM_STARTUP_BUILDER_HIDE; + public static boolean GM_STARTUP_INVULNERABLE; + public static boolean GM_STARTUP_INVISIBLE; + public static boolean GM_STARTUP_SILENCE; + public static boolean GM_STARTUP_AUTO_LIST; + public static boolean GM_STARTUP_DIET_MODE; + public static boolean GM_ITEM_RESTRICTION; + public static boolean GM_SKILL_RESTRICTION; + public static boolean GM_TRADE_RESTRICTED_ITEMS; + public static boolean GM_RESTART_FIGHTING; + public static boolean GM_ANNOUNCER_NAME; + public static boolean GM_CRITANNOUNCER_NAME; + public static boolean GM_GIVE_SPECIAL_SKILLS; + public static boolean GM_GIVE_SPECIAL_AURA_SKILLS; + public static boolean GM_DEBUG_HTML_PATHS; + public static boolean USE_SUPER_HASTE_AS_GM_SPEED; + public static boolean LOG_CHAT; + public static boolean LOG_ITEMS; + public static boolean LOG_ITEMS_SMALL_LOG; + public static boolean LOG_ITEMS_IDS_ONLY; + public static Set LOG_ITEMS_IDS_LIST; + public static boolean LOG_ITEM_ENCHANTS; + public static boolean LOG_SKILL_ENCHANTS; + public static boolean GMAUDIT; + public static boolean SKILL_CHECK_ENABLE; + public static boolean SKILL_CHECK_REMOVE; + public static boolean SKILL_CHECK_GM; + public static boolean ALLOW_DISCARDITEM; + public static int AUTODESTROY_ITEM_AFTER; + public static int HERB_AUTO_DESTROY_TIME; + public static Set LIST_PROTECTED_ITEMS; + public static int CHAR_DATA_STORE_INTERVAL; + public static boolean LAZY_ITEMS_UPDATE; + public static boolean UPDATE_ITEMS_ON_CHAR_STORE; + public static boolean DESTROY_DROPPED_PLAYER_ITEM; + public static boolean DESTROY_EQUIPABLE_PLAYER_ITEM; + public static boolean DESTROY_ALL_ITEMS; + public static boolean SAVE_DROPPED_ITEM; + public static boolean EMPTY_DROPPED_ITEM_TABLE_AFTER_LOAD; + public static int SAVE_DROPPED_ITEM_INTERVAL; + public static boolean CLEAR_DROPPED_ITEM_TABLE; + public static boolean AUTODELETE_INVALID_QUEST_DATA; + public static boolean MULTIPLE_ITEM_DROP; + public static boolean HTM_CACHE; + public static boolean CHECK_HTML_ENCODING; + public static int MIN_NPC_ANIMATION; + public static int MAX_NPC_ANIMATION; + public static int MIN_MONSTER_ANIMATION; + public static int MAX_MONSTER_ANIMATION; + public static boolean GRIDS_ALWAYS_ON; + public static int GRID_NEIGHBOR_TURNON_TIME; + public static int GRID_NEIGHBOR_TURNOFF_TIME; + public static int PEACE_ZONE_MODE; + public static ChatBroadcastType DEFAULT_GLOBAL_CHAT; + public static ChatBroadcastType DEFAULT_TRADE_CHAT; + public static int MINIMUM_CHAT_LEVEL; + public static boolean ALLOW_WAREHOUSE; + public static boolean ALLOW_REFUND; + public static boolean ALLOW_WEAR; + public static int WEAR_DELAY; + public static int WEAR_PRICE; + public static boolean ALT_VILLAGES_REPEATABLE_QUEST_REWARD; + public static int INSTANCE_FINISH_TIME; + public static boolean RESTORE_PLAYER_INSTANCE; + public static boolean ALLOW_SUMMON_IN_INSTANCE; + public static int EJECT_DEAD_PLAYER_TIME; + public static boolean ALLOW_LOTTERY; + public static boolean ALLOW_RACE; + public static boolean ALLOW_WATER; + public static boolean ALLOW_FISHING; + public static boolean ALLOW_MANOR; + public static boolean ALLOW_BOAT; + public static int BOAT_BROADCAST_RADIUS; + public static boolean ALLOW_CURSED_WEAPONS; + public static boolean ALLOW_PARTY_IN_SAME_EVENT; + public static boolean SERVER_NEWS; + public static boolean ENABLE_COMMUNITY_BOARD; + public static String BBS_DEFAULT; + public static boolean USE_SAY_FILTER; + public static String CHAT_FILTER_CHARS; + public static Set BAN_CHAT_CHANNELS; + public static int ALT_MANOR_REFRESH_TIME; + public static int ALT_MANOR_REFRESH_MIN; + public static int ALT_MANOR_APPROVE_TIME; + public static int ALT_MANOR_APPROVE_MIN; + public static int ALT_MANOR_MAINTENANCE_MIN; + public static boolean ALT_MANOR_SAVE_ALL_ACTIONS; + public static int ALT_MANOR_SAVE_PERIOD_RATE; + public static int ALT_LOTTERY_PRIZE; + public static int ALT_LOTTERY_TICKET_PRICE; + public static float ALT_LOTTERY_5_NUMBER_RATE; + public static float ALT_LOTTERY_4_NUMBER_RATE; + public static float ALT_LOTTERY_3_NUMBER_RATE; + public static int ALT_LOTTERY_2_AND_1_NUMBER_PRIZE; + public static boolean ALT_FISH_CHAMPIONSHIP_ENABLED; + public static int ALT_FISH_CHAMPIONSHIP_REWARD_ITEM; + public static int ALT_FISH_CHAMPIONSHIP_REWARD_1; + public static int ALT_FISH_CHAMPIONSHIP_REWARD_2; + public static int ALT_FISH_CHAMPIONSHIP_REWARD_3; + public static int ALT_FISH_CHAMPIONSHIP_REWARD_4; + public static int ALT_FISH_CHAMPIONSHIP_REWARD_5; + public static boolean ALT_ITEM_AUCTION_ENABLED; + public static int ALT_ITEM_AUCTION_EXPIRED_AFTER; + public static long ALT_ITEM_AUCTION_TIME_EXTENDS_ON_BID; + public static int RIFT_MIN_PARTY_SIZE; + public static int RIFT_MAX_JUMPS; + public static int RIFT_SPAWN_DELAY; + public static int RIFT_AUTO_JUMPS_TIME_MIN; + public static int RIFT_AUTO_JUMPS_TIME_MAX; + public static float RIFT_BOSS_ROOM_TIME_MUTIPLY; + public static int RIFT_ENTER_COST_RECRUIT; + public static int RIFT_ENTER_COST_SOLDIER; + public static int RIFT_ENTER_COST_OFFICER; + public static int RIFT_ENTER_COST_CAPTAIN; + public static int RIFT_ENTER_COST_COMMANDER; + public static int RIFT_ENTER_COST_HERO; + public static IllegalActionPunishmentType DEFAULT_PUNISH; + public static long DEFAULT_PUNISH_PARAM; + public static boolean ONLY_GM_ITEMS_FREE; + public static boolean JAIL_IS_PVP; + public static boolean JAIL_DISABLE_CHAT; + public static boolean JAIL_DISABLE_TRANSACTION; + public static boolean CUSTOM_NPC_DATA; + public static boolean CUSTOM_TELEPORT_TABLE; + public static boolean CUSTOM_SKILLS_LOAD; + public static boolean CUSTOM_ITEMS_LOAD; + public static boolean CUSTOM_MULTISELL_LOAD; + public static boolean CUSTOM_BUYLIST_LOAD; + public static int NORMAL_ENCHANT_COST_MULTIPLIER; + public static int SAFE_ENCHANT_COST_MULTIPLIER; + public static boolean CORRECT_PRICES; + public static long MULTISELL_AMOUNT_LIMIT; + public static boolean ENABLE_FALLING_DAMAGE; + public static boolean DEBUFF_DURATION_USES_RESISTS; + + public static void load() + { + final ConfigReader config = new ConfigReader(GENERAL_CONFIG_FILE); + EVERYBODY_HAS_ADMIN_RIGHTS = config.getBoolean("EverybodyHasAdminRights", false); + SERVER_GMONLY = config.getBoolean("ServerGMOnly", false); + GM_HERO_AURA = config.getBoolean("GMHeroAura", false); + GM_STARTUP_BUILDER_HIDE = config.getBoolean("GMStartupBuilderHide", false); + GM_STARTUP_INVULNERABLE = config.getBoolean("GMStartupInvulnerable", false); + GM_STARTUP_INVISIBLE = config.getBoolean("GMStartupInvisible", false); + GM_STARTUP_SILENCE = config.getBoolean("GMStartupSilence", false); + GM_STARTUP_AUTO_LIST = config.getBoolean("GMStartupAutoList", false); + GM_STARTUP_DIET_MODE = config.getBoolean("GMStartupDietMode", false); + GM_ITEM_RESTRICTION = config.getBoolean("GMItemRestriction", true); + GM_SKILL_RESTRICTION = config.getBoolean("GMSkillRestriction", true); + GM_TRADE_RESTRICTED_ITEMS = config.getBoolean("GMTradeRestrictedItems", false); + GM_RESTART_FIGHTING = config.getBoolean("GMRestartFighting", true); + GM_ANNOUNCER_NAME = config.getBoolean("GMShowAnnouncerName", false); + GM_CRITANNOUNCER_NAME = config.getBoolean("GMShowCritAnnouncerName", false); + GM_GIVE_SPECIAL_SKILLS = config.getBoolean("GMGiveSpecialSkills", false); + GM_GIVE_SPECIAL_AURA_SKILLS = config.getBoolean("GMGiveSpecialAuraSkills", false); + GM_DEBUG_HTML_PATHS = config.getBoolean("GMDebugHtmlPaths", true); + USE_SUPER_HASTE_AS_GM_SPEED = config.getBoolean("UseSuperHasteAsGMSpeed", false); + LOG_CHAT = config.getBoolean("LogChat", false); + LOG_ITEMS = config.getBoolean("LogItems", false); + LOG_ITEMS_SMALL_LOG = config.getBoolean("LogItemsSmallLog", false); + LOG_ITEMS_IDS_ONLY = config.getBoolean("LogItemsIdsOnly", false); + final String[] splitItemIds = config.getString("LogItemsIdsList", "0").split(","); + LOG_ITEMS_IDS_LIST = new HashSet<>(splitItemIds.length); + for (String id : splitItemIds) + { + LOG_ITEMS_IDS_LIST.add(Integer.parseInt(id)); + } + LOG_ITEM_ENCHANTS = config.getBoolean("LogItemEnchants", false); + LOG_SKILL_ENCHANTS = config.getBoolean("LogSkillEnchants", false); + GMAUDIT = config.getBoolean("GMAudit", false); + SKILL_CHECK_ENABLE = config.getBoolean("SkillCheckEnable", false); + SKILL_CHECK_REMOVE = config.getBoolean("SkillCheckRemove", false); + SKILL_CHECK_GM = config.getBoolean("SkillCheckGM", true); + ALLOW_DISCARDITEM = config.getBoolean("AllowDiscardItem", true); + AUTODESTROY_ITEM_AFTER = config.getInt("AutoDestroyDroppedItemAfter", 600); + HERB_AUTO_DESTROY_TIME = config.getInt("AutoDestroyHerbTime", 60) * 1000; + final String[] split = config.getString("ListOfProtectedItems", "0").split(","); + LIST_PROTECTED_ITEMS = new HashSet<>(split.length); + for (String id : split) + { + LIST_PROTECTED_ITEMS.add(Integer.parseInt(id)); + } + CHAR_DATA_STORE_INTERVAL = config.getInt("CharacterDataStoreInterval", 15) * 60 * 1000; + LAZY_ITEMS_UPDATE = config.getBoolean("LazyItemsUpdate", false); + UPDATE_ITEMS_ON_CHAR_STORE = config.getBoolean("UpdateItemsOnCharStore", false); + DESTROY_DROPPED_PLAYER_ITEM = config.getBoolean("DestroyPlayerDroppedItem", false); + DESTROY_EQUIPABLE_PLAYER_ITEM = config.getBoolean("DestroyEquipableItem", false); + DESTROY_ALL_ITEMS = config.getBoolean("DestroyAllItems", false); + SAVE_DROPPED_ITEM = config.getBoolean("SaveDroppedItem", false); + EMPTY_DROPPED_ITEM_TABLE_AFTER_LOAD = config.getBoolean("EmptyDroppedItemTableAfterLoad", false); + SAVE_DROPPED_ITEM_INTERVAL = config.getInt("SaveDroppedItemInterval", 60) * 60000; + CLEAR_DROPPED_ITEM_TABLE = config.getBoolean("ClearDroppedItemTable", false); + AUTODELETE_INVALID_QUEST_DATA = config.getBoolean("AutoDeleteInvalidQuestData", false); + MULTIPLE_ITEM_DROP = config.getBoolean("MultipleItemDrop", true); + HTM_CACHE = config.getBoolean("HtmCache", true); + CHECK_HTML_ENCODING = config.getBoolean("CheckHtmlEncoding", true); + MIN_NPC_ANIMATION = config.getInt("MinNpcAnimation", 5); + MAX_NPC_ANIMATION = config.getInt("MaxNpcAnimation", 60); + MIN_MONSTER_ANIMATION = config.getInt("MinMonsterAnimation", 5); + MAX_MONSTER_ANIMATION = config.getInt("MaxMonsterAnimation", 60); + GRIDS_ALWAYS_ON = config.getBoolean("GridsAlwaysOn", false); + GRID_NEIGHBOR_TURNON_TIME = config.getInt("GridNeighborTurnOnTime", 1); + GRID_NEIGHBOR_TURNOFF_TIME = config.getInt("GridNeighborTurnOffTime", 90); + PEACE_ZONE_MODE = config.getInt("PeaceZoneMode", 0); + DEFAULT_GLOBAL_CHAT = Enum.valueOf(ChatBroadcastType.class, config.getString("GlobalChat", "ON")); + DEFAULT_TRADE_CHAT = Enum.valueOf(ChatBroadcastType.class, config.getString("TradeChat", "ON")); + MINIMUM_CHAT_LEVEL = config.getInt("MinimumChatLevel", 20); + ALLOW_WAREHOUSE = config.getBoolean("AllowWarehouse", true); + ALLOW_REFUND = config.getBoolean("AllowRefund", true); + ALLOW_WEAR = config.getBoolean("AllowWear", true); + WEAR_DELAY = config.getInt("WearDelay", 5); + WEAR_PRICE = config.getInt("WearPrice", 10); + ALT_VILLAGES_REPEATABLE_QUEST_REWARD = config.getBoolean("AltVillagesRepQuestReward", false); + INSTANCE_FINISH_TIME = config.getInt("DefaultFinishTime", 300) * 1000; + RESTORE_PLAYER_INSTANCE = config.getBoolean("RestorePlayerInstance", false); + ALLOW_SUMMON_IN_INSTANCE = config.getBoolean("AllowSummonInInstance", false); + EJECT_DEAD_PLAYER_TIME = config.getInt("EjectDeadPlayerTime", 60) * 1000; + ALLOW_LOTTERY = config.getBoolean("AllowLottery", true); + ALLOW_RACE = config.getBoolean("AllowRace", true); + ALLOW_WATER = config.getBoolean("AllowWater", true); + ALLOW_FISHING = config.getBoolean("AllowFishing", true); + ALLOW_MANOR = config.getBoolean("AllowManor", true); + ALLOW_BOAT = config.getBoolean("AllowBoat", true); + BOAT_BROADCAST_RADIUS = config.getInt("BoatBroadcastRadius", 20000); + ALLOW_CURSED_WEAPONS = config.getBoolean("AllowCursedWeapons", true); + ALLOW_PARTY_IN_SAME_EVENT = config.getBoolean("AllowPartyInSameEvent", true); + SERVER_NEWS = config.getBoolean("ShowServerNews", false); + ENABLE_COMMUNITY_BOARD = config.getBoolean("EnableCommunityBoard", true); + BBS_DEFAULT = config.getString("BBSDefault", "_bbshome"); + USE_SAY_FILTER = config.getBoolean("UseChatFilter", false); + CHAT_FILTER_CHARS = config.getString("ChatFilterChars", "^_^"); + final String[] propertySplit4 = config.getString("BanChatChannels", "GENERAL;SHOUT;WORLD;TRADE;HERO_VOICE").trim().split(";"); + BAN_CHAT_CHANNELS = new HashSet<>(); + try + { + for (String chatId : propertySplit4) + { + BAN_CHAT_CHANNELS.add(Enum.valueOf(ChatType.class, chatId)); + } + } + catch (NumberFormatException nfe) + { + LOGGER.log(Level.WARNING, "There was an error while parsing ban chat channels: ", nfe); + } + ALT_MANOR_REFRESH_TIME = config.getInt("AltManorRefreshTime", 20); + ALT_MANOR_REFRESH_MIN = config.getInt("AltManorRefreshMin", 0); + ALT_MANOR_APPROVE_TIME = config.getInt("AltManorApproveTime", 4); + ALT_MANOR_APPROVE_MIN = config.getInt("AltManorApproveMin", 30); + ALT_MANOR_MAINTENANCE_MIN = config.getInt("AltManorMaintenanceMin", 6); + ALT_MANOR_SAVE_ALL_ACTIONS = config.getBoolean("AltManorSaveAllActions", false); + ALT_MANOR_SAVE_PERIOD_RATE = config.getInt("AltManorSavePeriodRate", 2); + ALT_LOTTERY_PRIZE = config.getInt("AltLotteryPrize", 50000); + ALT_LOTTERY_TICKET_PRICE = config.getInt("AltLotteryTicketPrice", 2000); + ALT_LOTTERY_5_NUMBER_RATE = config.getFloat("AltLottery5NumberRate", 0.6f); + ALT_LOTTERY_4_NUMBER_RATE = config.getFloat("AltLottery4NumberRate", 0.2f); + ALT_LOTTERY_3_NUMBER_RATE = config.getFloat("AltLottery3NumberRate", 0.2f); + ALT_LOTTERY_2_AND_1_NUMBER_PRIZE = config.getInt("AltLottery2and1NumberPrize", 200); + ALT_FISH_CHAMPIONSHIP_ENABLED = config.getBoolean("AltFishChampionshipEnabled", true); + ALT_FISH_CHAMPIONSHIP_REWARD_ITEM = config.getInt("AltFishChampionshipRewardItemId", 57); + ALT_FISH_CHAMPIONSHIP_REWARD_1 = config.getInt("AltFishChampionshipReward1", 800000); + ALT_FISH_CHAMPIONSHIP_REWARD_2 = config.getInt("AltFishChampionshipReward2", 500000); + ALT_FISH_CHAMPIONSHIP_REWARD_3 = config.getInt("AltFishChampionshipReward3", 300000); + ALT_FISH_CHAMPIONSHIP_REWARD_4 = config.getInt("AltFishChampionshipReward4", 200000); + ALT_FISH_CHAMPIONSHIP_REWARD_5 = config.getInt("AltFishChampionshipReward5", 100000); + ALT_ITEM_AUCTION_ENABLED = config.getBoolean("AltItemAuctionEnabled", true); + ALT_ITEM_AUCTION_EXPIRED_AFTER = config.getInt("AltItemAuctionExpiredAfter", 14); + ALT_ITEM_AUCTION_TIME_EXTENDS_ON_BID = config.getInt("AltItemAuctionTimeExtendsOnBid", 0) * 1000; + RIFT_MIN_PARTY_SIZE = config.getInt("RiftMinPartySize", 5); + RIFT_MAX_JUMPS = config.getInt("MaxRiftJumps", 4); + RIFT_SPAWN_DELAY = config.getInt("RiftSpawnDelay", 10000); + RIFT_AUTO_JUMPS_TIME_MIN = config.getInt("AutoJumpsDelayMin", 480); + RIFT_AUTO_JUMPS_TIME_MAX = config.getInt("AutoJumpsDelayMax", 600); + RIFT_BOSS_ROOM_TIME_MUTIPLY = config.getFloat("BossRoomTimeMultiply", 1.5f); + RIFT_ENTER_COST_RECRUIT = config.getInt("RecruitCost", 18); + RIFT_ENTER_COST_SOLDIER = config.getInt("SoldierCost", 21); + RIFT_ENTER_COST_OFFICER = config.getInt("OfficerCost", 24); + RIFT_ENTER_COST_CAPTAIN = config.getInt("CaptainCost", 27); + RIFT_ENTER_COST_COMMANDER = config.getInt("CommanderCost", 30); + RIFT_ENTER_COST_HERO = config.getInt("HeroCost", 33); + DEFAULT_PUNISH = IllegalActionPunishmentType.findByName(config.getString("DefaultPunish", "KICK")); + DEFAULT_PUNISH_PARAM = config.getLong("DefaultPunishParam", 0); + if (DEFAULT_PUNISH_PARAM == 0) + { + DEFAULT_PUNISH_PARAM = 3155695200L; // One hundred years in seconds. + } + ONLY_GM_ITEMS_FREE = config.getBoolean("OnlyGMItemsFree", true); + JAIL_IS_PVP = config.getBoolean("JailIsPvp", false); + JAIL_DISABLE_CHAT = config.getBoolean("JailDisableChat", true); + JAIL_DISABLE_TRANSACTION = config.getBoolean("JailDisableTransaction", false); + CUSTOM_NPC_DATA = config.getBoolean("CustomNpcData", false); + CUSTOM_TELEPORT_TABLE = config.getBoolean("CustomTeleportTable", false); + CUSTOM_SKILLS_LOAD = config.getBoolean("CustomSkillsLoad", false); + CUSTOM_ITEMS_LOAD = config.getBoolean("CustomItemsLoad", false); + CUSTOM_MULTISELL_LOAD = config.getBoolean("CustomMultisellLoad", false); + CUSTOM_BUYLIST_LOAD = config.getBoolean("CustomBuyListLoad", false); + NORMAL_ENCHANT_COST_MULTIPLIER = config.getInt("NormalEnchantCostMultipiler", 1); + SAFE_ENCHANT_COST_MULTIPLIER = config.getInt("SafeEnchantCostMultipiler", 5); + CORRECT_PRICES = config.getBoolean("CorrectPrices", true); + MULTISELL_AMOUNT_LIMIT = config.getLong("MultisellAmountLimit", 10000); + ENABLE_FALLING_DAMAGE = config.getBoolean("EnableFallingDamage", true); + DEBUFF_DURATION_USES_RESISTS = config.getBoolean("DebuffDurationUsesResists", false); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/GeoEngineConfig.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/GeoEngineConfig.java new file mode 100644 index 0000000..b61c7f2 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/GeoEngineConfig.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.config; + +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.l2jmobius.commons.util.ConfigReader; + +/** + * This class loads all the geo engine related configurations. + * @author Mobius + */ +public class GeoEngineConfig +{ + // File + private static final String GEOENGINE_CONFIG_FILE = "./config/GeoEngine.ini"; + + // Constants + public static Path GEODATA_PATH; + public static Path PATHNODE_PATH; + public static Path GEOEDIT_PATH; + public static int PATHFINDING; + public static String PATHFIND_BUFFERS; + public static float LOW_WEIGHT; + public static float MEDIUM_WEIGHT; + public static float HIGH_WEIGHT; + public static boolean ADVANCED_DIAGONAL_STRATEGY; + public static boolean AVOID_OBSTRUCTED_PATH_NODES; + public static float DIAGONAL_WEIGHT; + public static int MAX_POSTFILTER_PASSES; + + public static void load() + { + final ConfigReader config = new ConfigReader(GEOENGINE_CONFIG_FILE); + GEODATA_PATH = Paths.get(ServerConfig.DATAPACK_ROOT.getPath() + "/" + config.getString("GeoDataPath", "geodata")); + PATHNODE_PATH = Paths.get(ServerConfig.DATAPACK_ROOT.getPath() + "/" + config.getString("PathnodePath", "pathnode")); + GEOEDIT_PATH = Paths.get(ServerConfig.DATAPACK_ROOT.getPath() + "/" + config.getString("GeoEditPath", "saves")); + PATHFINDING = config.getInt("PathFinding", 0); + PATHFIND_BUFFERS = config.getString("PathFindBuffers", "100x6;128x6;192x6;256x4;320x4;384x4;500x2"); + LOW_WEIGHT = config.getFloat("LowWeight", 0.5f); + MEDIUM_WEIGHT = config.getFloat("MediumWeight", 2); + HIGH_WEIGHT = config.getFloat("HighWeight", 3); + ADVANCED_DIAGONAL_STRATEGY = config.getBoolean("AdvancedDiagonalStrategy", true); + AVOID_OBSTRUCTED_PATH_NODES = config.getBoolean("AvoidObstructedPathNodes", true); + DIAGONAL_WEIGHT = config.getFloat("DiagonalWeight", 0.707f); + MAX_POSTFILTER_PASSES = config.getInt("MaxPostfilterPasses", 3); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/GrandBossConfig.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/GrandBossConfig.java new file mode 100644 index 0000000..924f889 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/GrandBossConfig.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.config; + +import org.l2jmobius.commons.util.ConfigReader; + +/** + * This class loads all the grand boss related configurations. + * @author Mobius + */ +public class GrandBossConfig +{ + // File + private static final String GRANDBOSS_CONFIG_FILE = "./config/GrandBoss.ini"; + + // Constants + public static int ANTHARAS_WAIT_TIME; + public static int ANTHARAS_SPAWN_INTERVAL; + public static int ANTHARAS_SPAWN_RANDOM; + public static boolean ANTHARAS_RECOGNIZE_HERO; + public static int VALAKAS_WAIT_TIME; + public static int VALAKAS_SPAWN_INTERVAL; + public static int VALAKAS_SPAWN_RANDOM; + public static boolean VALAKAS_RECOGNIZE_HERO; + public static int BAIUM_SPAWN_INTERVAL; + public static int BAIUM_SPAWN_RANDOM; + public static boolean BAIUM_RECOGNIZE_HERO; + public static int CORE_SPAWN_INTERVAL; + public static int CORE_SPAWN_RANDOM; + public static int ORFEN_SPAWN_INTERVAL; + public static int ORFEN_SPAWN_RANDOM; + public static int QUEEN_ANT_SPAWN_INTERVAL; + public static int QUEEN_ANT_SPAWN_RANDOM; + public static int ZAKEN_SPAWN_INTERVAL; + public static int ZAKEN_SPAWN_RANDOM; + public static int FRINTEZZA_SPAWN_INTERVAL; + public static int FRINTEZZA_SPAWN_RANDOM; + public static boolean DISABLE_RAIDBOSS_HEAL_FROM_PLAYERS; + public static boolean DISABLE_RAIDBOSS_BUFF_FROM_PLAYERS; + + public static void load() + { + final ConfigReader config = new ConfigReader(GRANDBOSS_CONFIG_FILE); + ANTHARAS_WAIT_TIME = config.getInt("AntharasWaitTime", 30); + ANTHARAS_SPAWN_INTERVAL = config.getInt("IntervalOfAntharasSpawn", 264); + ANTHARAS_SPAWN_RANDOM = config.getInt("RandomOfAntharasSpawn", 72); + ANTHARAS_RECOGNIZE_HERO = config.getBoolean("AntharasRecognizeHero", true); + VALAKAS_WAIT_TIME = config.getInt("ValakasWaitTime", 30); + VALAKAS_SPAWN_INTERVAL = config.getInt("IntervalOfValakasSpawn", 264); + VALAKAS_SPAWN_RANDOM = config.getInt("RandomOfValakasSpawn", 72); + VALAKAS_RECOGNIZE_HERO = config.getBoolean("ValakasRecognizeHero", true); + BAIUM_SPAWN_INTERVAL = config.getInt("IntervalOfBaiumSpawn", 168); + BAIUM_SPAWN_RANDOM = config.getInt("RandomOfBaiumSpawn", 48); + BAIUM_RECOGNIZE_HERO = config.getBoolean("BaiumRecognizeHero", true); + CORE_SPAWN_INTERVAL = config.getInt("IntervalOfCoreSpawn", 60); + CORE_SPAWN_RANDOM = config.getInt("RandomOfCoreSpawn", 24); + ORFEN_SPAWN_INTERVAL = config.getInt("IntervalOfOrfenSpawn", 48); + ORFEN_SPAWN_RANDOM = config.getInt("RandomOfOrfenSpawn", 20); + QUEEN_ANT_SPAWN_INTERVAL = config.getInt("IntervalOfQueenAntSpawn", 36); + QUEEN_ANT_SPAWN_RANDOM = config.getInt("RandomOfQueenAntSpawn", 17); + ZAKEN_SPAWN_INTERVAL = config.getInt("IntervalOfZakenSpawn", 36); + ZAKEN_SPAWN_RANDOM = config.getInt("RandomOfZakenSpawn", 17); + FRINTEZZA_SPAWN_INTERVAL = config.getInt("IntervalOfFrintezzaSpawn", 48); + FRINTEZZA_SPAWN_RANDOM = config.getInt("RandomOfFrintezzaSpawn", 8); + DISABLE_RAIDBOSS_HEAL_FROM_PLAYERS = config.getBoolean("DisableRaidBossHealFromPlayers", false); + DISABLE_RAIDBOSS_BUFF_FROM_PLAYERS = config.getBoolean("DisableRaidBossBuffFromPlayers", false); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/IdManagerConfig.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/IdManagerConfig.java new file mode 100644 index 0000000..a43a930 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/IdManagerConfig.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.config; + +import org.l2jmobius.commons.util.ConfigReader; + +/** + * This class loads all the id manager related configurations. + * @author Mobius + */ +public class IdManagerConfig +{ + // File + private static final String ID_MANAGER_CONFIG_FILE = "./config/IdManager.ini"; + + // Constants + public static boolean DATABASE_CLEAN_UP; + public static int FIRST_OBJECT_ID; + public static int LAST_OBJECT_ID; + public static int INITIAL_CAPACITY; + public static double RESIZE_THRESHOLD; + public static double RESIZE_MULTIPLIER; + + public static void load() + { + final ConfigReader config = new ConfigReader(ID_MANAGER_CONFIG_FILE); + DATABASE_CLEAN_UP = config.getBoolean("DatabaseCleanUp", true); + FIRST_OBJECT_ID = config.getInt("FirstObjectId", 268435456); + LAST_OBJECT_ID = config.getInt("LastObjectId", 2147483647); + INITIAL_CAPACITY = Math.min(config.getInt("InitialCapacity", 100000), LAST_OBJECT_ID - 1); + RESIZE_THRESHOLD = config.getDouble("ResizeThreshold", 0.9); + RESIZE_MULTIPLIER = config.getDouble("ResizeMultiplier", 1.1); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/NpcConfig.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/NpcConfig.java new file mode 100644 index 0000000..bca64dc --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/NpcConfig.java @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.config; + +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Logger; + +import org.l2jmobius.commons.util.ConfigReader; +import org.l2jmobius.commons.util.StringUtil; + +/** + * This class loads all the NPC related configurations. + * @author Mobius + */ +public class NpcConfig +{ + private static final Logger LOGGER = Logger.getLogger(NpcConfig.class.getName()); + + // File + private static final String NPC_CONFIG_FILE = "./config/NPC.ini"; + + // Constants + public static boolean ANNOUNCE_MAMMON_SPAWN; + public static boolean ALT_MOB_AGRO_IN_PEACEZONE; + public static boolean ALT_ATTACKABLE_NPCS; + public static boolean ALT_GAME_VIEWNPC; + public static boolean SHOW_NPC_LEVEL; + public static boolean SHOW_NPC_AGGRESSION; + public static boolean ATTACKABLES_CAMP_PLAYER_CORPSES; + public static boolean SHOW_CREST_WITHOUT_QUEST; + public static boolean ENABLE_RANDOM_ENCHANT_EFFECT; + public static int DECAY_TIME_TASK; + public static int DEFAULT_CORPSE_TIME; + public static int SPOILED_CORPSE_EXTEND_TIME; + public static int CORPSE_CONSUME_SKILL_ALLOWED_TIME_BEFORE_DECAY; + public static int MAX_AGGRO_RANGE; + public static int MAX_DRIFT_RANGE; + public static boolean AGGRO_DISTANCE_CHECK_ENABLED; + public static int AGGRO_DISTANCE_CHECK_RANGE; + public static boolean AGGRO_DISTANCE_CHECK_RAIDS; + public static int AGGRO_DISTANCE_CHECK_RAID_RANGE; + public static boolean AGGRO_DISTANCE_CHECK_INSTANCES; + public static boolean AGGRO_DISTANCE_CHECK_RESTORE_LIFE; + public static boolean GUARD_ATTACK_AGGRO_MOB; + public static boolean ALLOW_WYVERN_UPGRADER; + public static double RAID_HP_REGEN_MULTIPLIER; + public static double RAID_MP_REGEN_MULTIPLIER; + public static double RAID_PDEFENCE_MULTIPLIER; + public static double RAID_MDEFENCE_MULTIPLIER; + public static double RAID_PATTACK_MULTIPLIER; + public static double RAID_MATTACK_MULTIPLIER; + public static float RAID_MIN_RESPAWN_MULTIPLIER; + public static float RAID_MAX_RESPAWN_MULTIPLIER; + public static double RAID_MINION_RESPAWN_TIMER; + public static Map MINIONS_RESPAWN_TIME; + public static boolean FORCE_DELETE_MINIONS; + public static boolean RAID_DISABLE_CURSE; + public static int RAID_CHAOS_TIME; + public static int GRAND_CHAOS_TIME; + public static int MINION_CHAOS_TIME; + public static int INVENTORY_MAXIMUM_PET; + public static double PET_HP_REGEN_MULTIPLIER; + public static double PET_MP_REGEN_MULTIPLIER; + + public static void load() + { + final ConfigReader config = new ConfigReader(NPC_CONFIG_FILE); + ANNOUNCE_MAMMON_SPAWN = config.getBoolean("AnnounceMammonSpawn", false); + ALT_MOB_AGRO_IN_PEACEZONE = config.getBoolean("AltMobAgroInPeaceZone", true); + ALT_ATTACKABLE_NPCS = config.getBoolean("AltAttackableNpcs", true); + ALT_GAME_VIEWNPC = config.getBoolean("AltGameViewNpc", false); + SHOW_NPC_LEVEL = config.getBoolean("ShowNpcLevel", false); + SHOW_NPC_AGGRESSION = config.getBoolean("ShowNpcAggression", false); + ATTACKABLES_CAMP_PLAYER_CORPSES = config.getBoolean("AttackablesCampPlayerCorpses", false); + SHOW_CREST_WITHOUT_QUEST = config.getBoolean("ShowCrestWithoutQuest", false); + ENABLE_RANDOM_ENCHANT_EFFECT = config.getBoolean("EnableRandomEnchantEffect", false); + DECAY_TIME_TASK = config.getInt("DecayTimeTask", 5000); + DEFAULT_CORPSE_TIME = config.getInt("DefaultCorpseTime", 7); + SPOILED_CORPSE_EXTEND_TIME = config.getInt("SpoiledCorpseExtendTime", 10); + CORPSE_CONSUME_SKILL_ALLOWED_TIME_BEFORE_DECAY = config.getInt("CorpseConsumeSkillAllowedTimeBeforeDecay", 2000); + MAX_AGGRO_RANGE = config.getInt("MaxAggroRange", 450); + MAX_DRIFT_RANGE = config.getInt("MaxDriftRange", 300); + AGGRO_DISTANCE_CHECK_ENABLED = config.getBoolean("AggroDistanceCheckEnabled", true); + AGGRO_DISTANCE_CHECK_RANGE = config.getInt("AggroDistanceCheckRange", 1500); + AGGRO_DISTANCE_CHECK_RAIDS = config.getBoolean("AggroDistanceCheckRaids", false); + AGGRO_DISTANCE_CHECK_RAID_RANGE = config.getInt("AggroDistanceCheckRaidRange", 3000); + AGGRO_DISTANCE_CHECK_INSTANCES = config.getBoolean("AggroDistanceCheckInstances", false); + AGGRO_DISTANCE_CHECK_RESTORE_LIFE = config.getBoolean("AggroDistanceCheckRestoreLife", true); + GUARD_ATTACK_AGGRO_MOB = config.getBoolean("GuardAttackAggroMob", false); + ALLOW_WYVERN_UPGRADER = config.getBoolean("AllowWyvernUpgrader", false); + RAID_HP_REGEN_MULTIPLIER = config.getDouble("RaidHpRegenMultiplier", 100) / 100; + RAID_MP_REGEN_MULTIPLIER = config.getDouble("RaidMpRegenMultiplier", 100) / 100; + RAID_PDEFENCE_MULTIPLIER = config.getDouble("RaidPDefenceMultiplier", 100) / 100; + RAID_MDEFENCE_MULTIPLIER = config.getDouble("RaidMDefenceMultiplier", 100) / 100; + RAID_PATTACK_MULTIPLIER = config.getDouble("RaidPAttackMultiplier", 100) / 100; + RAID_MATTACK_MULTIPLIER = config.getDouble("RaidMAttackMultiplier", 100) / 100; + RAID_MIN_RESPAWN_MULTIPLIER = config.getFloat("RaidMinRespawnMultiplier", 1.0f); + RAID_MAX_RESPAWN_MULTIPLIER = config.getFloat("RaidMaxRespawnMultiplier", 1.0f); + RAID_MINION_RESPAWN_TIMER = config.getInt("RaidMinionRespawnTime", 300000); + + final String[] split = config.getString("CustomMinionsRespawnTime", "").split(";"); + MINIONS_RESPAWN_TIME = new HashMap<>(split.length); + for (String prop : split) + { + final String[] propSplit = prop.split(","); + if (propSplit.length != 2) + { + LOGGER.warning(StringUtil.concat("[CustomMinionsRespawnTime]: invalid config property -> CustomMinionsRespawnTime \"", prop, "\"")); + } + try + { + MINIONS_RESPAWN_TIME.put(Integer.parseInt(propSplit[0]), Integer.parseInt(propSplit[1])); + } + catch (NumberFormatException nfe) + { + if (!prop.isEmpty()) + { + LOGGER.warning(StringUtil.concat("[CustomMinionsRespawnTime]: invalid config property -> CustomMinionsRespawnTime \"", propSplit[0], "\"", propSplit[1])); + } + } + } + + FORCE_DELETE_MINIONS = config.getBoolean("ForceDeleteMinions", false); + RAID_DISABLE_CURSE = config.getBoolean("DisableRaidCurse", false); + RAID_CHAOS_TIME = config.getInt("RaidChaosTime", 10); + GRAND_CHAOS_TIME = config.getInt("GrandChaosTime", 10); + MINION_CHAOS_TIME = config.getInt("MinionChaosTime", 10); + INVENTORY_MAXIMUM_PET = config.getInt("MaximumSlotsForPet", 12); + PET_HP_REGEN_MULTIPLIER = config.getDouble("PetHpRegenMultiplier", 100) / 100; + PET_MP_REGEN_MULTIPLIER = config.getDouble("PetMpRegenMultiplier", 100) / 100; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/OlympiadConfig.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/OlympiadConfig.java new file mode 100644 index 0000000..c12607d --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/OlympiadConfig.java @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.config; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.l2jmobius.commons.util.ConfigReader; + +/** + * This class loads all the olympiad related configurations. + * @author Mobius + */ +public class OlympiadConfig +{ + // File + private static final String OLYMPIAD_CONFIG_FILE = "./config/Olympiad.ini"; + + // Constants + public static boolean OLYMPIAD_ENABLED; + public static int OLYMPIAD_START_TIME; + public static int OLYMPIAD_MIN; + public static long OLYMPIAD_CPERIOD; + public static long OLYMPIAD_BATTLE; + public static long OLYMPIAD_WPERIOD; + public static long OLYMPIAD_VPERIOD; + public static int OLYMPIAD_START_POINTS; + public static int OLYMPIAD_WEEKLY_POINTS; + public static int OLYMPIAD_CLASSED; + public static int OLYMPIAD_NONCLASSED; + public static int OLYMPIAD_REG_DISPLAY; + public static int OLYMPIAD_BATTLE_REWARD_ITEM; + public static int OLYMPIAD_CLASSED_RITEM_C; + public static int OLYMPIAD_NONCLASSED_RITEM_C; + public static int OLYMPIAD_COMP_RITEM; + public static int OLYMPIAD_GP_PER_POINT; + public static int OLYMPIAD_HERO_POINTS; + public static int OLYMPIAD_MAX_POINTS; + public static boolean OLYMPIAD_LOG_FIGHTS; + public static boolean OLYMPIAD_SHOW_MONTHLY_WINNERS; + public static boolean OLYMPIAD_ANNOUNCE_GAMES; + public static Set LIST_OLY_RESTRICTED_ITEMS = new HashSet<>(); + public static boolean OLYMPIAD_DISABLE_BLESSED_SPIRITSHOTS; + public static int OLYMPIAD_ENCHANT_LIMIT; + public static int OLYMPIAD_WAIT_TIME; + public static boolean OLYMPIAD_USE_CUSTOM_PERIOD_SETTINGS; + public static String OLYMPIAD_PERIOD; + public static int OLYMPIAD_PERIOD_MULTIPLIER; + public static List OLYMPIAD_COMPETITION_DAYS; + + public static void load() + { + final ConfigReader config = new ConfigReader(OLYMPIAD_CONFIG_FILE); + OLYMPIAD_ENABLED = config.getBoolean("OlympiadEnabled", true); + OLYMPIAD_START_TIME = config.getInt("OlympiadStartTime", 18); + OLYMPIAD_MIN = config.getInt("OlympiadMin", 0); + OLYMPIAD_CPERIOD = config.getLong("OlympiadCPeriod", 21600000); + OLYMPIAD_BATTLE = config.getLong("OlympiadBattle", 360000); + OLYMPIAD_WPERIOD = config.getLong("OlympiadWPeriod", 604800000); + OLYMPIAD_VPERIOD = config.getLong("OlympiadVPeriod", 86400000); + OLYMPIAD_START_POINTS = config.getInt("OlympiadStartPoints", 18); + OLYMPIAD_WEEKLY_POINTS = config.getInt("OlympiadWeeklyPoints", 3); + OLYMPIAD_CLASSED = config.getInt("OlympiadClassedParticipants", 5); + OLYMPIAD_NONCLASSED = config.getInt("OlympiadNonClassedParticipants", 9); + OLYMPIAD_REG_DISPLAY = config.getInt("OlympiadRegistrationDisplayNumber", 0); + OLYMPIAD_BATTLE_REWARD_ITEM = config.getInt("OlympiadBattleRewItem", 6651); + OLYMPIAD_CLASSED_RITEM_C = config.getInt("OlympiadClassedRewItemCount", 50); + OLYMPIAD_NONCLASSED_RITEM_C = config.getInt("OlympiadNonClassedRewItemCount", 30); + OLYMPIAD_COMP_RITEM = config.getInt("OlympiadCompRewItem", 6651); + OLYMPIAD_GP_PER_POINT = config.getInt("OlympiadGPPerPoint", 1000); + OLYMPIAD_HERO_POINTS = config.getInt("OlympiadHeroPoints", 100); + OLYMPIAD_MAX_POINTS = config.getInt("OlympiadMaxPoints", 10); + OLYMPIAD_LOG_FIGHTS = config.getBoolean("OlympiadLogFights", false); + OLYMPIAD_SHOW_MONTHLY_WINNERS = config.getBoolean("OlympiadShowMonthlyWinners", true); + OLYMPIAD_ANNOUNCE_GAMES = config.getBoolean("OlympiadAnnounceGames", true); + final String olyRestrictedItems = config.getString("OlympiadRestrictedItems", "").trim(); + if (!olyRestrictedItems.isEmpty()) + { + final String[] olyRestrictedItemsSplit = olyRestrictedItems.split(","); + LIST_OLY_RESTRICTED_ITEMS = new HashSet<>(olyRestrictedItemsSplit.length); + for (String id : olyRestrictedItemsSplit) + { + LIST_OLY_RESTRICTED_ITEMS.add(Integer.parseInt(id)); + } + } + else // In case of reload with removal of all items ids. + { + LIST_OLY_RESTRICTED_ITEMS.clear(); + } + OLYMPIAD_DISABLE_BLESSED_SPIRITSHOTS = config.getBoolean("OlympiadDisableBlessedSpiritShots", true); + OLYMPIAD_ENCHANT_LIMIT = config.getInt("OlympiadEnchantLimit", -1); + OLYMPIAD_WAIT_TIME = config.getInt("OlympiadWaitTime", 120); + if ((OLYMPIAD_WAIT_TIME != 120) && (OLYMPIAD_WAIT_TIME != 60) && (OLYMPIAD_WAIT_TIME != 30) && (OLYMPIAD_WAIT_TIME != 15) && (OLYMPIAD_WAIT_TIME != 5)) + { + OLYMPIAD_WAIT_TIME = 120; + } + OLYMPIAD_USE_CUSTOM_PERIOD_SETTINGS = config.getBoolean("OlympiadUseCustomPeriodSettings", false); + OLYMPIAD_PERIOD = config.getString("OlympiadPeriod", "MONTH"); + OLYMPIAD_PERIOD_MULTIPLIER = config.getInt("OlympiadPeriodMultiplier", 1); + OLYMPIAD_COMPETITION_DAYS = new ArrayList<>(); + for (String s : config.getString("OlympiadCompetitionDays", "1,2,3,4,5,6,7").split(",")) + { + OLYMPIAD_COMPETITION_DAYS.add(Integer.parseInt(s)); + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/PlayerConfig.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/PlayerConfig.java new file mode 100644 index 0000000..d7d44ec --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/PlayerConfig.java @@ -0,0 +1,541 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.config; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.logging.Logger; + +import org.l2jmobius.commons.util.ConfigReader; +import org.l2jmobius.commons.util.StringUtil; +import org.l2jmobius.gameserver.entity.actor.enums.player.IllegalActionPunishmentType; +import org.l2jmobius.gameserver.entity.groups.PartyExpType; + +/** + * This class loads all the player related configurations. + * @author Mobius + */ +public class PlayerConfig +{ + private static final Logger LOGGER = Logger.getLogger(PlayerConfig.class.getName()); + + // File + private static final String PLAYER_CONFIG_FILE = "./config/Player.ini"; + + // Constants + public static boolean PLAYER_DELEVEL; + public static boolean DECREASE_SKILL_LEVEL; + public static double ALT_WEIGHT_LIMIT; + public static int RUN_SPD_BOOST; + public static int DEATH_PENALTY_CHANCE; + public static double RESPAWN_RESTORE_CP; + public static double RESPAWN_RESTORE_HP; + public static double RESPAWN_RESTORE_MP; + public static double HP_REGEN_MULTIPLIER; + public static double MP_REGEN_MULTIPLIER; + public static double CP_REGEN_MULTIPLIER; + public static boolean ENABLE_MODIFY_SKILL_DURATION; + public static Map SKILL_DURATION_LIST; + public static boolean ENABLE_MODIFY_SKILL_REUSE; + public static Map SKILL_REUSE_LIST; + public static boolean AUTO_LEARN_SKILLS; + public static boolean AUTO_LEARN_SKILLS_WITHOUT_ITEMS; + public static boolean AUTO_LEARN_FS_SKILLS; + public static boolean SHOW_EFFECT_MESSAGES_ON_LOGIN; + public static boolean AUTO_LOOT_HERBS; + public static byte BUFFS_MAX_AMOUNT; + public static byte DANCES_MAX_AMOUNT; + public static boolean DANCE_CANCEL_BUFF; + public static boolean DANCE_CONSUME_ADDITIONAL_MP; + public static boolean ALT_STORE_DANCES; + public static boolean ALT_STORE_TOGGLES; + public static boolean AUTO_LEARN_DIVINE_INSPIRATION; + public static boolean ALT_GAME_CANCEL_BOW; + public static boolean ALT_GAME_CANCEL_CAST; + public static boolean ALT_GAME_MAGICFAILURES; + public static int PLAYER_FAKEDEATH_UP_PROTECTION; + public static boolean STORE_SKILL_COOLTIME; + public static boolean SUBCLASS_STORE_SKILL_COOLTIME; + public static boolean SUMMON_STORE_SKILL_COOLTIME; + public static boolean ALT_GAME_SHIELD_BLOCKS; + public static int ALT_PERFECT_SHLD_BLOCK; + public static long EFFECT_TICK_RATIO; + public static boolean FAKE_DEATH_UNTARGET; + public static boolean FAKE_DEATH_DAMAGE_STAND; + public static boolean CALCULATE_MAGIC_SUCCESS_BY_SKILL_MAGIC_LEVEL; + public static boolean CALCULATE_DISTANCE_BOW_DAMAGE; + public static boolean LIFE_CRYSTAL_NEEDED; + public static boolean ES_SP_BOOK_NEEDED; + public static boolean DIVINE_SP_BOOK_NEEDED; + public static boolean ALT_GAME_SKILL_LEARN; + public static boolean ALT_GAME_SUBCLASS_WITHOUT_QUESTS; + public static boolean ALT_GAME_SUBCLASS_EVERYWHERE; + public static boolean RESTORE_SERVITOR_ON_RECONNECT; + public static boolean RESTORE_PET_ON_RECONNECT; + public static int FEE_DELETE_TRANSFER_SKILLS; + public static boolean ENABLE_VITALITY; + public static boolean RECOVER_VITALITY_ON_RECONNECT; + public static int STARTING_VITALITY_POINTS; + public static boolean RAIDBOSS_USE_VITALITY; + public static double MAX_BONUS_EXP; + public static double MAX_BONUS_SP; + public static int MAX_RUN_SPEED; + public static int MAX_PATK; + public static int MAX_MATK; + public static int MAX_PCRIT_RATE; + public static int MAX_MCRIT_RATE; + public static int MAX_PATK_SPEED; + public static int MAX_MATK_SPEED; + public static int MAX_EVASION; + public static int MIN_ABNORMAL_STATE_SUCCESS_RATE; + public static int MAX_ABNORMAL_STATE_SUCCESS_RATE; + public static long MAX_SP; + public static byte PLAYER_MAXIMUM_LEVEL; + public static byte MAX_SUBCLASS; + public static byte BASE_SUBCLASS_LEVEL; + public static byte MAX_SUBCLASS_LEVEL; + public static int MAX_PVTSTORESELL_SLOTS_DWARF; + public static int MAX_PVTSTORESELL_SLOTS_OTHER; + public static int MAX_PVTSTOREBUY_SLOTS_DWARF; + public static int MAX_PVTSTOREBUY_SLOTS_OTHER; + public static int INVENTORY_MAXIMUM_NO_DWARF; + public static int INVENTORY_MAXIMUM_DWARF; + public static int INVENTORY_MAXIMUM_GM; + public static int MAX_ITEM_IN_PACKET; + public static int WAREHOUSE_SLOTS_DWARF; + public static int WAREHOUSE_SLOTS_NO_DWARF; + public static int WAREHOUSE_SLOTS_CLAN; + public static int ALT_FREIGHT_SLOTS; + public static int ALT_FREIGHT_PRICE; + public static int[] ENCHANT_BLACKLIST; + public static boolean DISABLE_OVER_ENCHANTING; + public static boolean OVER_ENCHANT_PROTECTION; + public static IllegalActionPunishmentType OVER_ENCHANT_PUNISHMENT; + public static int AUGMENTATION_NG_SKILL_CHANCE; + public static int AUGMENTATION_NG_GLOW_CHANCE; + public static int AUGMENTATION_MID_SKILL_CHANCE; + public static int AUGMENTATION_MID_GLOW_CHANCE; + public static int AUGMENTATION_HIGH_SKILL_CHANCE; + public static int AUGMENTATION_HIGH_GLOW_CHANCE; + public static int AUGMENTATION_TOP_SKILL_CHANCE; + public static int AUGMENTATION_TOP_GLOW_CHANCE; + public static int AUGMENTATION_BASESTAT_CHANCE; + public static boolean RETAIL_LIKE_AUGMENTATION; + public static int[] RETAIL_LIKE_AUGMENTATION_NG_CHANCE; + public static int[] RETAIL_LIKE_AUGMENTATION_MID_CHANCE; + public static int[] RETAIL_LIKE_AUGMENTATION_HIGH_CHANCE; + public static int[] RETAIL_LIKE_AUGMENTATION_TOP_CHANCE; + public static int[] AUGMENTATION_BLACKLIST; + public static boolean ALT_ALLOW_AUGMENT_PVP_ITEMS; + public static boolean ALT_ALLOW_AUGMENT_TRADE; + public static boolean ALT_ALLOW_AUGMENT_DESTROY; + public static double SOUL_CRYSTAL_CHANCE_MULTIPLIER; + public static boolean ALT_GAME_KARMA_PLAYER_CAN_BE_KILLED_IN_PEACEZONE; + public static boolean ALT_GAME_KARMA_PLAYER_CAN_SHOP; + public static boolean ALT_GAME_KARMA_PLAYER_CAN_TELEPORT; + public static boolean ALT_GAME_KARMA_PLAYER_CAN_USE_GK; + public static boolean ALT_GAME_KARMA_PLAYER_CAN_TRADE; + public static boolean ALT_GAME_KARMA_PLAYER_CAN_USE_WAREHOUSE; + public static boolean FAME_SYSTEM_ENABLED; + public static int MAX_PERSONAL_FAME_POINTS; + public static int FORTRESS_ZONE_FAME_TASK_FREQUENCY; + public static int FORTRESS_ZONE_FAME_AQUIRE_POINTS; + public static int CASTLE_ZONE_FAME_TASK_FREQUENCY; + public static int CASTLE_ZONE_FAME_AQUIRE_POINTS; + public static boolean FAME_FOR_DEAD_PLAYERS; + public static boolean IS_CRAFTING_ENABLED; + public static int DWARF_RECIPE_LIMIT; + public static int COMMON_RECIPE_LIMIT; + public static boolean ALT_GAME_CREATION; + public static double ALT_GAME_CREATION_SPEED; + public static double ALT_GAME_CREATION_XP_RATE; + public static double ALT_GAME_CREATION_SP_RATE; + public static boolean ALT_BLACKSMITH_USE_RECIPES; + public static boolean ALT_CLAN_LEADER_INSTANT_ACTIVATION; + public static int ALT_CLAN_JOIN_DAYS; + public static int ALT_CLAN_CREATE_DAYS; + public static int ALT_CLAN_DISSOLVE_DAYS; + public static int ALT_ALLY_JOIN_DAYS_WHEN_LEAVED; + public static int ALT_ALLY_JOIN_DAYS_WHEN_DISMISSED; + public static int ALT_ACCEPT_CLAN_DAYS_WHEN_DISMISSED; + public static int ALT_CREATE_ALLY_DAYS_WHEN_DISSOLVED; + public static int ALT_MAX_NUM_OF_CLANS_IN_ALLY; + public static int ALT_CLAN_MEMBERS_FOR_WAR; + public static boolean ALT_GAME_NEW_CHAR_ALWAYS_IS_NEWBIE; + public static boolean ALT_MEMBERS_CAN_WITHDRAW_FROM_CLANWH; + public static boolean REMOVE_CASTLE_CIRCLETS; + public static int ALT_PARTY_RANGE; + public static boolean ALT_LEAVE_PARTY_LEADER; + public static int STARTING_ADENA; + public static byte STARTING_LEVEL; + public static int STARTING_SP; + public static int MAX_ADENA; + public static boolean AUTO_LOOT; + public static boolean AUTO_LOOT_RAIDS; + public static boolean AUTO_LOOT_SLOT_LIMIT; + public static int LOOT_RAIDS_PRIVILEGE_INTERVAL; + public static int LOOT_RAIDS_PRIVILEGE_CC_SIZE; + public static Set AUTO_LOOT_ITEM_IDS; + public static boolean ENABLE_KEYBOARD_MOVEMENT; + public static int UNSTUCK_INTERVAL; + public static int TELEPORT_WATCHDOG_TIMEOUT; + public static int PLAYER_SPAWN_PROTECTION; + public static int PLAYER_TELEPORT_PROTECTION; + public static boolean RANDOM_RESPAWN_IN_TOWN_ENABLED; + public static boolean OFFSET_ON_TELEPORT_ENABLED; + public static int MAX_OFFSET_ON_TELEPORT; + public static boolean TELEPORT_WHILE_SIEGE_IN_PROGRESS; + public static boolean PETITIONING_ALLOWED; + public static int MAX_PETITIONS_PER_PLAYER; + public static int MAX_PETITIONS_PENDING; + public static int MAX_FREE_TELEPORT_LEVEL; + public static boolean ALT_RECOMMEND; + public static int DELETE_DAYS; + public static boolean DISCONNECT_AFTER_DEATH; + public static PartyExpType PARTY_XP_CUTOFF_METHOD; + public static double PARTY_XP_CUTOFF_PERCENT; + public static int PARTY_XP_CUTOFF_LEVEL; + public static int[][] PARTY_XP_CUTOFF_GAPS; + public static int[] PARTY_XP_CUTOFF_GAP_PERCENTS; + public static boolean DISABLE_TUTORIAL; + public static boolean EXPERTISE_PENALTY; + public static boolean STORE_RECIPE_SHOPLIST; + public static String[] FORBIDDEN_NAMES; + public static boolean SILENCE_MODE_EXCLUDE; + public static boolean ALT_VALIDATE_TRIGGER_SKILLS; + public static int PLAYER_MOVEMENT_BLOCK_TIME; + public static boolean RANDOMIZE_AUTO_ATTACK_DAMAGE; + public static boolean RANDOMIZE_PHYSICAL_SKILL_DAMAGE; + public static boolean RANDOMIZE_MAGICAL_SKILL_DAMAGE; + + public static void load() + { + final ConfigReader config = new ConfigReader(PLAYER_CONFIG_FILE); + PLAYER_DELEVEL = config.getBoolean("Delevel", true); + DECREASE_SKILL_LEVEL = config.getBoolean("DecreaseSkillOnDelevel", true); + ALT_WEIGHT_LIMIT = config.getDouble("AltWeightLimit", 1); + RUN_SPD_BOOST = config.getInt("RunSpeedBoost", 0); + DEATH_PENALTY_CHANCE = config.getInt("DeathPenaltyChance", 20); + RESPAWN_RESTORE_CP = config.getDouble("RespawnRestoreCP", 0) / 100; + RESPAWN_RESTORE_HP = config.getDouble("RespawnRestoreHP", 65) / 100; + RESPAWN_RESTORE_MP = config.getDouble("RespawnRestoreMP", 0) / 100; + HP_REGEN_MULTIPLIER = config.getDouble("HpRegenMultiplier", 100) / 100; + MP_REGEN_MULTIPLIER = config.getDouble("MpRegenMultiplier", 100) / 100; + CP_REGEN_MULTIPLIER = config.getDouble("CpRegenMultiplier", 100) / 100; + ENABLE_MODIFY_SKILL_DURATION = config.getBoolean("EnableModifySkillDuration", false); + if (ENABLE_MODIFY_SKILL_DURATION) + { + final String[] propertySplit = config.getString("SkillDurationList", "").split(";"); + SKILL_DURATION_LIST = new HashMap<>(propertySplit.length); + for (String skill : propertySplit) + { + final String[] skillSplit = skill.split(","); + if (skillSplit.length != 2) + { + LOGGER.warning("[SkillDurationList]: invalid config property -> SkillDurationList " + skill); + } + else + { + try + { + SKILL_DURATION_LIST.put(Integer.parseInt(skillSplit[0]), Integer.parseInt(skillSplit[1])); + } + catch (NumberFormatException nfe) + { + if (!skill.isEmpty()) + { + LOGGER.warning(StringUtil.concat("[SkillDurationList]: invalid config property -> SkillList \"", skillSplit[0], "\"", skillSplit[1])); + } + } + } + } + } + ENABLE_MODIFY_SKILL_REUSE = config.getBoolean("EnableModifySkillReuse", false); + if (ENABLE_MODIFY_SKILL_REUSE) + { + final String[] propertySplit = config.getString("SkillReuseList", "").split(";"); + SKILL_REUSE_LIST = new HashMap<>(propertySplit.length); + for (String skill : propertySplit) + { + final String[] skillSplit = skill.split(","); + if (skillSplit.length != 2) + { + LOGGER.warning(StringUtil.concat("[SkillReuseList]: invalid config property -> SkillReuseList \"", skill, "\"")); + } + else + { + try + { + SKILL_REUSE_LIST.put(Integer.parseInt(skillSplit[0]), Integer.parseInt(skillSplit[1])); + } + catch (NumberFormatException nfe) + { + if (!skill.isEmpty()) + { + LOGGER.warning(StringUtil.concat("[SkillReuseList]: invalid config property -> SkillList \"", skillSplit[0], "\"", skillSplit[1])); + } + } + } + } + } + AUTO_LEARN_SKILLS = config.getBoolean("AutoLearnSkills", false); + AUTO_LEARN_SKILLS_WITHOUT_ITEMS = config.getBoolean("AutoLearnSkillsWithoutItems", false); + AUTO_LEARN_FS_SKILLS = config.getBoolean("AutoLearnForgottenScrollSkills", false); + SHOW_EFFECT_MESSAGES_ON_LOGIN = config.getBoolean("ShowEffectMessagesOnLogin", false); + AUTO_LOOT_HERBS = config.getBoolean("AutoLootHerbs", false); + BUFFS_MAX_AMOUNT = config.getByte("MaxBuffAmount", (byte) 20); + DANCES_MAX_AMOUNT = config.getByte("MaxDanceAmount", (byte) 12); + DANCE_CANCEL_BUFF = config.getBoolean("DanceCancelBuff", false); + DANCE_CONSUME_ADDITIONAL_MP = config.getBoolean("DanceConsumeAdditionalMP", true); + ALT_STORE_DANCES = config.getBoolean("AltStoreDances", false); + ALT_STORE_TOGGLES = config.getBoolean("AltStoreToggles", false); + AUTO_LEARN_DIVINE_INSPIRATION = config.getBoolean("AutoLearnDivineInspiration", false); + ALT_GAME_CANCEL_BOW = config.getString("AltGameCancelByHit", "Cast").equalsIgnoreCase("bow") || config.getString("AltGameCancelByHit", "Cast").equalsIgnoreCase("all"); + ALT_GAME_CANCEL_CAST = config.getString("AltGameCancelByHit", "Cast").equalsIgnoreCase("cast") || config.getString("AltGameCancelByHit", "Cast").equalsIgnoreCase("all"); + ALT_GAME_MAGICFAILURES = config.getBoolean("MagicFailures", true); + PLAYER_FAKEDEATH_UP_PROTECTION = config.getInt("PlayerFakeDeathUpProtection", 0); + STORE_SKILL_COOLTIME = config.getBoolean("StoreSkillCooltime", true); + SUBCLASS_STORE_SKILL_COOLTIME = config.getBoolean("SubclassStoreSkillCooltime", false); + SUMMON_STORE_SKILL_COOLTIME = config.getBoolean("SummonStoreSkillCooltime", true); + ALT_GAME_SHIELD_BLOCKS = config.getBoolean("AltShieldBlocks", false); + ALT_PERFECT_SHLD_BLOCK = config.getInt("AltPerfectShieldBlockRate", 10); + EFFECT_TICK_RATIO = config.getLong("EffectTickRatio", 666); + FAKE_DEATH_UNTARGET = config.getBoolean("FakeDeathUntarget", false); + FAKE_DEATH_DAMAGE_STAND = config.getBoolean("FakeDeathDamageStand", true); + CALCULATE_MAGIC_SUCCESS_BY_SKILL_MAGIC_LEVEL = config.getBoolean("CalculateMagicSuccessBySkillMagicLevel", true); + CALCULATE_DISTANCE_BOW_DAMAGE = config.getBoolean("DistanceBowDamage", false); + LIFE_CRYSTAL_NEEDED = config.getBoolean("LifeCrystalNeeded", true); + ES_SP_BOOK_NEEDED = config.getBoolean("EnchantSkillSpBookNeeded", true); + DIVINE_SP_BOOK_NEEDED = config.getBoolean("DivineInspirationSpBookNeeded", true); + ALT_GAME_SKILL_LEARN = config.getBoolean("AltGameSkillLearn", false); + ALT_GAME_SUBCLASS_WITHOUT_QUESTS = config.getBoolean("AltSubClassWithoutQuests", false); + ALT_GAME_SUBCLASS_EVERYWHERE = config.getBoolean("AltSubclassEverywhere", false); + RESTORE_SERVITOR_ON_RECONNECT = config.getBoolean("RestoreServitorOnReconnect", true); + RESTORE_PET_ON_RECONNECT = config.getBoolean("RestorePetOnReconnect", true); + FEE_DELETE_TRANSFER_SKILLS = config.getInt("FeeDeleteTransferSkills", 10000000); + ENABLE_VITALITY = config.getBoolean("EnableVitality", false); + RECOVER_VITALITY_ON_RECONNECT = config.getBoolean("RecoverVitalityOnReconnect", true); + STARTING_VITALITY_POINTS = config.getInt("StartingVitalityPoints", 20000); + RAIDBOSS_USE_VITALITY = config.getBoolean("RaidbossUseVitality", true); + MAX_BONUS_EXP = config.getDouble("MaxExpBonus", 3.5); + MAX_BONUS_SP = config.getDouble("MaxSpBonus", 3.5); + MAX_RUN_SPEED = config.getInt("MaxRunSpeed", 250); + MAX_PATK = config.getInt("MaxPAtk", 999999); + MAX_MATK = config.getInt("MaxMAtk", 999999); + MAX_PCRIT_RATE = config.getInt("MaxPCritRate", 500); + MAX_MCRIT_RATE = config.getInt("MaxMCritRate", 200); + MAX_PATK_SPEED = config.getInt("MaxPAtkSpeed", 1500); + MAX_MATK_SPEED = config.getInt("MaxMAtkSpeed", 1999); + MAX_EVASION = config.getInt("MaxEvasion", 250); + MIN_ABNORMAL_STATE_SUCCESS_RATE = config.getInt("MinAbnormalStateSuccessRate", 10); + MAX_ABNORMAL_STATE_SUCCESS_RATE = config.getInt("MaxAbnormalStateSuccessRate", 90); + MAX_SP = config.getLong("MaxSp", 50000000000L) >= 0 ? config.getLong("MaxSp", 50000000000L) : Long.MAX_VALUE; + PLAYER_MAXIMUM_LEVEL = config.getByte("MaximumPlayerLevel", (byte) 80); + PLAYER_MAXIMUM_LEVEL++; + MAX_SUBCLASS = config.getByte("MaxSubclass", (byte) 3); + BASE_SUBCLASS_LEVEL = config.getByte("BaseSubclassLevel", (byte) 40); + MAX_SUBCLASS_LEVEL = config.getByte("MaxSubclassLevel", (byte) 80); + MAX_PVTSTORESELL_SLOTS_DWARF = config.getInt("MaxPvtStoreSellSlotsDwarf", 4); + MAX_PVTSTORESELL_SLOTS_OTHER = config.getInt("MaxPvtStoreSellSlotsOther", 3); + MAX_PVTSTOREBUY_SLOTS_DWARF = config.getInt("MaxPvtStoreBuySlotsDwarf", 5); + MAX_PVTSTOREBUY_SLOTS_OTHER = config.getInt("MaxPvtStoreBuySlotsOther", 4); + INVENTORY_MAXIMUM_NO_DWARF = config.getInt("MaximumSlotsForNoDwarf", 80); + INVENTORY_MAXIMUM_DWARF = config.getInt("MaximumSlotsForDwarf", 100); + INVENTORY_MAXIMUM_GM = config.getInt("MaximumSlotsForGMPlayer", 250); + MAX_ITEM_IN_PACKET = Math.max(INVENTORY_MAXIMUM_NO_DWARF, Math.max(INVENTORY_MAXIMUM_DWARF, INVENTORY_MAXIMUM_GM)); + WAREHOUSE_SLOTS_DWARF = config.getInt("MaximumWarehouseSlotsForDwarf", 120); + WAREHOUSE_SLOTS_NO_DWARF = config.getInt("MaximumWarehouseSlotsForNoDwarf", 100); + WAREHOUSE_SLOTS_CLAN = config.getInt("MaximumWarehouseSlotsForClan", 150); + ALT_FREIGHT_SLOTS = config.getInt("MaximumFreightSlots", 200); + ALT_FREIGHT_PRICE = config.getInt("FreightPrice", 1000); + final String[] notenchantable = config.getString("EnchantBlackList", "7816,7817,7818,7819,7820,7821,7822,7823,7824,7825,7826,7827,7828,7829,7830,7831,13293,13294,13296").split(","); + ENCHANT_BLACKLIST = new int[notenchantable.length]; + for (int i = 0; i < notenchantable.length; i++) + { + ENCHANT_BLACKLIST[i] = Integer.parseInt(notenchantable[i]); + } + Arrays.sort(ENCHANT_BLACKLIST); + DISABLE_OVER_ENCHANTING = config.getBoolean("DisableOverEnchanting", true); + OVER_ENCHANT_PROTECTION = config.getBoolean("OverEnchantProtection", true); + OVER_ENCHANT_PUNISHMENT = IllegalActionPunishmentType.findByName(config.getString("OverEnchantPunishment", "JAIL")); + AUGMENTATION_NG_SKILL_CHANCE = config.getInt("AugmentationNGSkillChance", 15); + AUGMENTATION_NG_GLOW_CHANCE = config.getInt("AugmentationNGGlowChance", 0); + AUGMENTATION_MID_SKILL_CHANCE = config.getInt("AugmentationMidSkillChance", 30); + AUGMENTATION_MID_GLOW_CHANCE = config.getInt("AugmentationMidGlowChance", 40); + AUGMENTATION_HIGH_SKILL_CHANCE = config.getInt("AugmentationHighSkillChance", 45); + AUGMENTATION_HIGH_GLOW_CHANCE = config.getInt("AugmentationHighGlowChance", 70); + AUGMENTATION_TOP_SKILL_CHANCE = config.getInt("AugmentationTopSkillChance", 60); + AUGMENTATION_TOP_GLOW_CHANCE = config.getInt("AugmentationTopGlowChance", 100); + AUGMENTATION_BASESTAT_CHANCE = config.getInt("AugmentationBaseStatChance", 1); + RETAIL_LIKE_AUGMENTATION = config.getBoolean("RetailLikeAugmentation", true); + String[] array = config.getString("RetailLikeAugmentationNoGradeChance", "55,35,7,3").split(","); + RETAIL_LIKE_AUGMENTATION_NG_CHANCE = new int[array.length]; + for (int i = 0; i < 4; i++) + { + RETAIL_LIKE_AUGMENTATION_NG_CHANCE[i] = Integer.parseInt(array[i]); + } + array = config.getString("RetailLikeAugmentationMidGradeChance", "55,35,7,3").split(","); + RETAIL_LIKE_AUGMENTATION_MID_CHANCE = new int[array.length]; + for (int i = 0; i < 4; i++) + { + RETAIL_LIKE_AUGMENTATION_MID_CHANCE[i] = Integer.parseInt(array[i]); + } + array = config.getString("RetailLikeAugmentationHighGradeChance", "55,35,7,3").split(","); + RETAIL_LIKE_AUGMENTATION_HIGH_CHANCE = new int[array.length]; + for (int i = 0; i < 4; i++) + { + RETAIL_LIKE_AUGMENTATION_HIGH_CHANCE[i] = Integer.parseInt(array[i]); + } + array = config.getString("RetailLikeAugmentationTopGradeChance", "55,35,7,3").split(","); + RETAIL_LIKE_AUGMENTATION_TOP_CHANCE = new int[array.length]; + for (int i = 0; i < 4; i++) + { + RETAIL_LIKE_AUGMENTATION_TOP_CHANCE[i] = Integer.parseInt(array[i]); + } + array = config.getString("AugmentationBlackList", "6656,6657,6658,6659,6660,6661,6662,8191").split(","); + AUGMENTATION_BLACKLIST = new int[array.length]; + for (int i = 0; i < array.length; i++) + { + AUGMENTATION_BLACKLIST[i] = Integer.parseInt(array[i]); + } + Arrays.sort(AUGMENTATION_BLACKLIST); + ALT_ALLOW_AUGMENT_PVP_ITEMS = config.getBoolean("AltAllowAugmentPvPItems", false); + ALT_ALLOW_AUGMENT_TRADE = config.getBoolean("AltAllowAugmentTrade", false); + ALT_ALLOW_AUGMENT_DESTROY = config.getBoolean("AltAllowAugmentDestroy", true); + SOUL_CRYSTAL_CHANCE_MULTIPLIER = config.getDouble("SoulCrystalChanceMultiplier", 1); + ALT_GAME_KARMA_PLAYER_CAN_BE_KILLED_IN_PEACEZONE = config.getBoolean("AltKarmaPlayerCanBeKilledInPeaceZone", false); + ALT_GAME_KARMA_PLAYER_CAN_SHOP = config.getBoolean("AltKarmaPlayerCanShop", true); + ALT_GAME_KARMA_PLAYER_CAN_TELEPORT = config.getBoolean("AltKarmaPlayerCanTeleport", true); + ALT_GAME_KARMA_PLAYER_CAN_USE_GK = config.getBoolean("AltKarmaPlayerCanUseGK", false); + ALT_GAME_KARMA_PLAYER_CAN_TRADE = config.getBoolean("AltKarmaPlayerCanTrade", true); + ALT_GAME_KARMA_PLAYER_CAN_USE_WAREHOUSE = config.getBoolean("AltKarmaPlayerCanUseWareHouse", true); + FAME_SYSTEM_ENABLED = config.getBoolean("EnableFameSystem", true); + MAX_PERSONAL_FAME_POINTS = config.getInt("MaxPersonalFamePoints", 100000); + FORTRESS_ZONE_FAME_TASK_FREQUENCY = config.getInt("FortressZoneFameTaskFrequency", 300); + FORTRESS_ZONE_FAME_AQUIRE_POINTS = config.getInt("FortressZoneFameAquirePoints", 31); + CASTLE_ZONE_FAME_TASK_FREQUENCY = config.getInt("CastleZoneFameTaskFrequency", 300); + CASTLE_ZONE_FAME_AQUIRE_POINTS = config.getInt("CastleZoneFameAquirePoints", 125); + FAME_FOR_DEAD_PLAYERS = config.getBoolean("FameForDeadPlayers", true); + IS_CRAFTING_ENABLED = config.getBoolean("CraftingEnabled", true); + DWARF_RECIPE_LIMIT = config.getInt("DwarfRecipeLimit", 50); + COMMON_RECIPE_LIMIT = config.getInt("CommonRecipeLimit", 50); + ALT_GAME_CREATION = config.getBoolean("AltGameCreation", false); + ALT_GAME_CREATION_SPEED = config.getDouble("AltGameCreationSpeed", 1); + ALT_GAME_CREATION_XP_RATE = config.getDouble("AltGameCreationXpRate", 1); + ALT_GAME_CREATION_SP_RATE = config.getDouble("AltGameCreationSpRate", 1); + ALT_BLACKSMITH_USE_RECIPES = config.getBoolean("AltBlacksmithUseRecipes", true); + ALT_CLAN_LEADER_INSTANT_ACTIVATION = config.getBoolean("AltClanLeaderInstantActivation", false); + ALT_CLAN_JOIN_DAYS = config.getInt("DaysBeforeJoinAClan", 1); + ALT_CLAN_CREATE_DAYS = config.getInt("DaysBeforeCreateAClan", 10); + ALT_CLAN_DISSOLVE_DAYS = config.getInt("DaysToPassToDissolveAClan", 7); + ALT_ALLY_JOIN_DAYS_WHEN_LEAVED = config.getInt("DaysBeforeJoinAllyWhenLeaved", 1); + ALT_ALLY_JOIN_DAYS_WHEN_DISMISSED = config.getInt("DaysBeforeJoinAllyWhenDismissed", 1); + ALT_ACCEPT_CLAN_DAYS_WHEN_DISMISSED = config.getInt("DaysBeforeAcceptNewClanWhenDismissed", 1); + ALT_CREATE_ALLY_DAYS_WHEN_DISSOLVED = config.getInt("DaysBeforeCreateNewAllyWhenDissolved", 1); + ALT_MAX_NUM_OF_CLANS_IN_ALLY = config.getInt("AltMaxNumOfClansInAlly", 3); + ALT_CLAN_MEMBERS_FOR_WAR = config.getInt("AltClanMembersForWar", 15); + ALT_GAME_NEW_CHAR_ALWAYS_IS_NEWBIE = config.getBoolean("AltNewCharAlwaysIsNewbie", false); + ALT_MEMBERS_CAN_WITHDRAW_FROM_CLANWH = config.getBoolean("AltMembersCanWithdrawFromClanWH", false); + REMOVE_CASTLE_CIRCLETS = config.getBoolean("RemoveCastleCirclets", true); + ALT_PARTY_RANGE = config.getInt("AltPartyRange", 1500); + ALT_LEAVE_PARTY_LEADER = config.getBoolean("AltLeavePartyLeader", false); + STARTING_ADENA = config.getInt("StartingAdena", 0); + STARTING_LEVEL = config.getByte("StartingLevel", (byte) 1); + STARTING_SP = config.getInt("StartingSP", 0); + MAX_ADENA = config.getInt("MaxAdena", 2000000000); + if (MAX_ADENA < 0) + { + MAX_ADENA = Integer.MAX_VALUE; + } + AUTO_LOOT = config.getBoolean("AutoLoot", false); + AUTO_LOOT_RAIDS = config.getBoolean("AutoLootRaids", false); + AUTO_LOOT_SLOT_LIMIT = config.getBoolean("AutoLootSlotLimit", false); + LOOT_RAIDS_PRIVILEGE_INTERVAL = config.getInt("RaidLootRightsInterval", 900) * 1000; + LOOT_RAIDS_PRIVILEGE_CC_SIZE = config.getInt("RaidLootRightsCCSize", 45); + final String[] autoLootItemIds = config.getString("AutoLootItemIds", "0").split(","); + AUTO_LOOT_ITEM_IDS = new HashSet<>(autoLootItemIds.length); + for (String item : autoLootItemIds) + { + Integer itm = 0; + try + { + itm = Integer.parseInt(item); + } + catch (NumberFormatException nfe) + { + LOGGER.warning("Auto loot item ids: Wrong ItemId passed: " + item); + LOGGER.warning(nfe.getMessage()); + } + if (itm != 0) + { + AUTO_LOOT_ITEM_IDS.add(itm); + } + } + ENABLE_KEYBOARD_MOVEMENT = config.getBoolean("KeyboardMovement", true); + UNSTUCK_INTERVAL = config.getInt("UnstuckInterval", 300); + TELEPORT_WATCHDOG_TIMEOUT = config.getInt("TeleportWatchdogTimeout", 0); + PLAYER_SPAWN_PROTECTION = config.getInt("PlayerSpawnProtection", 0); + PLAYER_TELEPORT_PROTECTION = config.getInt("PlayerTeleportProtection", 0); + RANDOM_RESPAWN_IN_TOWN_ENABLED = config.getBoolean("RandomRespawnInTownEnabled", true); + OFFSET_ON_TELEPORT_ENABLED = config.getBoolean("OffsetOnTeleportEnabled", true); + MAX_OFFSET_ON_TELEPORT = config.getInt("MaxOffsetOnTeleport", 50); + TELEPORT_WHILE_SIEGE_IN_PROGRESS = config.getBoolean("TeleportWhileSiegeInProgress", true); + PETITIONING_ALLOWED = config.getBoolean("PetitioningAllowed", true); + MAX_PETITIONS_PER_PLAYER = config.getInt("MaxPetitionsPerPlayer", 5); + MAX_PETITIONS_PENDING = config.getInt("MaxPetitionsPending", 25); + MAX_FREE_TELEPORT_LEVEL = config.getInt("MaxFreeTeleportLevel", 40); + ALT_RECOMMEND = config.getBoolean("AltRecommend", false); + DELETE_DAYS = config.getInt("DeleteCharAfterDays", 7); + DISCONNECT_AFTER_DEATH = config.getBoolean("DisconnectAfterDeath", true); + PARTY_XP_CUTOFF_METHOD = Enum.valueOf(PartyExpType.class, config.getString("PartyXpCutoffMethod", "LEVEL").toUpperCase()); + PARTY_XP_CUTOFF_PERCENT = config.getDouble("PartyXpCutoffPercent", 3); + PARTY_XP_CUTOFF_LEVEL = config.getInt("PartyXpCutoffLevel", 20); + final String[] gaps = config.getString("PartyXpCutoffGaps", "0,9;10,14;15,99").split(";"); + PARTY_XP_CUTOFF_GAPS = new int[gaps.length][2]; + for (int i = 0; i < gaps.length; i++) + { + PARTY_XP_CUTOFF_GAPS[i] = new int[] + { + Integer.parseInt(gaps[i].split(",")[0]), + Integer.parseInt(gaps[i].split(",")[1]) + }; + } + final String[] percents = config.getString("PartyXpCutoffGapPercent", "100;30;0").split(";"); + PARTY_XP_CUTOFF_GAP_PERCENTS = new int[percents.length]; + for (int i = 0; i < percents.length; i++) + { + PARTY_XP_CUTOFF_GAP_PERCENTS[i] = Integer.parseInt(percents[i]); + } + DISABLE_TUTORIAL = config.getBoolean("DisableTutorial", false); + EXPERTISE_PENALTY = config.getBoolean("ExpertisePenalty", true); + STORE_RECIPE_SHOPLIST = config.getBoolean("StoreRecipeShopList", false); + FORBIDDEN_NAMES = config.getString("ForbiddenNames", "").split(","); + SILENCE_MODE_EXCLUDE = config.getBoolean("SilenceModeExclude", false); + ALT_VALIDATE_TRIGGER_SKILLS = config.getBoolean("AltValidateTriggerSkills", false); + PLAYER_MOVEMENT_BLOCK_TIME = config.getInt("NpcTalkBlockingTime", 0) * 1000; + RANDOMIZE_AUTO_ATTACK_DAMAGE = config.getBoolean("RandomizeAutoAttackDamage", true); + RANDOMIZE_PHYSICAL_SKILL_DAMAGE = config.getBoolean("RandomizePhysicalSkillDamage", true); + RANDOMIZE_MAGICAL_SKILL_DAMAGE = config.getBoolean("RandomizeMagicalSkillDamage", true); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/PvpConfig.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/PvpConfig.java new file mode 100644 index 0000000..97580f4 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/PvpConfig.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.config; + +import java.util.Arrays; + +import org.l2jmobius.commons.util.ConfigReader; + +/** + * This class loads all the PVP related configurations. + * @author Mobius + */ +public class PvpConfig +{ + // File + private static final String PVP_CONFIG_FILE = "./config/PVP.ini"; + + // Constants + public static boolean KARMA_DROP_GM; + public static boolean KARMA_AWARD_PK_KILL; + public static int KARMA_PK_LIMIT; + public static String KARMA_NONDROPPABLE_PET_ITEMS; + public static String KARMA_NONDROPPABLE_ITEMS; + public static int[] KARMA_LIST_NONDROPPABLE_PET_ITEMS; + public static int[] KARMA_LIST_NONDROPPABLE_ITEMS; + public static boolean ANTIFEED_ENABLE; + public static boolean ANTIFEED_DUALBOX; + public static boolean ANTIFEED_DISCONNECTED_AS_DUALBOX; + public static int ANTIFEED_INTERVAL; + public static int PVP_NORMAL_TIME; + public static int PVP_PVP_TIME; + public static boolean FLAG_PLAYER_ON_RAID_ATTACK; + public static boolean FLAG_PLAYER_ON_CHAMPION_ATTACK; + public static boolean FLAG_PLAYER_ON_SUMMON_PET_RAID_CHAMPION_ATTACK; + + public static void load() + { + final ConfigReader config = new ConfigReader(PVP_CONFIG_FILE); + KARMA_DROP_GM = config.getBoolean("CanGMDropEquipment", false); + KARMA_AWARD_PK_KILL = config.getBoolean("AwardPKKillPVPPoint", false); + KARMA_PK_LIMIT = config.getInt("MinimumPKRequiredToDrop", 5); + KARMA_NONDROPPABLE_PET_ITEMS = config.getString("ListOfPetItems", "2375,3500,3501,3502,4422,4423,4424,4425,6648,6649,6650,9882"); + KARMA_NONDROPPABLE_ITEMS = config.getString("ListOfNonDroppableItems", "57,1147,425,1146,461,10,2368,7,6,2370,2369,6842,6611,6612,6613,6614,6615,6616,6617,6618,6619,6620,6621,7694,8181,5575,7694"); + String[] karma = KARMA_NONDROPPABLE_PET_ITEMS.split(","); + KARMA_LIST_NONDROPPABLE_PET_ITEMS = new int[karma.length]; + for (int i = 0; i < karma.length; i++) + { + KARMA_LIST_NONDROPPABLE_PET_ITEMS[i] = Integer.parseInt(karma[i]); + } + Arrays.sort(KARMA_LIST_NONDROPPABLE_PET_ITEMS); + karma = KARMA_NONDROPPABLE_ITEMS.split(","); + KARMA_LIST_NONDROPPABLE_ITEMS = new int[karma.length]; + for (int i = 0; i < karma.length; i++) + { + KARMA_LIST_NONDROPPABLE_ITEMS[i] = Integer.parseInt(karma[i]); + } + Arrays.sort(KARMA_LIST_NONDROPPABLE_ITEMS); + ANTIFEED_ENABLE = config.getBoolean("AntiFeedEnable", false); + ANTIFEED_DUALBOX = config.getBoolean("AntiFeedDualbox", true); + ANTIFEED_DISCONNECTED_AS_DUALBOX = config.getBoolean("AntiFeedDisconnectedAsDualbox", true); + ANTIFEED_INTERVAL = config.getInt("AntiFeedInterval", 120) * 1000; + PVP_NORMAL_TIME = config.getInt("PvPVsNormalTime", 120000); + PVP_PVP_TIME = config.getInt("PvPVsPvPTime", 60000); + FLAG_PLAYER_ON_RAID_ATTACK = config.getBoolean("FlagPlayerOnRaidAttack", true); + FLAG_PLAYER_ON_CHAMPION_ATTACK = config.getBoolean("FlagPlayerOnChampionAttack", true); + FLAG_PLAYER_ON_SUMMON_PET_RAID_CHAMPION_ATTACK = config.getBoolean("FlagPlayerOnSummonPetRaidChampionAttack", true); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/RatesConfig.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/RatesConfig.java new file mode 100644 index 0000000..44de280 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/RatesConfig.java @@ -0,0 +1,243 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.config; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Logger; + +import org.l2jmobius.commons.util.ConfigReader; +import org.l2jmobius.commons.util.StringUtil; +import org.l2jmobius.gameserver.entity.actor.enums.npc.DropType; +import org.l2jmobius.gameserver.entity.actor.holders.npc.DropHolder; + +/** + * This class loads all the rates related configurations. + * @author Mobius + */ +public class RatesConfig +{ + private static final Logger LOGGER = Logger.getLogger(RatesConfig.class.getName()); + + // File + private static final String RATES_CONFIG_FILE = "./config/Rates.ini"; + + // Constants + public static float RATE_XP; + public static float RATE_SP; + public static float RATE_PARTY_XP; + public static float RATE_PARTY_SP; + public static float RATE_EXTRACTABLE; + public static int RATE_DROP_MANOR; + public static float QUEST_ITEM_DROP_AMOUNT_MULTIPLIER; + public static float RATE_QUEST_REWARD; + public static float RATE_QUEST_REWARD_XP; + public static float RATE_QUEST_REWARD_SP; + public static float RATE_QUEST_REWARD_ADENA; + public static boolean RATE_QUEST_REWARD_USE_MULTIPLIERS; + public static float RATE_QUEST_REWARD_POTION; + public static float RATE_QUEST_REWARD_SCROLL; + public static float RATE_QUEST_REWARD_RECIPE; + public static float RATE_QUEST_REWARD_MATERIAL; + public static int MONSTER_EXP_MAX_LEVEL_DIFFERENCE; + public static float RATE_VITALITY_LEVEL_1; + public static float RATE_VITALITY_LEVEL_2; + public static float RATE_VITALITY_LEVEL_3; + public static float RATE_VITALITY_LEVEL_4; + public static float RATE_RECOVERY_VITALITY_PEACE_ZONE; + public static float RATE_VITALITY_LOST; + public static float RATE_VITALITY_GAIN; + public static float RATE_RECOVERY_ON_RECONNECT; + public static float RATE_KARMA_LOST; + public static float RATE_KARMA_EXP_LOST; + public static float RATE_SIEGE_GUARDS_PRICE; + public static int PLAYER_DROP_LIMIT; + public static int PLAYER_RATE_DROP; + public static int PLAYER_RATE_DROP_ITEM; + public static int PLAYER_RATE_DROP_EQUIP; + public static int PLAYER_RATE_DROP_EQUIP_WEAPON; + public static float PET_XP_RATE; + public static int PET_FOOD_RATE; + public static float SINEATER_XP_RATE; + public static int KARMA_DROP_LIMIT; + public static int KARMA_RATE_DROP; + public static int KARMA_RATE_DROP_ITEM; + public static int KARMA_RATE_DROP_EQUIP; + public static int KARMA_RATE_DROP_EQUIP_WEAPON; + public static float RATE_DEATH_DROP_AMOUNT_MULTIPLIER; + public static float RATE_SPOIL_DROP_AMOUNT_MULTIPLIER; + public static float RATE_HERB_DROP_AMOUNT_MULTIPLIER; + public static float RATE_RAID_DROP_AMOUNT_MULTIPLIER; + public static float RATE_DEATH_DROP_CHANCE_MULTIPLIER; + public static float RATE_SPOIL_DROP_CHANCE_MULTIPLIER; + public static float RATE_HERB_DROP_CHANCE_MULTIPLIER; + public static float RATE_RAID_DROP_CHANCE_MULTIPLIER; + public static Map RATE_DROP_AMOUNT_BY_ID; + public static Map RATE_DROP_CHANCE_BY_ID; + public static int DROP_MAX_OCCURRENCES_NORMAL; + public static int DROP_MAX_OCCURRENCES_RAIDBOSS; + public static int DROP_ADENA_MIN_LEVEL_DIFFERENCE; + public static int DROP_ADENA_MAX_LEVEL_DIFFERENCE; + public static double DROP_ADENA_MIN_LEVEL_GAP_CHANCE; + public static int DROP_ITEM_MIN_LEVEL_DIFFERENCE; + public static int DROP_ITEM_MAX_LEVEL_DIFFERENCE; + public static double DROP_ITEM_MIN_LEVEL_GAP_CHANCE; + public static int EVENT_ITEM_MAX_LEVEL_DIFFERENCE; + public static boolean BOSS_DROP_ENABLED; + public static int BOSS_DROP_MIN_LEVEL; + public static int BOSS_DROP_MAX_LEVEL; + public static List BOSS_DROP_LIST = new ArrayList<>(); + + public static void load() + { + final ConfigReader config = new ConfigReader(RATES_CONFIG_FILE); + RATE_XP = config.getFloat("RateXp", 1); + RATE_SP = config.getFloat("RateSp", 1); + RATE_PARTY_XP = config.getFloat("RatePartyXp", 1); + RATE_PARTY_SP = config.getFloat("RatePartySp", 1); + RATE_EXTRACTABLE = config.getFloat("RateExtractable", 1); + RATE_DROP_MANOR = config.getInt("RateDropManor", 1); + QUEST_ITEM_DROP_AMOUNT_MULTIPLIER = config.getFloat("QuestItemDropAmountMultiplier", 1); + RATE_QUEST_REWARD = config.getFloat("RateQuestReward", 1); + RATE_QUEST_REWARD_XP = config.getFloat("RateQuestRewardXP", 1); + RATE_QUEST_REWARD_SP = config.getFloat("RateQuestRewardSP", 1); + RATE_QUEST_REWARD_ADENA = config.getFloat("RateQuestRewardAdena", 1); + RATE_QUEST_REWARD_USE_MULTIPLIERS = config.getBoolean("UseQuestRewardMultipliers", false); + RATE_QUEST_REWARD_POTION = config.getFloat("RateQuestRewardPotion", 1); + RATE_QUEST_REWARD_SCROLL = config.getFloat("RateQuestRewardScroll", 1); + RATE_QUEST_REWARD_RECIPE = config.getFloat("RateQuestRewardRecipe", 1); + RATE_QUEST_REWARD_MATERIAL = config.getFloat("RateQuestRewardMaterial", 1); + MONSTER_EXP_MAX_LEVEL_DIFFERENCE = config.getInt("MonsterExpMaxLevelDifference", 11); + RATE_VITALITY_LEVEL_1 = config.getFloat("RateVitalityLevel1", 1.5f); + RATE_VITALITY_LEVEL_2 = config.getFloat("RateVitalityLevel2", 2); + RATE_VITALITY_LEVEL_3 = config.getFloat("RateVitalityLevel3", 2.5f); + RATE_VITALITY_LEVEL_4 = config.getFloat("RateVitalityLevel4", 3); + RATE_RECOVERY_VITALITY_PEACE_ZONE = config.getFloat("RateRecoveryPeaceZone", 1); + RATE_VITALITY_LOST = config.getFloat("RateVitalityLost", 1); + RATE_VITALITY_GAIN = config.getFloat("RateVitalityGain", 1); + RATE_RECOVERY_ON_RECONNECT = config.getFloat("RateRecoveryOnReconnect", 4); + RATE_KARMA_LOST = config.getFloat("RateKarmaLost", -1); + if (RATE_KARMA_LOST == -1) + { + RATE_KARMA_LOST = RATE_XP; + } + RATE_KARMA_EXP_LOST = config.getFloat("RateKarmaExpLost", 1); + RATE_SIEGE_GUARDS_PRICE = config.getFloat("RateSiegeGuardsPrice", 1); + PLAYER_DROP_LIMIT = config.getInt("PlayerDropLimit", 3); + PLAYER_RATE_DROP = config.getInt("PlayerRateDrop", 5); + PLAYER_RATE_DROP_ITEM = config.getInt("PlayerRateDropItem", 70); + PLAYER_RATE_DROP_EQUIP = config.getInt("PlayerRateDropEquip", 25); + PLAYER_RATE_DROP_EQUIP_WEAPON = config.getInt("PlayerRateDropEquipWeapon", 5); + PET_XP_RATE = config.getFloat("PetXpRate", 1); + PET_FOOD_RATE = config.getInt("PetFoodRate", 1); + SINEATER_XP_RATE = config.getFloat("SinEaterXpRate", 1); + KARMA_DROP_LIMIT = config.getInt("KarmaDropLimit", 10); + KARMA_RATE_DROP = config.getInt("KarmaRateDrop", 70); + KARMA_RATE_DROP_ITEM = config.getInt("KarmaRateDropItem", 50); + KARMA_RATE_DROP_EQUIP = config.getInt("KarmaRateDropEquip", 40); + KARMA_RATE_DROP_EQUIP_WEAPON = config.getInt("KarmaRateDropEquipWeapon", 10); + RATE_DEATH_DROP_AMOUNT_MULTIPLIER = config.getFloat("DeathDropAmountMultiplier", 1); + RATE_SPOIL_DROP_AMOUNT_MULTIPLIER = config.getFloat("SpoilDropAmountMultiplier", 1); + RATE_HERB_DROP_AMOUNT_MULTIPLIER = config.getFloat("HerbDropAmountMultiplier", 1); + RATE_RAID_DROP_AMOUNT_MULTIPLIER = config.getFloat("RaidDropAmountMultiplier", 1); + RATE_DEATH_DROP_CHANCE_MULTIPLIER = config.getFloat("DeathDropChanceMultiplier", 1); + RATE_SPOIL_DROP_CHANCE_MULTIPLIER = config.getFloat("SpoilDropChanceMultiplier", 1); + RATE_HERB_DROP_CHANCE_MULTIPLIER = config.getFloat("HerbDropChanceMultiplier", 1); + RATE_RAID_DROP_CHANCE_MULTIPLIER = config.getFloat("RaidDropChanceMultiplier", 1); + final String[] dropAmountMultiplier = config.getString("DropAmountMultiplierByItemId", "").split(";"); + RATE_DROP_AMOUNT_BY_ID = new HashMap<>(dropAmountMultiplier.length); + if (!dropAmountMultiplier[0].isEmpty()) + { + for (String item : dropAmountMultiplier) + { + final String[] itemSplit = item.split(","); + if (itemSplit.length != 2) + { + LOGGER.warning(StringUtil.concat("Config.load(): invalid config property -> DropAmountMultiplierByItemId \"", item, "\"")); + } + else + { + try + { + RATE_DROP_AMOUNT_BY_ID.put(Integer.parseInt(itemSplit[0]), Float.parseFloat(itemSplit[1])); + } + catch (NumberFormatException nfe) + { + if (!item.isEmpty()) + { + LOGGER.warning(StringUtil.concat("Config.load(): invalid config property -> DropAmountMultiplierByItemId \"", item, "\"")); + } + } + } + } + } + final String[] dropChanceMultiplier = config.getString("DropChanceMultiplierByItemId", "").split(";"); + RATE_DROP_CHANCE_BY_ID = new HashMap<>(dropChanceMultiplier.length); + if (!dropChanceMultiplier[0].isEmpty()) + { + for (String item : dropChanceMultiplier) + { + final String[] itemSplit = item.split(","); + if (itemSplit.length != 2) + { + LOGGER.warning(StringUtil.concat("Config.load(): invalid config property -> DropChanceMultiplierByItemId \"", item, "\"")); + } + else + { + try + { + RATE_DROP_CHANCE_BY_ID.put(Integer.parseInt(itemSplit[0]), Float.parseFloat(itemSplit[1])); + } + catch (NumberFormatException nfe) + { + if (!item.isEmpty()) + { + LOGGER.warning(StringUtil.concat("Config.load(): invalid config property -> DropChanceMultiplierByItemId \"", item, "\"")); + } + } + } + } + } + DROP_MAX_OCCURRENCES_NORMAL = config.getInt("DropMaxOccurrencesNormal", 2); + DROP_MAX_OCCURRENCES_RAIDBOSS = config.getInt("DropMaxOccurrencesRaidboss", 7); + DROP_ADENA_MIN_LEVEL_DIFFERENCE = config.getInt("DropAdenaMinLevelDifference", 8); + DROP_ADENA_MAX_LEVEL_DIFFERENCE = config.getInt("DropAdenaMaxLevelDifference", 15); + DROP_ADENA_MIN_LEVEL_GAP_CHANCE = config.getDouble("DropAdenaMinLevelGapChance", 10); + DROP_ITEM_MIN_LEVEL_DIFFERENCE = config.getInt("DropItemMinLevelDifference", 5); + DROP_ITEM_MAX_LEVEL_DIFFERENCE = config.getInt("DropItemMaxLevelDifference", 10); + DROP_ITEM_MIN_LEVEL_GAP_CHANCE = config.getDouble("DropItemMinLevelGapChance", 10); + EVENT_ITEM_MAX_LEVEL_DIFFERENCE = config.getInt("EventItemMaxLevelDifference", 9); + BOSS_DROP_ENABLED = config.getBoolean("BossDropEnable", false); + BOSS_DROP_MIN_LEVEL = config.getInt("BossDropMinLevel", 40); + BOSS_DROP_MAX_LEVEL = config.getInt("BossDropMaxLevel", 999); + BOSS_DROP_LIST.clear(); + for (String s : config.getString("BossDropList", "").trim().split(";")) + { + if (s.isEmpty()) + { + continue; + } + BOSS_DROP_LIST.add(new DropHolder(DropType.DROP, Integer.parseInt(s.split(",")[0]), Integer.parseInt(s.split(",")[1]), Integer.parseInt(s.split(",")[2]), (Double.parseDouble(s.split(",")[3])))); + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/ServerConfig.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/ServerConfig.java new file mode 100644 index 0000000..2579c1d --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/ServerConfig.java @@ -0,0 +1,559 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.config; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.math.BigInteger; +import java.net.Inet6Address; +import java.net.InterfaceAddress; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.net.URI; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Enumeration; +import java.util.List; +import java.util.Properties; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; +import java.util.stream.IntStream; + +import org.w3c.dom.Document; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; + +import org.l2jmobius.commons.util.ConfigReader; +import org.l2jmobius.commons.util.IXmlReader; +import org.l2jmobius.commons.util.StringUtil; + +/** + * This class loads all the server related configurations. + * @author Mobius + */ +public class ServerConfig +{ + private static final Logger LOGGER = Logger.getLogger(ServerConfig.class.getName()); + + // Files + private static final String SERVER_CONFIG_FILE = "./config/Server.ini"; + private static final String IPCONFIG_FILE = "./config/ipconfig.xml"; + private static final String CHAT_FILTER_FILE = "./config/chatfilter.txt"; + private static final String HEXID_FILE = "./config/hexid.txt"; + + // Constants + public static String GAMESERVER_HOSTNAME; + public static int PORT_GAME; + public static int GAME_SERVER_LOGIN_PORT; + public static String GAME_SERVER_LOGIN_HOST; + public static boolean PACKET_ENCRYPTION; + public static int REQUEST_ID; + public static boolean ACCEPT_ALTERNATE_ID; + public static File DATAPACK_ROOT; + public static File SCRIPT_ROOT; + public static Pattern CHARNAME_TEMPLATE_PATTERN; + public static Pattern PET_NAME_TEMPLATE_PATTERN; + public static Pattern CLAN_NAME_TEMPLATE_PATTERN; + public static int MAX_CHARACTERS_NUMBER_PER_ACCOUNT; + public static int MAXIMUM_ONLINE_USERS; + public static boolean HARDWARE_INFO_ENABLED; + public static boolean KICK_MISSING_HWID; + public static int MAX_PLAYERS_PER_HWID; + public static List PROTOCOL_LIST; + public static int SERVER_LIST_TYPE; + public static int SERVER_LIST_AGE; + public static boolean SERVER_LIST_BRACKET; + public static boolean DEADLOCK_WATCHER; + public static int DEADLOCK_CHECK_INTERVAL; + public static boolean RESTART_ON_DEADLOCK; + public static boolean SERVER_RESTART_SCHEDULE_ENABLED; + public static boolean SERVER_RESTART_SCHEDULE_MESSAGE; + public static int SERVER_RESTART_SCHEDULE_COUNTDOWN; + public static String[] SERVER_RESTART_SCHEDULE; + public static List SERVER_RESTART_DAYS; + public static boolean PRECAUTIONARY_RESTART_ENABLED; + public static boolean PRECAUTIONARY_RESTART_CPU; + public static boolean PRECAUTIONARY_RESTART_MEMORY; + public static boolean PRECAUTIONARY_RESTART_CHECKS; + public static int PRECAUTIONARY_RESTART_PERCENTAGE; + public static int PRECAUTIONARY_RESTART_DELAY; + public static List GAME_SERVER_SUBNETS; + public static List GAME_SERVER_HOSTS; + + // Other + public static boolean RESERVE_HOST_ON_LOGIN = false; + public static List FILTER_LIST; + public static int SERVER_ID; + public static byte[] HEX_ID; + + public static void load() + { + final ConfigReader config = new ConfigReader(SERVER_CONFIG_FILE); + GAMESERVER_HOSTNAME = config.getString("GameserverHostname", "0.0.0.0"); + PORT_GAME = config.getInt("GameserverPort", 7777); + GAME_SERVER_LOGIN_PORT = config.getInt("LoginPort", 9014); + GAME_SERVER_LOGIN_HOST = config.getString("LoginHost", "127.0.0.1"); + PACKET_ENCRYPTION = config.getBoolean("PacketEncryption", false); + REQUEST_ID = config.getInt("RequestServerID", 0); + ACCEPT_ALTERNATE_ID = config.getBoolean("AcceptAlternateID", true); + + try + { + DATAPACK_ROOT = new File(config.getString("DatapackRoot", ".").replace('\\', '/')).getCanonicalFile(); + } + catch (IOException e) + { + LOGGER.log(Level.WARNING, "Error setting datapack root!", e); + DATAPACK_ROOT = new File("."); + } + + try + { + SCRIPT_ROOT = new File(config.getString("ScriptRoot", "./data/scripts").replace('\\', '/')).getCanonicalFile(); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Error setting script root!", e); + SCRIPT_ROOT = new File("."); + } + + Pattern charNamePattern; + try + { + charNamePattern = Pattern.compile(config.getString("CnameTemplate", ".*")); + } + catch (PatternSyntaxException e) + { + LOGGER.log(Level.WARNING, "Character name pattern is invalid!", e); + charNamePattern = Pattern.compile(".*"); + } + CHARNAME_TEMPLATE_PATTERN = charNamePattern; + + Pattern petNamePattern; + try + { + petNamePattern = Pattern.compile(config.getString("PetNameTemplate", ".*")); + } + catch (PatternSyntaxException e) + { + LOGGER.log(Level.WARNING, "Pet name pattern is invalid!", e); + petNamePattern = Pattern.compile(".*"); + } + PET_NAME_TEMPLATE_PATTERN = petNamePattern; + + Pattern clanNamePattern; + try + { + clanNamePattern = Pattern.compile(config.getString("ClanNameTemplate", ".*")); + } + catch (PatternSyntaxException e) + { + LOGGER.log(Level.WARNING, "Clan name pattern is invalid!", e); + clanNamePattern = Pattern.compile(".*"); + } + + CLAN_NAME_TEMPLATE_PATTERN = clanNamePattern; + MAX_CHARACTERS_NUMBER_PER_ACCOUNT = config.getInt("CharMaxNumber", 7); + MAXIMUM_ONLINE_USERS = config.getInt("MaximumOnlineUsers", 100); + HARDWARE_INFO_ENABLED = config.getBoolean("EnableHardwareInfo", false); + KICK_MISSING_HWID = config.getBoolean("KickMissingHWID", false); + MAX_PLAYERS_PER_HWID = config.getInt("MaxPlayersPerHWID", 0); + if (MAX_PLAYERS_PER_HWID > 0) + { + KICK_MISSING_HWID = true; + } + + final String[] protocols = config.getString("AllowedProtocolRevisions", "746").split(";"); + PROTOCOL_LIST = new ArrayList<>(protocols.length); + for (String protocol : protocols) + { + try + { + PROTOCOL_LIST.add(Integer.parseInt(protocol.trim())); + } + catch (NumberFormatException e) + { + LOGGER.warning("Wrong config protocol version: " + protocol + ". Skipped."); + } + } + + SERVER_LIST_TYPE = getServerTypeId(config.getString("ServerListType", "Free").split(",")); + SERVER_LIST_AGE = config.getInt("ServerListAge", 0); + SERVER_LIST_BRACKET = config.getBoolean("ServerListBrackets", false); + DEADLOCK_WATCHER = config.getBoolean("DeadlockWatcher", true); + DEADLOCK_CHECK_INTERVAL = config.getInt("DeadlockCheckInterval", 20); + RESTART_ON_DEADLOCK = config.getBoolean("RestartOnDeadlock", false); + SERVER_RESTART_SCHEDULE_ENABLED = config.getBoolean("ServerRestartScheduleEnabled", false); + SERVER_RESTART_SCHEDULE_MESSAGE = config.getBoolean("ServerRestartScheduleMessage", false); + SERVER_RESTART_SCHEDULE_COUNTDOWN = config.getInt("ServerRestartScheduleCountdown", 600); + SERVER_RESTART_SCHEDULE = config.getString("ServerRestartSchedule", "08:00").split(","); + + SERVER_RESTART_DAYS = new ArrayList<>(); + for (String day : config.getString("ServerRestartDays", "").trim().split(",")) + { + if (StringUtil.isNumeric(day)) + { + SERVER_RESTART_DAYS.add(Integer.parseInt(day)); + } + } + + PRECAUTIONARY_RESTART_ENABLED = config.getBoolean("PrecautionaryRestartEnabled", false); + PRECAUTIONARY_RESTART_CPU = config.getBoolean("PrecautionaryRestartCpu", true); + PRECAUTIONARY_RESTART_MEMORY = config.getBoolean("PrecautionaryRestartMemory", false); + PRECAUTIONARY_RESTART_CHECKS = config.getBoolean("PrecautionaryRestartChecks", true); + PRECAUTIONARY_RESTART_PERCENTAGE = config.getInt("PrecautionaryRestartPercentage", 95); + PRECAUTIONARY_RESTART_DELAY = config.getInt("PrecautionaryRestartDelay", 60) * 1000; + + final IPConfigData ipConfigData = new IPConfigData(); + GAME_SERVER_SUBNETS = ipConfigData.getSubnets(); + GAME_SERVER_HOSTS = ipConfigData.getHosts(); + + // Load chatfilter.txt file. + loadChatFilter(); + + // Load hexid.txt file. + loadHexid(); + } + + /** + * Loads the chat filter words from the specified file.
    + * This method reads lines from the {@code CHAT_FILTER_FILE}, trims whitespace and ignores empty lines or lines starting with a '#' character.
    + * The filtered words are collected into the {@code FILTER_LIST}. If an error occurs during file reading, a warning message is logged. + */ + private static void loadChatFilter() + { + try + { + FILTER_LIST = Files.lines(Paths.get(CHAT_FILTER_FILE), StandardCharsets.UTF_8).map(String::trim).filter(line -> (!line.isEmpty() && (line.charAt(0) != '#'))).toList(); + LOGGER.info("Loaded " + FILTER_LIST.size() + " Filter Words."); + } + catch (IOException e) + { + LOGGER.log(Level.WARNING, "Error while loading chat filter words!", e); + } + } + + /** + * Loads the HexID configuration from a properties file.
    + * This method reads the {@code HEXID_FILE} and attempts to load the server ID and hexadecimal ID if available.
    + * If the file exists, it parses the properties to retrieve the {@code ServerID} and {@code HexID} values.
    + * The {@code ServerID} is stored as an integer, while the {@code HexID} is converted from a hexadecimal string to a byte array.
    + * If the file does not contain valid data or cannot be loaded, a warning is logged and the system attempts to retrieve the HexID from another source. + */ + private static void loadHexid() + { + final File hexIdFile = new File(HEXID_FILE); + if (hexIdFile.exists()) + { + final ConfigReader hexId = new ConfigReader(HEXID_FILE); + if (hexId.containsKey("ServerID") && hexId.containsKey("HexID")) + { + SERVER_ID = hexId.getInt("ServerID", 1); + try + { + HEX_ID = new BigInteger(hexId.getString("HexID", null), 16).toByteArray(); + } + catch (Exception e) + { + LOGGER.warning("Could not load HexID file (" + HEXID_FILE + "). Hopefully login will give us one."); + } + } + } + + if (HEX_ID == null) + { + LOGGER.warning("Could not load HexID file (" + HEXID_FILE + "). Hopefully login will give us one."); + } + } + + /** + * Save hexadecimal ID of the server in the config file.
    + * Check {@link #HEXID_FILE}. + * @param serverId the ID of the server whose hexId to save + * @param hexId the hexadecimal ID to store + */ + public static void saveHexid(int serverId, String hexId) + { + saveHexid(serverId, hexId, HEXID_FILE); + } + + /** + * Save hexadecimal ID of the server in the config file. + * @param serverId the ID of the server whose hexId to save + * @param hexId the hexadecimal ID to store + * @param fileName name of the config file + */ + private static void saveHexid(int serverId, String hexId, String fileName) + { + try + { + final Properties hexSetting = new Properties(); + final File file = new File(fileName); + + // Create a new empty file only if it doesn't exist. + if (!file.exists()) + { + try (OutputStream out = new FileOutputStream(file)) + { + hexSetting.setProperty("ServerID", String.valueOf(serverId)); + hexSetting.setProperty("HexID", hexId); + hexSetting.store(out, "The HexId to Auth into LoginServer"); + LOGGER.log(Level.INFO, "Gameserver: Generated new HexID file for server id " + serverId + "."); + } + } + } + catch (Exception e) + { + LOGGER.warning(StringUtil.concat("Failed to save hex id to ", fileName, " File.")); + LOGGER.warning("Config: " + e.getMessage()); + } + } + + /** + * Calculates a bitwise ID representing the types of servers specified. Each server type is associated with a unique bit position, allowing multiple types to be combined in a single integer using bitwise OR. + * @param serverTypes An array of server type names as strings. Any unrecognized types are ignored. + * @return An integer representing the combined server types, where each bit corresponds to a specific server type. The result is 0 if no recognized types are provided. + */ + public static int getServerTypeId(String[] serverTypes) + { + int serverType = 0; + for (String cType : serverTypes) + { + switch (cType.trim().toLowerCase()) + { + case "normal": + { + serverType |= 0x01; + break; + } + case "relax": + { + serverType |= 0x02; + break; + } + case "test": + { + serverType |= 0x04; + break; + } + case "nolabel": + { + serverType |= 0x08; + break; + } + case "restricted": + { + serverType |= 0x10; + break; + } + case "event": + { + serverType |= 0x20; + break; + } + case "free": + { + serverType |= 0x40; + break; + } + default: + { + break; + } + } + } + + return serverType; + } + + /** + * A configuration class for managing server IP and subnet settings. This class loads network configuration settings from an XML file or performs automatic configuration if the file is unavailable.
    + *

    + * If the configuration file exists, it parses the file to define subnets and hosts manually. If the file is missing, it attempts automatic configuration by retrieving the external IP address and identifying local network interfaces to configure internal IP addresses and subnets. + *

    + */ + private static class IPConfigData implements IXmlReader + { + private static final List _subnets = new ArrayList<>(5); + private static final List _hosts = new ArrayList<>(5); + + public IPConfigData() + { + load(); + } + + @Override + public void load() + { + final File file = new File(IPCONFIG_FILE); + if (file.exists()) + { + LOGGER.info("Network Config: ipconfig.xml exists, using manual configuration..."); + parseFile(new File(IPCONFIG_FILE)); + } + else // Auto configuration... + { + LOGGER.info("Network Config: ipconfig.xml does not exist, using automatic configuration..."); + autoIpConfig(); + } + } + + @Override + public void parseDocument(Document document, File file) + { + NamedNodeMap attrs; + for (Node n = document.getFirstChild(); n != null; n = n.getNextSibling()) + { + if ("gameserver".equalsIgnoreCase(n.getNodeName())) + { + for (Node d = n.getFirstChild(); d != null; d = d.getNextSibling()) + { + if ("define".equalsIgnoreCase(d.getNodeName())) + { + attrs = d.getAttributes(); + _subnets.add(attrs.getNamedItem("subnet").getNodeValue()); + _hosts.add(attrs.getNamedItem("address").getNodeValue()); + + if (_hosts.size() != _subnets.size()) + { + LOGGER.warning("Failed to Load " + IPCONFIG_FILE + " File - subnets does not match server addresses."); + } + } + } + + final Node att = n.getAttributes().getNamedItem("address"); + if (att == null) + { + LOGGER.warning("Failed to load " + IPCONFIG_FILE + " file - default server address is missing."); + _hosts.add("127.0.0.1"); + } + else + { + _hosts.add(att.getNodeValue()); + } + + _subnets.add("0.0.0.0/0"); + } + } + } + + protected void autoIpConfig() + { + String externalIp = "127.0.0.1"; + try + { + // Java 19 + // final URL autoIp = new URL("http://checkip.amazonaws.com"); + // Java 20 + final URL autoIp = URI.create("http://checkip.amazonaws.com").toURL(); + try (BufferedReader in = new BufferedReader(new InputStreamReader(autoIp.openStream()))) + { + externalIp = in.readLine(); + } + } + catch (IOException e) + { + LOGGER.log(Level.INFO, "Failed to connect to checkip.amazonaws.com please check your internet connection using 127.0.0.1!"); + externalIp = "127.0.0.1"; + } + + try + { + final Enumeration niList = NetworkInterface.getNetworkInterfaces(); + while (niList.hasMoreElements()) + { + final NetworkInterface ni = niList.nextElement(); + if (!ni.isUp() || ni.isVirtual()) + { + continue; + } + + if (!ni.isLoopback() && ((ni.getHardwareAddress() == null) || (ni.getHardwareAddress().length != 6))) + { + continue; + } + + for (InterfaceAddress ia : ni.getInterfaceAddresses()) + { + if (ia.getAddress() instanceof Inet6Address) + { + continue; + } + + final String hostAddress = ia.getAddress().getHostAddress(); + final int subnetPrefixLength = ia.getNetworkPrefixLength(); + final int subnetMaskInt = IntStream.rangeClosed(1, subnetPrefixLength).reduce((r, _) -> (r << 1) + 1).orElse(0) << (32 - subnetPrefixLength); + final int hostAddressInt = Arrays.stream(hostAddress.split("\\.")).mapToInt(Integer::parseInt).reduce((r, e) -> (r << 8) + e).orElse(0); + final int subnetAddressInt = hostAddressInt & subnetMaskInt; + final String subnetAddress = ((subnetAddressInt >> 24) & 0xFF) + "." + ((subnetAddressInt >> 16) & 0xFF) + "." + ((subnetAddressInt >> 8) & 0xFF) + "." + (subnetAddressInt & 0xFF); + final String subnet = subnetAddress + '/' + subnetPrefixLength; + if (!_subnets.contains(subnet) && !subnet.equals("0.0.0.0/0")) + { + _subnets.add(subnet); + _hosts.add(hostAddress); + LOGGER.info("Network Config: Adding new subnet: " + subnet + " address: " + hostAddress); + } + } + } + + // External host and subnet. + _hosts.add(externalIp); + _subnets.add("0.0.0.0/0"); + LOGGER.info("Network Config: Adding new subnet: 0.0.0.0/0 address: " + externalIp); + } + catch (SocketException e) + { + LOGGER.log(Level.INFO, "Network Config: Configuration failed please configure manually using ipconfig.xml", e); + System.exit(0); + } + } + + protected List getSubnets() + { + if (_subnets.isEmpty()) + { + return Arrays.asList("0.0.0.0/0"); + } + + return _subnets; + } + + protected List getHosts() + { + if (_hosts.isEmpty()) + { + return Arrays.asList("127.0.0.1"); + } + + return _hosts; + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/AllowedPlayerRacesConfig.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/AllowedPlayerRacesConfig.java new file mode 100644 index 0000000..ec5ae46 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/AllowedPlayerRacesConfig.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.config.custom; + +import org.l2jmobius.commons.util.ConfigReader; + +/** + * This class loads all the allowed player races related configurations. + * @author Mobius + */ +public class AllowedPlayerRacesConfig +{ + // File + private static final String ALLOWED_PLAYER_RACES_CONFIG_FILE = "./config/Custom/AllowedPlayerRaces.ini"; + + // Constants + public static boolean ALLOW_HUMAN; + public static boolean ALLOW_ELF; + public static boolean ALLOW_DARKELF; + public static boolean ALLOW_ORC; + public static boolean ALLOW_DWARF; + + public static void load() + { + final ConfigReader config = new ConfigReader(ALLOWED_PLAYER_RACES_CONFIG_FILE); + ALLOW_HUMAN = config.getBoolean("AllowHuman", true); + ALLOW_ELF = config.getBoolean("AllowElf", true); + ALLOW_DARKELF = config.getBoolean("AllowDarkElf", true); + ALLOW_ORC = config.getBoolean("AllowOrc", true); + ALLOW_DWARF = config.getBoolean("AllowDwarf", true); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/AutoPlayConfig.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/AutoPlayConfig.java new file mode 100644 index 0000000..6c5ccae --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/AutoPlayConfig.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.config.custom; + +import java.util.HashSet; +import java.util.Set; + +import org.l2jmobius.commons.util.ConfigReader; + +/** + * This class loads all the custom auto play related configurations. + * @author Mobius + */ +public class AutoPlayConfig +{ + // File + private static final String AUTO_PLAY_CONFIG_FILE = "./config/Custom/AutoPlay.ini"; + + // Constants + public static boolean ENABLE_AUTO_PLAY; + public static boolean ENABLE_AUTO_POTION; + public static boolean ENABLE_AUTO_SKILL; + public static boolean ENABLE_AUTO_ITEM; + public static boolean RESUME_AUTO_PLAY; + public static boolean ENABLE_AUTO_ASSIST; + public static int AUTO_PLAY_SHORT_RANGE; + public static int AUTO_PLAY_LONG_RANGE; + public static boolean AUTO_PLAY_PREMIUM; + public static Set DISABLED_AUTO_SKILLS = new HashSet<>(); + public static Set DISABLED_AUTO_ITEMS = new HashSet<>(); + public static Set IGNORED_AUTO_PICK_ITEMS = new HashSet<>(); + public static String AUTO_PLAY_LOGIN_MESSAGE; + + public static void load() + { + final ConfigReader config = new ConfigReader(AUTO_PLAY_CONFIG_FILE); + ENABLE_AUTO_PLAY = config.getBoolean("EnableAutoPlay", false); + ENABLE_AUTO_POTION = config.getBoolean("EnableAutoPotion", true); + ENABLE_AUTO_SKILL = config.getBoolean("EnableAutoSkill", true); + ENABLE_AUTO_ITEM = config.getBoolean("EnableAutoItem", true); + RESUME_AUTO_PLAY = config.getBoolean("ResumeAutoPlay", false); + ENABLE_AUTO_ASSIST = config.getBoolean("AssistLeader", false); + AUTO_PLAY_SHORT_RANGE = config.getInt("ShortRange", 600); + AUTO_PLAY_LONG_RANGE = config.getInt("LongRange", 1400); + AUTO_PLAY_PREMIUM = config.getBoolean("AutoPlayPremium", false); + + DISABLED_AUTO_SKILLS.clear(); + final String disabledSkills = config.getString("DisabledSkillIds", ""); + if (!disabledSkills.isEmpty()) + { + for (String s : disabledSkills.split(",")) + { + DISABLED_AUTO_SKILLS.add(Integer.parseInt(s.trim())); + } + } + + DISABLED_AUTO_ITEMS.clear(); + final String disabledItems = config.getString("DisabledItemIds", ""); + if (!disabledItems.isEmpty()) + { + for (String s : disabledItems.split(",")) + { + DISABLED_AUTO_ITEMS.add(Integer.parseInt(s.trim())); + } + } + + IGNORED_AUTO_PICK_ITEMS.clear(); + final String ignoredAutoPickItems = config.getString("IgnoredAutoPickItems", "").trim(); + if (!ignoredAutoPickItems.isEmpty()) + { + for (String itemIdString : ignoredAutoPickItems.split(",")) + { + IGNORED_AUTO_PICK_ITEMS.add(Integer.parseInt(itemIdString.trim())); + } + } + + AUTO_PLAY_LOGIN_MESSAGE = config.getString("AutoPlayLoginMessage", ""); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/AutoPotionsConfig.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/AutoPotionsConfig.java new file mode 100644 index 0000000..c824fd7 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/AutoPotionsConfig.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.config.custom; + +import java.util.HashSet; +import java.util.Set; + +import org.l2jmobius.commons.util.ConfigReader; + +/** + * This class loads all the custom auto potions related configurations. + * @author Mobius + */ +public class AutoPotionsConfig +{ + // File + private static final String AUTO_POTIONS_CONFIG_FILE = "./config/Custom/AutoPotions.ini"; + + // Constants + public static boolean AUTO_POTIONS_ENABLED; + public static boolean AUTO_POTIONS_IN_OLYMPIAD; + public static int AUTO_POTION_MIN_LEVEL; + public static boolean AUTO_CP_ENABLED; + public static boolean AUTO_HP_ENABLED; + public static boolean AUTO_MP_ENABLED; + public static int AUTO_CP_PERCENTAGE; + public static int AUTO_HP_PERCENTAGE; + public static int AUTO_MP_PERCENTAGE; + public static Set AUTO_CP_ITEM_IDS = new HashSet<>(); + public static Set AUTO_HP_ITEM_IDS = new HashSet<>(); + public static Set AUTO_MP_ITEM_IDS = new HashSet<>(); + + public static void load() + { + final ConfigReader config = new ConfigReader(AUTO_POTIONS_CONFIG_FILE); + AUTO_POTIONS_ENABLED = config.getBoolean("AutoPotionsEnabled", false); + AUTO_POTIONS_IN_OLYMPIAD = config.getBoolean("AutoPotionsInOlympiad", false); + AUTO_POTION_MIN_LEVEL = config.getInt("AutoPotionMinimumLevel", 1); + AUTO_CP_ENABLED = config.getBoolean("AutoCpEnabled", true); + AUTO_HP_ENABLED = config.getBoolean("AutoHpEnabled", true); + AUTO_MP_ENABLED = config.getBoolean("AutoMpEnabled", true); + AUTO_CP_PERCENTAGE = config.getInt("AutoCpPercentage", 70); + AUTO_HP_PERCENTAGE = config.getInt("AutoHpPercentage", 70); + AUTO_MP_PERCENTAGE = config.getInt("AutoMpPercentage", 70); + + AUTO_CP_ITEM_IDS.clear(); + for (String s : config.getString("AutoCpItemIds", "0").split(",")) + { + AUTO_CP_ITEM_IDS.add(Integer.parseInt(s)); + } + + AUTO_HP_ITEM_IDS.clear(); + for (String s : config.getString("AutoHpItemIds", "0").split(",")) + { + AUTO_HP_ITEM_IDS.add(Integer.parseInt(s)); + } + + AUTO_MP_ITEM_IDS.clear(); + for (String s : config.getString("AutoMpItemIds", "0").split(",")) + { + AUTO_MP_ITEM_IDS.add(Integer.parseInt(s)); + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/BankingConfig.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/BankingConfig.java new file mode 100644 index 0000000..7e99ed4 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/BankingConfig.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.config.custom; + +import org.l2jmobius.commons.util.ConfigReader; + +/** + * This class loads all the custom banking related configurations. + * @author Mobius + */ +public class BankingConfig +{ + // File + private static final String BANKING_CONFIG_FILE = "./config/Custom/Banking.ini"; + + // Constants + public static boolean BANKING_SYSTEM_ENABLED; + public static int BANKING_SYSTEM_GOLDBAR_COUNT; + public static int BANKING_SYSTEM_ADENA_COUNT; + public static boolean BANKING_SYSTEM_AUTO_CONVERT_ENABLED; + public static long BANKING_SYSTEM_AUTO_CONVERT_ADENA_LIMIT; + + public static void load() + { + final ConfigReader config = new ConfigReader(BANKING_CONFIG_FILE); + BANKING_SYSTEM_ENABLED = config.getBoolean("BankingEnabled", false); + BANKING_SYSTEM_GOLDBAR_COUNT = config.getInt("BankingGoldbarCount", 1); + BANKING_SYSTEM_ADENA_COUNT = config.getInt("BankingAdenaCount", 1000000000); + BANKING_SYSTEM_AUTO_CONVERT_ENABLED = config.getBoolean("BankingAutoConvert", false); + BANKING_SYSTEM_AUTO_CONVERT_ADENA_LIMIT = config.getLong("BankingAutoConvertAdenaLimit", 51000000000L); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/BossAnnouncementsConfig.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/BossAnnouncementsConfig.java new file mode 100644 index 0000000..de9a0d3 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/BossAnnouncementsConfig.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.config.custom; + +import java.util.HashSet; +import java.util.Set; + +import org.l2jmobius.commons.util.ConfigReader; + +/** + * This class loads all the custom boss announcement related configurations. + * @author Mobius + */ +public class BossAnnouncementsConfig +{ + // File + private static final String BOSS_ANNOUNCEMENTS_CONFIG_FILE = "./config/Custom/BossAnnouncements.ini"; + + // Constants + public static boolean RAIDBOSS_SPAWN_ANNOUNCEMENTS; + public static boolean RAIDBOSS_DEFEAT_ANNOUNCEMENTS; + public static boolean RAIDBOSS_INSTANCE_ANNOUNCEMENTS; + public static boolean GRANDBOSS_SPAWN_ANNOUNCEMENTS; + public static boolean GRANDBOSS_DEFEAT_ANNOUNCEMENTS; + public static boolean GRANDBOSS_INSTANCE_ANNOUNCEMENTS; + public static Set RAIDBOSSES_EXCLUDED_FROM_SPAWN_ANNOUNCEMENTS = new HashSet<>(); + public static Set RAIDBOSSES_EXCLUDED_FROM_DEFEAT_ANNOUNCEMENTS = new HashSet<>(); + + public static void load() + { + final ConfigReader config = new ConfigReader(BOSS_ANNOUNCEMENTS_CONFIG_FILE); + RAIDBOSS_SPAWN_ANNOUNCEMENTS = config.getBoolean("RaidBossSpawnAnnouncements", false); + RAIDBOSS_DEFEAT_ANNOUNCEMENTS = config.getBoolean("RaidBossDefeatAnnouncements", false); + RAIDBOSS_INSTANCE_ANNOUNCEMENTS = config.getBoolean("RaidBossInstanceAnnouncements", false); + GRANDBOSS_SPAWN_ANNOUNCEMENTS = config.getBoolean("GrandBossSpawnAnnouncements", false); + GRANDBOSS_DEFEAT_ANNOUNCEMENTS = config.getBoolean("GrandBossDefeatAnnouncements", false); + GRANDBOSS_INSTANCE_ANNOUNCEMENTS = config.getBoolean("GrandBossInstanceAnnouncements", false); + + RAIDBOSSES_EXCLUDED_FROM_SPAWN_ANNOUNCEMENTS.clear(); + for (String raidbossId : config.getString("RaidbossExcludedFromSpawnAnnouncements", "").split(",")) + { + if (!raidbossId.isEmpty()) + { + RAIDBOSSES_EXCLUDED_FROM_SPAWN_ANNOUNCEMENTS.add(Integer.parseInt(raidbossId.trim())); + } + } + + RAIDBOSSES_EXCLUDED_FROM_DEFEAT_ANNOUNCEMENTS.clear(); + for (String raidbossId : config.getString("RaidbossExcludedFromDefeatAnnouncements", "").split(",")) + { + if (!raidbossId.isEmpty()) + { + RAIDBOSSES_EXCLUDED_FROM_DEFEAT_ANNOUNCEMENTS.add(Integer.parseInt(raidbossId.trim())); + } + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/CancelReturnConfig.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/CancelReturnConfig.java new file mode 100644 index 0000000..33179f3 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/CancelReturnConfig.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.config.custom; + +import org.l2jmobius.commons.util.ConfigReader; + +/** + * This class loads all the cancel return related configurations. + * @author Naker + */ +public class CancelReturnConfig +{ + // File + private static final String CANCEL_RETURN_CONFIG_FILE = "./config/Custom/CancelReturn.ini"; + + // Constants + public static boolean CANCEL_RETURN_ON; + public static boolean CANCEL_RETURN_MOB; + public static boolean CANCEL_RETURN_PLAYER; + public static boolean CANCEL_RETURN_PLAYER_OLYS; + public static int TIME_TO_RETURN; + + public static void load() + { + final ConfigReader config = new ConfigReader(CANCEL_RETURN_CONFIG_FILE); + CANCEL_RETURN_ON = config.getBoolean("CancelReturn", false); + CANCEL_RETURN_MOB = config.getBoolean("ReturnMonster", true); + CANCEL_RETURN_PLAYER = config.getBoolean("ReturnPlayer", true); + CANCEL_RETURN_PLAYER_OLYS = config.getBoolean("ReturnPlayerOlys", false); + TIME_TO_RETURN = config.getInt("TimeToReturn", 10) * 1000; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/CaptchaConfig.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/CaptchaConfig.java new file mode 100644 index 0000000..e40d665 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/CaptchaConfig.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.config.custom; + +import org.l2jmobius.commons.util.ConfigReader; + +/** + * This class loads all the custom captcha related configurations. + * @author Mobius + */ +public class CaptchaConfig +{ + // File + private static final String CAPTCHA_CONFIG_FILE = "./config/Custom/Captcha.ini"; + + // Constants + public static boolean ENABLE_CAPTCHA; + public static int KILL_COUNTER; + public static int KILL_COUNTER_RANDOMIZATION; + public static boolean KILL_COUNTER_RESET; + public static int KILL_COUNTER_RESET_TIME; + public static int VALIDATION_TIME; + public static int CAPTCHA_ATTEMPTS; + public static int PUNISHMENT; + public static int JAIL_TIME; + public static boolean DOUBLE_JAIL_TIME; + + public static void load() + { + final ConfigReader config = new ConfigReader(CAPTCHA_CONFIG_FILE); + ENABLE_CAPTCHA = config.getBoolean("EnableCaptcha", false); + KILL_COUNTER = config.getInt("KillCounter", 100); + KILL_COUNTER_RANDOMIZATION = config.getInt("KillCounterRandomization", 50); + KILL_COUNTER_RESET = config.getBoolean("KillCounterReset", false); + KILL_COUNTER_RESET_TIME = config.getInt("KillCounterResetTime", 20) * 60000; + VALIDATION_TIME = config.getInt("ValidationTime", 60); + CAPTCHA_ATTEMPTS = config.getInt("CaptchaAttempts", 2); + PUNISHMENT = config.getInt("Punishment", 0); + JAIL_TIME = config.getInt("JailTime", 2); + DOUBLE_JAIL_TIME = config.getBoolean("DoubleJailTime", false); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/ChampionMonstersConfig.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/ChampionMonstersConfig.java new file mode 100644 index 0000000..1db6131 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/ChampionMonstersConfig.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.config.custom; + +import java.util.ArrayList; +import java.util.List; + +import org.l2jmobius.commons.util.ConfigReader; +import org.l2jmobius.gameserver.entity.item.holders.ItemHolder; + +/** + * This class loads all the custom champion monster related configurations. + * @author Mobius + */ +public class ChampionMonstersConfig +{ + // File + private static final String CHAMPION_MONSTERS_CONFIG_FILE = "./config/Custom/ChampionMonsters.ini"; + + // Constants + public static boolean CHAMPION_ENABLE; + public static boolean CHAMPION_PASSIVE; + public static int CHAMPION_FREQUENCY; + public static String CHAMP_TITLE; + public static boolean SHOW_CHAMPION_AURA; + public static int CHAMP_MIN_LEVEL; + public static int CHAMP_MAX_LEVEL; + public static int CHAMPION_HP; + public static float CHAMPION_HP_REGEN; + public static float CHAMPION_REWARDS_EXP_SP; + public static float CHAMPION_REWARDS_CHANCE; + public static float CHAMPION_REWARDS_AMOUNT; + public static float CHAMPION_ADENAS_REWARDS_CHANCE; + public static float CHAMPION_ADENAS_REWARDS_AMOUNT; + public static float CHAMPION_ATK; + public static float CHAMPION_SPD_ATK; + public static int CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE; + public static int CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE; + public static List CHAMPION_REWARD_ITEMS = new ArrayList<>(); + public static boolean CHAMPION_ENABLE_VITALITY; + public static boolean CHAMPION_ENABLE_IN_INSTANCES; + + public static void load() + { + final ConfigReader config = new ConfigReader(CHAMPION_MONSTERS_CONFIG_FILE); + CHAMPION_ENABLE = config.getBoolean("ChampionEnable", false); + CHAMPION_PASSIVE = config.getBoolean("ChampionPassive", false); + CHAMPION_FREQUENCY = config.getInt("ChampionFrequency", 0); + CHAMP_TITLE = config.getString("ChampionTitle", "Champion"); + SHOW_CHAMPION_AURA = config.getBoolean("ChampionAura", true); + CHAMP_MIN_LEVEL = config.getInt("ChampionMinLevel", 20); + CHAMP_MAX_LEVEL = config.getInt("ChampionMaxLevel", 60); + CHAMPION_HP = config.getInt("ChampionHp", 7); + CHAMPION_HP_REGEN = config.getFloat("ChampionHpRegen", 1); + CHAMPION_REWARDS_EXP_SP = config.getFloat("ChampionRewardsExpSp", 8); + CHAMPION_REWARDS_CHANCE = config.getFloat("ChampionRewardsChance", 8); + CHAMPION_REWARDS_AMOUNT = config.getFloat("ChampionRewardsAmount", 1); + CHAMPION_ADENAS_REWARDS_CHANCE = config.getFloat("ChampionAdenasRewardsChance", 1); + CHAMPION_ADENAS_REWARDS_AMOUNT = config.getFloat("ChampionAdenasRewardsAmount", 1); + CHAMPION_ATK = config.getFloat("ChampionAtk", 1); + CHAMPION_SPD_ATK = config.getFloat("ChampionSpdAtk", 1); + CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE = config.getInt("ChampionRewardLowerLvlItemChance", 0); + CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE = config.getInt("ChampionRewardHigherLvlItemChance", 0); + + CHAMPION_REWARD_ITEMS.clear(); + for (String s : config.getString("ChampionRewardItems", "4356,10").split(";")) + { + if (s.isEmpty()) + { + continue; + } + CHAMPION_REWARD_ITEMS.add(new ItemHolder(Integer.parseInt(s.split(",")[0]), Integer.parseInt(s.split(",")[1]))); + } + + CHAMPION_ENABLE_VITALITY = config.getBoolean("ChampionEnableVitality", false); + CHAMPION_ENABLE_IN_INSTANCES = config.getBoolean("ChampionEnableInInstances", false); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/ChatModerationConfig.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/ChatModerationConfig.java new file mode 100644 index 0000000..0cd33cc --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/ChatModerationConfig.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.config.custom; + +import org.l2jmobius.commons.util.ConfigReader; + +/** + * This class loads all the custom chat moderation related configurations. + * @author Mobius + */ +public class ChatModerationConfig +{ + // File + private static final String CHAT_MODERATION_CONFIG_FILE = "./config/Custom/ChatModeration.ini"; + + // Constants + public static boolean CHAT_ADMIN; + + public static void load() + { + final ConfigReader config = new ConfigReader(CHAT_MODERATION_CONFIG_FILE); + CHAT_ADMIN = config.getBoolean("ChatAdmin", true); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/ClassBalanceConfig.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/ClassBalanceConfig.java new file mode 100644 index 0000000..9a5f8d5 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/ClassBalanceConfig.java @@ -0,0 +1,636 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.config.custom; + +import java.util.Arrays; + +import org.l2jmobius.commons.util.ConfigReader; +import org.l2jmobius.commons.util.StringUtil; +import org.l2jmobius.gameserver.entity.actor.enums.player.PlayerClass; + +/** + * This class loads all the custom class balance related configurations. + * @author Mobius + */ +public class ClassBalanceConfig +{ + // File + private static final String CLASS_BALANCE_CONFIG_FILE = "./config/Custom/ClassBalance.ini"; + + // Constants + public static float[] PVE_MAGICAL_SKILL_DAMAGE_MULTIPLIERS = new float[119]; + public static float[] PVP_MAGICAL_SKILL_DAMAGE_MULTIPLIERS = new float[119]; + public static float[] PVE_MAGICAL_SKILL_DEFENCE_MULTIPLIERS = new float[119]; + public static float[] PVP_MAGICAL_SKILL_DEFENCE_MULTIPLIERS = new float[119]; + public static float[] PVE_MAGICAL_SKILL_CRITICAL_CHANCE_MULTIPLIERS = new float[119]; + public static float[] PVP_MAGICAL_SKILL_CRITICAL_CHANCE_MULTIPLIERS = new float[119]; + public static float[] PVE_MAGICAL_SKILL_CRITICAL_DAMAGE_MULTIPLIERS = new float[119]; + public static float[] PVP_MAGICAL_SKILL_CRITICAL_DAMAGE_MULTIPLIERS = new float[119]; + public static float[] PVE_PHYSICAL_SKILL_DAMAGE_MULTIPLIERS = new float[119]; + public static float[] PVP_PHYSICAL_SKILL_DAMAGE_MULTIPLIERS = new float[119]; + public static float[] PVE_PHYSICAL_SKILL_DEFENCE_MULTIPLIERS = new float[119]; + public static float[] PVP_PHYSICAL_SKILL_DEFENCE_MULTIPLIERS = new float[119]; + public static float[] PVE_PHYSICAL_SKILL_CRITICAL_CHANCE_MULTIPLIERS = new float[119]; + public static float[] PVP_PHYSICAL_SKILL_CRITICAL_CHANCE_MULTIPLIERS = new float[119]; + public static float[] PVE_PHYSICAL_SKILL_CRITICAL_DAMAGE_MULTIPLIERS = new float[119]; + public static float[] PVP_PHYSICAL_SKILL_CRITICAL_DAMAGE_MULTIPLIERS = new float[119]; + public static float[] PVE_PHYSICAL_ATTACK_DAMAGE_MULTIPLIERS = new float[119]; + public static float[] PVP_PHYSICAL_ATTACK_DAMAGE_MULTIPLIERS = new float[119]; + public static float[] PVE_PHYSICAL_ATTACK_DEFENCE_MULTIPLIERS = new float[119]; + public static float[] PVP_PHYSICAL_ATTACK_DEFENCE_MULTIPLIERS = new float[119]; + public static float[] PVE_PHYSICAL_ATTACK_CRITICAL_CHANCE_MULTIPLIERS = new float[119]; + public static float[] PVP_PHYSICAL_ATTACK_CRITICAL_CHANCE_MULTIPLIERS = new float[119]; + public static float[] PVE_PHYSICAL_ATTACK_CRITICAL_DAMAGE_MULTIPLIERS = new float[119]; + public static float[] PVP_PHYSICAL_ATTACK_CRITICAL_DAMAGE_MULTIPLIERS = new float[119]; + public static float[] PVE_BLOW_SKILL_DAMAGE_MULTIPLIERS = new float[119]; + public static float[] PVP_BLOW_SKILL_DAMAGE_MULTIPLIERS = new float[119]; + public static float[] PVE_BLOW_SKILL_DEFENCE_MULTIPLIERS = new float[119]; + public static float[] PVP_BLOW_SKILL_DEFENCE_MULTIPLIERS = new float[119]; + public static float[] PVE_ENERGY_SKILL_DAMAGE_MULTIPLIERS = new float[119]; + public static float[] PVP_ENERGY_SKILL_DAMAGE_MULTIPLIERS = new float[119]; + public static float[] PVE_ENERGY_SKILL_DEFENCE_MULTIPLIERS = new float[119]; + public static float[] PVP_ENERGY_SKILL_DEFENCE_MULTIPLIERS = new float[119]; + public static float[] PLAYER_HEALING_SKILL_MULTIPLIERS = new float[119]; + public static float[] SKILL_MASTERY_CHANCE_MULTIPLIERS = new float[119]; + public static float[] SKILL_REUSE_MULTIPLIERS = new float[119]; + public static float[] EXP_AMOUNT_MULTIPLIERS = new float[119]; + public static float[] SP_AMOUNT_MULTIPLIERS = new float[119]; + + public static void load() + { + final ConfigReader config = new ConfigReader(CLASS_BALANCE_CONFIG_FILE); + + Arrays.fill(PVE_MAGICAL_SKILL_DAMAGE_MULTIPLIERS, 1f); + final String[] pveMagicalSkillDamageMultipliers = config.getString("PveMagicalSkillDamageMultipliers", "").trim().split(";"); + if (pveMagicalSkillDamageMultipliers.length > 0) + { + for (String info : pveMagicalSkillDamageMultipliers) + { + final String[] classInfo = info.trim().split("[*]"); + if (classInfo.length == 2) + { + final String id = classInfo[0].trim(); + PVE_MAGICAL_SKILL_DAMAGE_MULTIPLIERS[StringUtil.isNumeric(id) ? Integer.parseInt(id) : Enum.valueOf(PlayerClass.class, id).getId()] = Float.parseFloat(classInfo[1].trim()); + } + } + } + + Arrays.fill(PVP_MAGICAL_SKILL_DAMAGE_MULTIPLIERS, 1f); + final String[] pvpMagicalSkillDamageMultipliers = config.getString("PvpMagicalSkillDamageMultipliers", "").trim().split(";"); + if (pvpMagicalSkillDamageMultipliers.length > 0) + { + for (String info : pvpMagicalSkillDamageMultipliers) + { + final String[] classInfo = info.trim().split("[*]"); + if (classInfo.length == 2) + { + final String id = classInfo[0].trim(); + PVP_MAGICAL_SKILL_DAMAGE_MULTIPLIERS[StringUtil.isNumeric(id) ? Integer.parseInt(id) : Enum.valueOf(PlayerClass.class, id).getId()] = Float.parseFloat(classInfo[1].trim()); + } + } + } + + Arrays.fill(PVE_MAGICAL_SKILL_DEFENCE_MULTIPLIERS, 1f); + final String[] pveMagicalSkillDefenceMultipliers = config.getString("PveMagicalSkillDefenceMultipliers", "").trim().split(";"); + if (pveMagicalSkillDefenceMultipliers.length > 0) + { + for (String info : pveMagicalSkillDefenceMultipliers) + { + final String[] classInfo = info.trim().split("[*]"); + if (classInfo.length == 2) + { + final String id = classInfo[0].trim(); + PVE_MAGICAL_SKILL_DEFENCE_MULTIPLIERS[StringUtil.isNumeric(id) ? Integer.parseInt(id) : Enum.valueOf(PlayerClass.class, id).getId()] = Float.parseFloat(classInfo[1].trim()); + } + } + } + + Arrays.fill(PVP_MAGICAL_SKILL_DEFENCE_MULTIPLIERS, 1f); + final String[] pvpMagicalSkillDefenceMultipliers = config.getString("PvpMagicalSkillDefenceMultipliers", "").trim().split(";"); + if (pvpMagicalSkillDefenceMultipliers.length > 0) + { + for (String info : pvpMagicalSkillDefenceMultipliers) + { + final String[] classInfo = info.trim().split("[*]"); + if (classInfo.length == 2) + { + final String id = classInfo[0].trim(); + PVP_MAGICAL_SKILL_DEFENCE_MULTIPLIERS[StringUtil.isNumeric(id) ? Integer.parseInt(id) : Enum.valueOf(PlayerClass.class, id).getId()] = Float.parseFloat(classInfo[1].trim()); + } + } + } + + Arrays.fill(PVE_MAGICAL_SKILL_CRITICAL_CHANCE_MULTIPLIERS, 1f); + final String[] pveMagicalSkillCriticalChanceMultipliers = config.getString("PveMagicalSkillCriticalChanceMultipliers", "").trim().split(";"); + if (pveMagicalSkillCriticalChanceMultipliers.length > 0) + { + for (String info : pveMagicalSkillCriticalChanceMultipliers) + { + final String[] classInfo = info.trim().split("[*]"); + if (classInfo.length == 2) + { + final String id = classInfo[0].trim(); + PVE_MAGICAL_SKILL_CRITICAL_CHANCE_MULTIPLIERS[StringUtil.isNumeric(id) ? Integer.parseInt(id) : Enum.valueOf(PlayerClass.class, id).getId()] = Float.parseFloat(classInfo[1].trim()); + } + } + } + + Arrays.fill(PVP_MAGICAL_SKILL_CRITICAL_CHANCE_MULTIPLIERS, 1f); + final String[] pvpMagicalSkillCriticalChanceMultipliers = config.getString("PvpMagicalSkillCriticalChanceMultipliers", "").trim().split(";"); + if (pvpMagicalSkillCriticalChanceMultipliers.length > 0) + { + for (String info : pvpMagicalSkillCriticalChanceMultipliers) + { + final String[] classInfo = info.trim().split("[*]"); + if (classInfo.length == 2) + { + final String id = classInfo[0].trim(); + PVP_MAGICAL_SKILL_CRITICAL_CHANCE_MULTIPLIERS[StringUtil.isNumeric(id) ? Integer.parseInt(id) : Enum.valueOf(PlayerClass.class, id).getId()] = Float.parseFloat(classInfo[1].trim()); + } + } + } + + Arrays.fill(PVE_MAGICAL_SKILL_CRITICAL_DAMAGE_MULTIPLIERS, 1f); + final String[] pveMagicalSkillCriticalDamageMultipliers = config.getString("PveMagicalSkillCriticalDamageMultipliers", "").trim().split(";"); + if (pveMagicalSkillCriticalDamageMultipliers.length > 0) + { + for (String info : pveMagicalSkillCriticalDamageMultipliers) + { + final String[] classInfo = info.trim().split("[*]"); + if (classInfo.length == 2) + { + final String id = classInfo[0].trim(); + PVE_MAGICAL_SKILL_CRITICAL_DAMAGE_MULTIPLIERS[StringUtil.isNumeric(id) ? Integer.parseInt(id) : Enum.valueOf(PlayerClass.class, id).getId()] = Float.parseFloat(classInfo[1].trim()); + } + } + } + + Arrays.fill(PVP_MAGICAL_SKILL_CRITICAL_DAMAGE_MULTIPLIERS, 1f); + final String[] pvpMagicalSkillCriticalDamageMultipliers = config.getString("PvpMagicalSkillCriticalDamageMultipliers", "").trim().split(";"); + if (pvpMagicalSkillCriticalDamageMultipliers.length > 0) + { + for (String info : pvpMagicalSkillCriticalDamageMultipliers) + { + final String[] classInfo = info.trim().split("[*]"); + if (classInfo.length == 2) + { + final String id = classInfo[0].trim(); + PVP_MAGICAL_SKILL_CRITICAL_DAMAGE_MULTIPLIERS[StringUtil.isNumeric(id) ? Integer.parseInt(id) : Enum.valueOf(PlayerClass.class, id).getId()] = Float.parseFloat(classInfo[1].trim()); + } + } + } + + Arrays.fill(PVE_PHYSICAL_SKILL_DAMAGE_MULTIPLIERS, 1f); + final String[] pvePhysicalSkillDamageMultipliers = config.getString("PvePhysicalSkillDamageMultipliers", "").trim().split(";"); + if (pvePhysicalSkillDamageMultipliers.length > 0) + { + for (String info : pvePhysicalSkillDamageMultipliers) + { + final String[] classInfo = info.trim().split("[*]"); + if (classInfo.length == 2) + { + final String id = classInfo[0].trim(); + PVE_PHYSICAL_SKILL_DAMAGE_MULTIPLIERS[StringUtil.isNumeric(id) ? Integer.parseInt(id) : Enum.valueOf(PlayerClass.class, id).getId()] = Float.parseFloat(classInfo[1].trim()); + } + } + } + + Arrays.fill(PVP_PHYSICAL_SKILL_DAMAGE_MULTIPLIERS, 1f); + final String[] pvpPhysicalSkillDamageMultipliers = config.getString("PvpPhysicalSkillDamageMultipliers", "").trim().split(";"); + if (pvpPhysicalSkillDamageMultipliers.length > 0) + { + for (String info : pvpPhysicalSkillDamageMultipliers) + { + final String[] classInfo = info.trim().split("[*]"); + if (classInfo.length == 2) + { + final String id = classInfo[0].trim(); + PVP_PHYSICAL_SKILL_DAMAGE_MULTIPLIERS[StringUtil.isNumeric(id) ? Integer.parseInt(id) : Enum.valueOf(PlayerClass.class, id).getId()] = Float.parseFloat(classInfo[1].trim()); + } + } + } + + Arrays.fill(PVE_PHYSICAL_SKILL_DEFENCE_MULTIPLIERS, 1f); + final String[] pvePhysicalSkillDefenceMultipliers = config.getString("PvePhysicalSkillDefenceMultipliers", "").trim().split(";"); + if (pvePhysicalSkillDefenceMultipliers.length > 0) + { + for (String info : pvePhysicalSkillDefenceMultipliers) + { + final String[] classInfo = info.trim().split("[*]"); + if (classInfo.length == 2) + { + final String id = classInfo[0].trim(); + PVE_PHYSICAL_SKILL_DEFENCE_MULTIPLIERS[StringUtil.isNumeric(id) ? Integer.parseInt(id) : Enum.valueOf(PlayerClass.class, id).getId()] = Float.parseFloat(classInfo[1].trim()); + } + } + } + + Arrays.fill(PVP_PHYSICAL_SKILL_DEFENCE_MULTIPLIERS, 1f); + final String[] pvpPhysicalSkillDefenceMultipliers = config.getString("PvpPhysicalSkillDefenceMultipliers", "").trim().split(";"); + if (pvpPhysicalSkillDefenceMultipliers.length > 0) + { + for (String info : pvpPhysicalSkillDefenceMultipliers) + { + final String[] classInfo = info.trim().split("[*]"); + if (classInfo.length == 2) + { + final String id = classInfo[0].trim(); + PVP_PHYSICAL_SKILL_DEFENCE_MULTIPLIERS[StringUtil.isNumeric(id) ? Integer.parseInt(id) : Enum.valueOf(PlayerClass.class, id).getId()] = Float.parseFloat(classInfo[1].trim()); + } + } + } + + Arrays.fill(PVE_PHYSICAL_SKILL_CRITICAL_CHANCE_MULTIPLIERS, 1f); + final String[] pvePhysicalSkillCriticalChanceMultipliers = config.getString("PvePhysicalSkillCriticalChanceMultipliers", "").trim().split(";"); + if (pvePhysicalSkillCriticalChanceMultipliers.length > 0) + { + for (String info : pvePhysicalSkillCriticalChanceMultipliers) + { + final String[] classInfo = info.trim().split("[*]"); + if (classInfo.length == 2) + { + final String id = classInfo[0].trim(); + PVE_PHYSICAL_SKILL_CRITICAL_CHANCE_MULTIPLIERS[StringUtil.isNumeric(id) ? Integer.parseInt(id) : Enum.valueOf(PlayerClass.class, id).getId()] = Float.parseFloat(classInfo[1].trim()); + } + } + } + + Arrays.fill(PVP_PHYSICAL_SKILL_CRITICAL_CHANCE_MULTIPLIERS, 1f); + final String[] pvpPhysicalSkillCriticalChanceMultipliers = config.getString("PvpPhysicalSkillCriticalChanceMultipliers", "").trim().split(";"); + if (pvpPhysicalSkillCriticalChanceMultipliers.length > 0) + { + for (String info : pvpPhysicalSkillCriticalChanceMultipliers) + { + final String[] classInfo = info.trim().split("[*]"); + if (classInfo.length == 2) + { + final String id = classInfo[0].trim(); + PVP_PHYSICAL_SKILL_CRITICAL_CHANCE_MULTIPLIERS[StringUtil.isNumeric(id) ? Integer.parseInt(id) : Enum.valueOf(PlayerClass.class, id).getId()] = Float.parseFloat(classInfo[1].trim()); + } + } + } + + Arrays.fill(PVE_PHYSICAL_SKILL_CRITICAL_DAMAGE_MULTIPLIERS, 1f); + final String[] pvePhysicalSkillCriticalDamageMultipliers = config.getString("PvePhysicalSkillCriticalDamageMultipliers", "").trim().split(";"); + if (pvePhysicalSkillCriticalDamageMultipliers.length > 0) + { + for (String info : pvePhysicalSkillCriticalDamageMultipliers) + { + final String[] classInfo = info.trim().split("[*]"); + if (classInfo.length == 2) + { + final String id = classInfo[0].trim(); + PVE_PHYSICAL_SKILL_CRITICAL_DAMAGE_MULTIPLIERS[StringUtil.isNumeric(id) ? Integer.parseInt(id) : Enum.valueOf(PlayerClass.class, id).getId()] = Float.parseFloat(classInfo[1].trim()); + } + } + } + + Arrays.fill(PVP_PHYSICAL_SKILL_CRITICAL_DAMAGE_MULTIPLIERS, 1f); + final String[] pvpPhysicalSkillCriticalDamageMultipliers = config.getString("PvpPhysicalSkillCriticalDamageMultipliers", "").trim().split(";"); + if (pvpPhysicalSkillCriticalDamageMultipliers.length > 0) + { + for (String info : pvpPhysicalSkillCriticalDamageMultipliers) + { + final String[] classInfo = info.trim().split("[*]"); + if (classInfo.length == 2) + { + final String id = classInfo[0].trim(); + PVP_PHYSICAL_SKILL_CRITICAL_DAMAGE_MULTIPLIERS[StringUtil.isNumeric(id) ? Integer.parseInt(id) : Enum.valueOf(PlayerClass.class, id).getId()] = Float.parseFloat(classInfo[1].trim()); + } + } + } + + Arrays.fill(PVE_PHYSICAL_ATTACK_DAMAGE_MULTIPLIERS, 1f); + final String[] pvePhysicalAttackDamageMultipliers = config.getString("PvePhysicalAttackDamageMultipliers", "").trim().split(";"); + if (pvePhysicalAttackDamageMultipliers.length > 0) + { + for (String info : pvePhysicalAttackDamageMultipliers) + { + final String[] classInfo = info.trim().split("[*]"); + if (classInfo.length == 2) + { + final String id = classInfo[0].trim(); + PVE_PHYSICAL_ATTACK_DAMAGE_MULTIPLIERS[StringUtil.isNumeric(id) ? Integer.parseInt(id) : Enum.valueOf(PlayerClass.class, id).getId()] = Float.parseFloat(classInfo[1].trim()); + } + } + } + + Arrays.fill(PVP_PHYSICAL_ATTACK_DAMAGE_MULTIPLIERS, 1f); + final String[] pvpPhysicalAttackDamageMultipliers = config.getString("PvpPhysicalAttackDamageMultipliers", "").trim().split(";"); + if (pvpPhysicalAttackDamageMultipliers.length > 0) + { + for (String info : pvpPhysicalAttackDamageMultipliers) + { + final String[] classInfo = info.trim().split("[*]"); + if (classInfo.length == 2) + { + final String id = classInfo[0].trim(); + PVP_PHYSICAL_ATTACK_DAMAGE_MULTIPLIERS[StringUtil.isNumeric(id) ? Integer.parseInt(id) : Enum.valueOf(PlayerClass.class, id).getId()] = Float.parseFloat(classInfo[1].trim()); + } + } + } + + Arrays.fill(PVE_PHYSICAL_ATTACK_DEFENCE_MULTIPLIERS, 1f); + final String[] pvePhysicalAttackDefenceMultipliers = config.getString("PvePhysicalAttackDefenceMultipliers", "").trim().split(";"); + if (pvePhysicalAttackDefenceMultipliers.length > 0) + { + for (String info : pvePhysicalAttackDefenceMultipliers) + { + final String[] classInfo = info.trim().split("[*]"); + if (classInfo.length == 2) + { + final String id = classInfo[0].trim(); + PVE_PHYSICAL_ATTACK_DEFENCE_MULTIPLIERS[StringUtil.isNumeric(id) ? Integer.parseInt(id) : Enum.valueOf(PlayerClass.class, id).getId()] = Float.parseFloat(classInfo[1].trim()); + } + } + } + + Arrays.fill(PVP_PHYSICAL_ATTACK_DEFENCE_MULTIPLIERS, 1f); + final String[] pvpPhysicalAttackDefenceMultipliers = config.getString("PvpPhysicalAttackDefenceMultipliers", "").trim().split(";"); + if (pvpPhysicalAttackDefenceMultipliers.length > 0) + { + for (String info : pvpPhysicalAttackDefenceMultipliers) + { + final String[] classInfo = info.trim().split("[*]"); + if (classInfo.length == 2) + { + final String id = classInfo[0].trim(); + PVP_PHYSICAL_ATTACK_DEFENCE_MULTIPLIERS[StringUtil.isNumeric(id) ? Integer.parseInt(id) : Enum.valueOf(PlayerClass.class, id).getId()] = Float.parseFloat(classInfo[1].trim()); + } + } + } + + Arrays.fill(PVE_PHYSICAL_ATTACK_CRITICAL_CHANCE_MULTIPLIERS, 1f); + final String[] pvePhysicalAttackCriticalChanceMultipliers = config.getString("PvePhysicalAttackCriticalChanceMultipliers", "").trim().split(";"); + if (pvePhysicalAttackCriticalChanceMultipliers.length > 0) + { + for (String info : pvePhysicalAttackCriticalChanceMultipliers) + { + final String[] classInfo = info.trim().split("[*]"); + if (classInfo.length == 2) + { + final String id = classInfo[0].trim(); + PVE_PHYSICAL_ATTACK_CRITICAL_CHANCE_MULTIPLIERS[StringUtil.isNumeric(id) ? Integer.parseInt(id) : Enum.valueOf(PlayerClass.class, id).getId()] = Float.parseFloat(classInfo[1].trim()); + } + } + } + + Arrays.fill(PVP_PHYSICAL_ATTACK_CRITICAL_CHANCE_MULTIPLIERS, 1f); + final String[] pvpPhysicalAttackCriticalChanceMultipliers = config.getString("PvpPhysicalAttackCriticalChanceMultipliers", "").trim().split(";"); + if (pvpPhysicalAttackCriticalChanceMultipliers.length > 0) + { + for (String info : pvpPhysicalAttackCriticalChanceMultipliers) + { + final String[] classInfo = info.trim().split("[*]"); + if (classInfo.length == 2) + { + final String id = classInfo[0].trim(); + PVP_PHYSICAL_ATTACK_CRITICAL_CHANCE_MULTIPLIERS[StringUtil.isNumeric(id) ? Integer.parseInt(id) : Enum.valueOf(PlayerClass.class, id).getId()] = Float.parseFloat(classInfo[1].trim()); + } + } + } + + Arrays.fill(PVE_PHYSICAL_ATTACK_CRITICAL_DAMAGE_MULTIPLIERS, 1f); + final String[] pvePhysicalAttackCriticalDamageMultipliers = config.getString("PvePhysicalAttackCriticalDamageMultipliers", "").trim().split(";"); + if (pvePhysicalAttackCriticalDamageMultipliers.length > 0) + { + for (String info : pvePhysicalAttackCriticalDamageMultipliers) + { + final String[] classInfo = info.trim().split("[*]"); + if (classInfo.length == 2) + { + final String id = classInfo[0].trim(); + PVE_PHYSICAL_ATTACK_CRITICAL_DAMAGE_MULTIPLIERS[StringUtil.isNumeric(id) ? Integer.parseInt(id) : Enum.valueOf(PlayerClass.class, id).getId()] = Float.parseFloat(classInfo[1].trim()); + } + } + } + + Arrays.fill(PVP_PHYSICAL_ATTACK_CRITICAL_DAMAGE_MULTIPLIERS, 1f); + final String[] pvpPhysicalAttackCriticalDamageMultipliers = config.getString("PvpPhysicalAttackCriticalDamageMultipliers", "").trim().split(";"); + if (pvpPhysicalAttackCriticalDamageMultipliers.length > 0) + { + for (String info : pvpPhysicalAttackCriticalDamageMultipliers) + { + final String[] classInfo = info.trim().split("[*]"); + if (classInfo.length == 2) + { + final String id = classInfo[0].trim(); + PVP_PHYSICAL_ATTACK_CRITICAL_DAMAGE_MULTIPLIERS[StringUtil.isNumeric(id) ? Integer.parseInt(id) : Enum.valueOf(PlayerClass.class, id).getId()] = Float.parseFloat(classInfo[1].trim()); + } + } + } + + Arrays.fill(PVE_BLOW_SKILL_DAMAGE_MULTIPLIERS, 1f); + final String[] pveBlowSkillDamageMultipliers = config.getString("PveBlowSkillDamageMultipliers", "").trim().split(";"); + if (pveBlowSkillDamageMultipliers.length > 0) + { + for (String info : pveBlowSkillDamageMultipliers) + { + final String[] classInfo = info.trim().split("[*]"); + if (classInfo.length == 2) + { + final String id = classInfo[0].trim(); + PVE_BLOW_SKILL_DAMAGE_MULTIPLIERS[StringUtil.isNumeric(id) ? Integer.parseInt(id) : Enum.valueOf(PlayerClass.class, id).getId()] = Float.parseFloat(classInfo[1].trim()); + } + } + } + + Arrays.fill(PVP_BLOW_SKILL_DAMAGE_MULTIPLIERS, 1f); + final String[] pvpBlowSkillDamageMultipliers = config.getString("PvpBlowSkillDamageMultipliers", "").trim().split(";"); + if (pvpBlowSkillDamageMultipliers.length > 0) + { + for (String info : pvpBlowSkillDamageMultipliers) + { + final String[] classInfo = info.trim().split("[*]"); + if (classInfo.length == 2) + { + final String id = classInfo[0].trim(); + PVP_BLOW_SKILL_DAMAGE_MULTIPLIERS[StringUtil.isNumeric(id) ? Integer.parseInt(id) : Enum.valueOf(PlayerClass.class, id).getId()] = Float.parseFloat(classInfo[1].trim()); + } + } + } + + Arrays.fill(PVE_BLOW_SKILL_DEFENCE_MULTIPLIERS, 1f); + final String[] pveBlowSkillDefenceMultipliers = config.getString("PveBlowSkillDefenceMultipliers", "").trim().split(";"); + if (pveBlowSkillDefenceMultipliers.length > 0) + { + for (String info : pveBlowSkillDefenceMultipliers) + { + final String[] classInfo = info.trim().split("[*]"); + if (classInfo.length == 2) + { + final String id = classInfo[0].trim(); + PVE_BLOW_SKILL_DEFENCE_MULTIPLIERS[StringUtil.isNumeric(id) ? Integer.parseInt(id) : Enum.valueOf(PlayerClass.class, id).getId()] = Float.parseFloat(classInfo[1].trim()); + } + } + } + + Arrays.fill(PVP_BLOW_SKILL_DEFENCE_MULTIPLIERS, 1f); + final String[] pvpBlowSkillDefenceMultipliers = config.getString("PvpBlowSkillDefenceMultipliers", "").trim().split(";"); + if (pvpBlowSkillDefenceMultipliers.length > 0) + { + for (String info : pvpBlowSkillDefenceMultipliers) + { + final String[] classInfo = info.trim().split("[*]"); + if (classInfo.length == 2) + { + final String id = classInfo[0].trim(); + PVP_BLOW_SKILL_DEFENCE_MULTIPLIERS[StringUtil.isNumeric(id) ? Integer.parseInt(id) : Enum.valueOf(PlayerClass.class, id).getId()] = Float.parseFloat(classInfo[1].trim()); + } + } + } + + Arrays.fill(PVE_ENERGY_SKILL_DAMAGE_MULTIPLIERS, 1f); + final String[] pveEnergySkillDamageMultipliers = config.getString("PveEnergySkillDamageMultipliers", "").trim().split(";"); + if (pveEnergySkillDamageMultipliers.length > 0) + { + for (String info : pveEnergySkillDamageMultipliers) + { + final String[] classInfo = info.trim().split("[*]"); + if (classInfo.length == 2) + { + final String id = classInfo[0].trim(); + PVE_ENERGY_SKILL_DAMAGE_MULTIPLIERS[StringUtil.isNumeric(id) ? Integer.parseInt(id) : Enum.valueOf(PlayerClass.class, id).getId()] = Float.parseFloat(classInfo[1].trim()); + } + } + } + + Arrays.fill(PVP_ENERGY_SKILL_DAMAGE_MULTIPLIERS, 1f); + final String[] pvpEnergySkillDamageMultipliers = config.getString("PvpEnergySkillDamageMultipliers", "").trim().split(";"); + if (pvpEnergySkillDamageMultipliers.length > 0) + { + for (String info : pvpEnergySkillDamageMultipliers) + { + final String[] classInfo = info.trim().split("[*]"); + if (classInfo.length == 2) + { + final String id = classInfo[0].trim(); + PVP_ENERGY_SKILL_DAMAGE_MULTIPLIERS[StringUtil.isNumeric(id) ? Integer.parseInt(id) : Enum.valueOf(PlayerClass.class, id).getId()] = Float.parseFloat(classInfo[1].trim()); + } + } + } + + Arrays.fill(PVE_ENERGY_SKILL_DEFENCE_MULTIPLIERS, 1f); + final String[] pveEnergySkillDefenceMultipliers = config.getString("PveEnergySkillDefenceMultipliers", "").trim().split(";"); + if (pveEnergySkillDefenceMultipliers.length > 0) + { + for (String info : pveEnergySkillDefenceMultipliers) + { + final String[] classInfo = info.trim().split("[*]"); + if (classInfo.length == 2) + { + final String id = classInfo[0].trim(); + PVE_ENERGY_SKILL_DEFENCE_MULTIPLIERS[StringUtil.isNumeric(id) ? Integer.parseInt(id) : Enum.valueOf(PlayerClass.class, id).getId()] = Float.parseFloat(classInfo[1].trim()); + } + } + } + + Arrays.fill(PVP_ENERGY_SKILL_DEFENCE_MULTIPLIERS, 1f); + final String[] pvpEnergySkillDefenceMultipliers = config.getString("PvpEnergySkillDefenceMultipliers", "").trim().split(";"); + if (pvpEnergySkillDefenceMultipliers.length > 0) + { + for (String info : pvpEnergySkillDefenceMultipliers) + { + final String[] classInfo = info.trim().split("[*]"); + if (classInfo.length == 2) + { + final String id = classInfo[0].trim(); + PVP_ENERGY_SKILL_DEFENCE_MULTIPLIERS[StringUtil.isNumeric(id) ? Integer.parseInt(id) : Enum.valueOf(PlayerClass.class, id).getId()] = Float.parseFloat(classInfo[1].trim()); + } + } + } + + Arrays.fill(PLAYER_HEALING_SKILL_MULTIPLIERS, 1f); + final String[] playerHealingSkillMultipliers = config.getString("PlayerHealingSkillMultipliers", "").trim().split(";"); + if (playerHealingSkillMultipliers.length > 0) + { + for (String info : playerHealingSkillMultipliers) + { + final String[] classInfo = info.trim().split("[*]"); + if (classInfo.length == 2) + { + final String id = classInfo[0].trim(); + PLAYER_HEALING_SKILL_MULTIPLIERS[StringUtil.isNumeric(id) ? Integer.parseInt(id) : Enum.valueOf(PlayerClass.class, id).getId()] = Float.parseFloat(classInfo[1].trim()); + } + } + } + + Arrays.fill(SKILL_MASTERY_CHANCE_MULTIPLIERS, 1f); + final String[] skillMasteryChanceMultipliers = config.getString("SkillMasteryChanceMultipliers", "").trim().split(";"); + if (skillMasteryChanceMultipliers.length > 0) + { + for (String info : skillMasteryChanceMultipliers) + { + final String[] classInfo = info.trim().split("[*]"); + if (classInfo.length == 2) + { + final String id = classInfo[0].trim(); + SKILL_MASTERY_CHANCE_MULTIPLIERS[StringUtil.isNumeric(id) ? Integer.parseInt(id) : Enum.valueOf(PlayerClass.class, id).getId()] = Float.parseFloat(classInfo[1].trim()); + } + } + } + + Arrays.fill(SKILL_REUSE_MULTIPLIERS, 1f); + final String[] skillReuseMultipliers = config.getString("SkillReuseMultipliers", "").trim().split(";"); + if (skillReuseMultipliers.length > 0) + { + for (String info : skillReuseMultipliers) + { + final String[] classInfo = info.trim().split("[*]"); + if (classInfo.length == 2) + { + final String id = classInfo[0].trim(); + SKILL_REUSE_MULTIPLIERS[StringUtil.isNumeric(id) ? Integer.parseInt(id) : Enum.valueOf(PlayerClass.class, id).getId()] = Float.parseFloat(classInfo[1].trim()); + } + } + } + + Arrays.fill(EXP_AMOUNT_MULTIPLIERS, 1f); + final String[] expAmountMultipliers = config.getString("ExpAmountMultipliers", "").trim().split(";"); + if (expAmountMultipliers.length > 0) + { + for (String info : expAmountMultipliers) + { + final String[] classInfo = info.trim().split("[*]"); + if (classInfo.length == 2) + { + final String id = classInfo[0].trim(); + EXP_AMOUNT_MULTIPLIERS[StringUtil.isNumeric(id) ? Integer.parseInt(id) : Enum.valueOf(PlayerClass.class, id).getId()] = Float.parseFloat(classInfo[1].trim()); + } + } + } + + Arrays.fill(SP_AMOUNT_MULTIPLIERS, 1f); + final String[] spAmountMultipliers = config.getString("SpAmountMultipliers", "").trim().split(";"); + if (spAmountMultipliers.length > 0) + { + for (String info : spAmountMultipliers) + { + final String[] classInfo = info.trim().split("[*]"); + if (classInfo.length == 2) + { + final String id = classInfo[0].trim(); + SP_AMOUNT_MULTIPLIERS[StringUtil.isNumeric(id) ? Integer.parseInt(id) : Enum.valueOf(PlayerClass.class, id).getId()] = Float.parseFloat(classInfo[1].trim()); + } + } + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/CommunityBoardConfig.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/CommunityBoardConfig.java new file mode 100644 index 0000000..010d9c5 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/CommunityBoardConfig.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.config.custom; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.l2jmobius.commons.util.ConfigReader; +import org.l2jmobius.gameserver.entity.Location; + +/** + * This class loads all the custom community board related configurations. + * @author Mobius + */ +public class CommunityBoardConfig +{ + // File + private static final String COMMUNITY_BOARD_CONFIG_FILE = "./config/Custom/CommunityBoard.ini"; + + // Constants + public static boolean CUSTOM_CB_ENABLED; + public static int COMMUNITYBOARD_CURRENCY; + public static boolean COMMUNITYBOARD_ENABLE_MULTISELLS; + public static boolean COMMUNITYBOARD_ENABLE_TELEPORTS; + public static boolean COMMUNITYBOARD_ENABLE_BUFFS; + public static boolean COMMUNITYBOARD_ENABLE_HEAL; + public static boolean COMMUNITYBOARD_ENABLE_DELEVEL; + public static int COMMUNITYBOARD_TELEPORT_PRICE; + public static int COMMUNITYBOARD_BUFF_PRICE; + public static int COMMUNITYBOARD_HEAL_PRICE; + public static int COMMUNITYBOARD_DELEVEL_PRICE; + public static boolean COMMUNITYBOARD_PEACE_ONLY; + public static boolean COMMUNITYBOARD_COMBAT_DISABLED; + public static boolean COMMUNITYBOARD_KARMA_DISABLED; + public static boolean COMMUNITYBOARD_CAST_ANIMATIONS; + public static boolean COMMUNITY_PREMIUM_SYSTEM_ENABLED; + public static int COMMUNITY_PREMIUM_COIN_ID; + public static int COMMUNITY_PREMIUM_PRICE_PER_DAY; + public static Set COMMUNITY_AVAILABLE_BUFFS; + public static Map COMMUNITY_AVAILABLE_TELEPORTS; + + public static void load() + { + final ConfigReader config = new ConfigReader(COMMUNITY_BOARD_CONFIG_FILE); + CUSTOM_CB_ENABLED = config.getBoolean("CustomCommunityBoard", false); + COMMUNITYBOARD_CURRENCY = config.getInt("CommunityCurrencyId", 57); + COMMUNITYBOARD_ENABLE_MULTISELLS = config.getBoolean("CommunityEnableMultisells", true); + COMMUNITYBOARD_ENABLE_TELEPORTS = config.getBoolean("CommunityEnableTeleports", true); + COMMUNITYBOARD_ENABLE_BUFFS = config.getBoolean("CommunityEnableBuffs", true); + COMMUNITYBOARD_ENABLE_HEAL = config.getBoolean("CommunityEnableHeal", true); + COMMUNITYBOARD_ENABLE_DELEVEL = config.getBoolean("CommunityEnableDelevel", false); + COMMUNITYBOARD_TELEPORT_PRICE = config.getInt("CommunityTeleportPrice", 0); + COMMUNITYBOARD_BUFF_PRICE = config.getInt("CommunityBuffPrice", 0); + COMMUNITYBOARD_HEAL_PRICE = config.getInt("CommunityHealPrice", 0); + COMMUNITYBOARD_DELEVEL_PRICE = config.getInt("CommunityDelevelPrice", 0); + COMMUNITYBOARD_PEACE_ONLY = config.getBoolean("CommunityBoardPeaceOnly", false); + COMMUNITYBOARD_COMBAT_DISABLED = config.getBoolean("CommunityCombatDisabled", true); + COMMUNITYBOARD_KARMA_DISABLED = config.getBoolean("CommunityKarmaDisabled", true); + COMMUNITYBOARD_CAST_ANIMATIONS = config.getBoolean("CommunityCastAnimations", false); + COMMUNITY_PREMIUM_SYSTEM_ENABLED = config.getBoolean("CommunityPremiumSystem", false); + COMMUNITY_PREMIUM_COIN_ID = config.getInt("CommunityPremiumBuyCoinId", 57); + COMMUNITY_PREMIUM_PRICE_PER_DAY = config.getInt("CommunityPremiumPricePerDay", 1000000); + final String[] allowedBuffs = config.getString("CommunityAvailableBuffs", "").split(","); + COMMUNITY_AVAILABLE_BUFFS = new HashSet<>(allowedBuffs.length); + for (String s : allowedBuffs) + { + COMMUNITY_AVAILABLE_BUFFS.add(Integer.parseInt(s)); + } + final String[] availableTeleports = config.getString("CommunityTeleportList", "").split(";"); + COMMUNITY_AVAILABLE_TELEPORTS = new HashMap<>(availableTeleports.length); + for (String s : availableTeleports) + { + final String[] splitInfo = s.split(","); + COMMUNITY_AVAILABLE_TELEPORTS.put(splitInfo[0], new Location(Integer.parseInt(splitInfo[1]), Integer.parseInt(splitInfo[2]), Integer.parseInt(splitInfo[3]))); + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/CustomMailManagerConfig.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/CustomMailManagerConfig.java new file mode 100644 index 0000000..c35aa39 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/CustomMailManagerConfig.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.config.custom; + +import org.l2jmobius.commons.util.ConfigReader; + +/** + * This class loads all the custom mail manager related configurations. + * @author Mobius + */ +public class CustomMailManagerConfig +{ + // File + private static final String CUSTOM_MAIL_MANAGER_CONFIG_FILE = "./config/Custom/CustomMailManager.ini"; + + // Constants + public static boolean CUSTOM_MAIL_MANAGER_ENABLED; + public static int CUSTOM_MAIL_MANAGER_DELAY; + + public static void load() + { + final ConfigReader custom = new ConfigReader(CUSTOM_MAIL_MANAGER_CONFIG_FILE); + CUSTOM_MAIL_MANAGER_ENABLED = custom.getBoolean("CustomMailManagerEnabled", false); + CUSTOM_MAIL_MANAGER_DELAY = custom.getInt("DatabaseQueryDelay", 30) * 1000; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/DelevelManagerConfig.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/DelevelManagerConfig.java new file mode 100644 index 0000000..ca80adc --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/DelevelManagerConfig.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.config.custom; + +import org.l2jmobius.commons.util.ConfigReader; + +/** + * This class loads all the custom delevel manager related configurations. + * @author Mobius + */ +public class DelevelManagerConfig +{ + // File + private static final String DELEVEL_MANAGER_CONFIG_FILE = "./config/Custom/DelevelManager.ini"; + + // Constants + public static boolean DELEVEL_MANAGER_ENABLED; + public static int DELEVEL_MANAGER_NPCID; + public static int DELEVEL_MANAGER_ITEMID; + public static int DELEVEL_MANAGER_ITEMCOUNT; + public static int DELEVEL_MANAGER_MINIMUM_DELEVEL; + + public static void load() + { + final ConfigReader config = new ConfigReader(DELEVEL_MANAGER_CONFIG_FILE); + DELEVEL_MANAGER_ENABLED = config.getBoolean("Enabled", false); + DELEVEL_MANAGER_NPCID = config.getInt("NpcId", 1002000); + DELEVEL_MANAGER_ITEMID = config.getInt("RequiredItemId", 4356); + DELEVEL_MANAGER_ITEMCOUNT = config.getInt("RequiredItemCount", 2); + DELEVEL_MANAGER_MINIMUM_DELEVEL = config.getInt("MimimumDelevel", 20); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/DualboxCheckConfig.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/DualboxCheckConfig.java new file mode 100644 index 0000000..218fba1 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/DualboxCheckConfig.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.config.custom; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Logger; + +import org.l2jmobius.commons.util.ConfigReader; +import org.l2jmobius.commons.util.StringUtil; + +/** + * This class loads all the custom dualbox check related configurations. + * @author Mobius + */ +public class DualboxCheckConfig +{ + private static final Logger LOGGER = Logger.getLogger(DualboxCheckConfig.class.getName()); + + // File + private static final String DUALBOX_CHECK_CONFIG_FILE = "./config/Custom/DualboxCheck.ini"; + + // Constants + public static int DUALBOX_CHECK_MAX_PLAYERS_PER_IP; + public static int DUALBOX_CHECK_MAX_OLYMPIAD_PARTICIPANTS_PER_IP; + public static int DUALBOX_CHECK_MAX_L2EVENT_PARTICIPANTS_PER_IP; + public static int DUALBOX_CHECK_MAX_OFFLINEPLAY_PER_IP; + public static int DUALBOX_CHECK_MAX_OFFLINEPLAY_PREMIUM_PER_IP; + public static boolean DUALBOX_COUNT_OFFLINE_TRADERS; + public static Map DUALBOX_CHECK_WHITELIST; + + public static void load() + { + final ConfigReader config = new ConfigReader(DUALBOX_CHECK_CONFIG_FILE); + DUALBOX_CHECK_MAX_PLAYERS_PER_IP = config.getInt("DualboxCheckMaxPlayersPerIP", 0); + DUALBOX_CHECK_MAX_OLYMPIAD_PARTICIPANTS_PER_IP = config.getInt("DualboxCheckMaxOlympiadParticipantsPerIP", 0); + DUALBOX_CHECK_MAX_L2EVENT_PARTICIPANTS_PER_IP = config.getInt("DualboxCheckMaxL2EventParticipantsPerIP", 0); + DUALBOX_CHECK_MAX_OFFLINEPLAY_PER_IP = config.getInt("DualboxCheckMaxOfflinePlayPerIP", 0); + DUALBOX_CHECK_MAX_OFFLINEPLAY_PREMIUM_PER_IP = config.getInt("DualboxCheckMaxOfflinePlayPremiumPerIP", 0); + DUALBOX_COUNT_OFFLINE_TRADERS = config.getBoolean("DualboxCountOfflineTraders", false); + + final String[] dualboxCheckWhiteList = config.getString("DualboxCheckWhitelist", "127.0.0.1,0").split(";"); + DUALBOX_CHECK_WHITELIST = new HashMap<>(dualboxCheckWhiteList.length); + for (String entry : dualboxCheckWhiteList) + { + final String[] entrySplit = entry.split(","); + if (entrySplit.length != 2) + { + LOGGER.warning(StringUtil.concat("DualboxCheck[DualboxCheckConfig.load()]: invalid config property -> DualboxCheckWhitelist \"", entry, "\"")); + } + else + { + try + { + int num = Integer.parseInt(entrySplit[1]); + num = num == 0 ? -1 : num; + DUALBOX_CHECK_WHITELIST.put(InetAddress.getByName(entrySplit[0]).hashCode(), num); + } + catch (UnknownHostException e) + { + LOGGER.warning(StringUtil.concat("DualboxCheck[DualboxCheckConfig.load()]: invalid address -> DualboxCheckWhitelist \"", entrySplit[0], "\"")); + } + catch (NumberFormatException e) + { + LOGGER.warning(StringUtil.concat("DualboxCheck[DualboxCheckConfig.load()]: invalid number -> DualboxCheckWhitelist \"", entrySplit[1], "\"")); + } + } + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/FactionSystemConfig.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/FactionSystemConfig.java new file mode 100644 index 0000000..e825231 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/FactionSystemConfig.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.config.custom; + +import org.l2jmobius.commons.util.ConfigReader; +import org.l2jmobius.gameserver.entity.Location; + +/** + * This class loads all the custom faction system related configurations. + * @author Mobius + */ +public class FactionSystemConfig +{ + // File + private static final String FACTION_SYSTEM_CONFIG_FILE = "./config/Custom/FactionSystem.ini"; + + // Constants + public static boolean FACTION_SYSTEM_ENABLED; + public static Location FACTION_STARTING_LOCATION; + public static Location FACTION_MANAGER_LOCATION; + public static Location FACTION_GOOD_BASE_LOCATION; + public static Location FACTION_EVIL_BASE_LOCATION; + public static String FACTION_GOOD_TEAM_NAME; + public static String FACTION_EVIL_TEAM_NAME; + public static int FACTION_GOOD_NAME_COLOR; + public static int FACTION_EVIL_NAME_COLOR; + public static boolean FACTION_GUARDS_ENABLED; + public static boolean FACTION_RESPAWN_AT_BASE; + public static boolean FACTION_AUTO_NOBLESS; + public static boolean FACTION_SPECIFIC_CHAT; + public static boolean FACTION_BALANCE_ONLINE_PLAYERS; + public static int FACTION_BALANCE_PLAYER_EXCEED_LIMIT; + + public static void load() + { + final ConfigReader config = new ConfigReader(FACTION_SYSTEM_CONFIG_FILE); + String[] tempString; + FACTION_SYSTEM_ENABLED = config.getBoolean("EnableFactionSystem", false); + tempString = config.getString("StartingLocation", "85332,16199,-1252").split(","); + FACTION_STARTING_LOCATION = new Location(Integer.parseInt(tempString[0]), Integer.parseInt(tempString[1]), Integer.parseInt(tempString[2])); + tempString = config.getString("ManagerSpawnLocation", "85712,15974,-1260,26808").split(","); + FACTION_MANAGER_LOCATION = new Location(Integer.parseInt(tempString[0]), Integer.parseInt(tempString[1]), Integer.parseInt(tempString[2]), tempString[3] != null ? Integer.parseInt(tempString[3]) : 0); + tempString = config.getString("GoodBaseLocation", "45306,48878,-3058").split(","); + FACTION_GOOD_BASE_LOCATION = new Location(Integer.parseInt(tempString[0]), Integer.parseInt(tempString[1]), Integer.parseInt(tempString[2])); + tempString = config.getString("EvilBaseLocation", "-44037,-113283,-237").split(","); + FACTION_EVIL_BASE_LOCATION = new Location(Integer.parseInt(tempString[0]), Integer.parseInt(tempString[1]), Integer.parseInt(tempString[2])); + FACTION_GOOD_TEAM_NAME = config.getString("GoodTeamName", "Good"); + FACTION_EVIL_TEAM_NAME = config.getString("EvilTeamName", "Evil"); + FACTION_GOOD_NAME_COLOR = Integer.decode("0x" + config.getString("GoodNameColor", "00FF00")); + FACTION_EVIL_NAME_COLOR = Integer.decode("0x" + config.getString("EvilNameColor", "0000FF")); + FACTION_GUARDS_ENABLED = config.getBoolean("EnableFactionGuards", true); + FACTION_RESPAWN_AT_BASE = config.getBoolean("RespawnAtFactionBase", true); + FACTION_AUTO_NOBLESS = config.getBoolean("FactionAutoNobless", false); + FACTION_SPECIFIC_CHAT = config.getBoolean("EnableFactionChat", true); + FACTION_BALANCE_ONLINE_PLAYERS = config.getBoolean("BalanceOnlinePlayers", true); + FACTION_BALANCE_PLAYER_EXCEED_LIMIT = config.getInt("BalancePlayerExceedLimit", 20); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/FakePlayersConfig.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/FakePlayersConfig.java new file mode 100644 index 0000000..85a290c --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/FakePlayersConfig.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.config.custom; + +import org.l2jmobius.commons.util.ConfigReader; + +/** + * This class loads all the custom fake player related configurations. + * @author Mobius + */ +public class FakePlayersConfig +{ + // File + private static final String FAKE_PLAYERS_CONFIG_FILE = "./config/Custom/FakePlayers.ini"; + + // Constants + public static boolean FAKE_PLAYERS_ENABLED; + public static boolean FAKE_PLAYER_CHAT; + public static boolean FAKE_PLAYER_USE_SHOTS; + public static boolean FAKE_PLAYER_KILL_PVP; + public static boolean FAKE_PLAYER_KILL_KARMA; + public static boolean FAKE_PLAYER_AUTO_ATTACKABLE; + public static boolean FAKE_PLAYER_AGGRO_MONSTERS; + public static boolean FAKE_PLAYER_AGGRO_PLAYERS; + public static boolean FAKE_PLAYER_AGGRO_FPC; + public static boolean FAKE_PLAYER_CAN_DROP_ITEMS; + public static boolean FAKE_PLAYER_CAN_PICKUP; + + public static void load() + { + final ConfigReader config = new ConfigReader(FAKE_PLAYERS_CONFIG_FILE); + FAKE_PLAYERS_ENABLED = config.getBoolean("EnableFakePlayers", false); + FAKE_PLAYER_CHAT = config.getBoolean("FakePlayerChat", false); + FAKE_PLAYER_USE_SHOTS = config.getBoolean("FakePlayerUseShots", false); + FAKE_PLAYER_KILL_PVP = config.getBoolean("FakePlayerKillsRewardPvP", false); + FAKE_PLAYER_KILL_KARMA = config.getBoolean("FakePlayerUnflaggedKillsKarma", false); + FAKE_PLAYER_AUTO_ATTACKABLE = config.getBoolean("FakePlayerAutoAttackable", false); + FAKE_PLAYER_AGGRO_MONSTERS = config.getBoolean("FakePlayerAggroMonsters", false); + FAKE_PLAYER_AGGRO_PLAYERS = config.getBoolean("FakePlayerAggroPlayers", false); + FAKE_PLAYER_AGGRO_FPC = config.getBoolean("FakePlayerAggroFPC", false); + FAKE_PLAYER_CAN_DROP_ITEMS = config.getBoolean("FakePlayerCanDropItems", false); + FAKE_PLAYER_CAN_PICKUP = config.getBoolean("FakePlayerCanPickup", false); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/FindPvpConfig.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/FindPvpConfig.java new file mode 100644 index 0000000..c2676a8 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/FindPvpConfig.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.config.custom; + +import org.l2jmobius.commons.util.ConfigReader; + +/** + * This class loads all the custom find PVP related configurations. + * @author Mobius + */ +public class FindPvpConfig +{ + // File + private static final String FIND_PVP_CONFIG_FILE = "./config/Custom/FindPvP.ini"; + + // Constants + public static boolean ENABLE_FIND_PVP; + + public static void load() + { + final ConfigReader config = new ConfigReader(FIND_PVP_CONFIG_FILE); + ENABLE_FIND_PVP = config.getBoolean("EnableFindPvP", false); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/FreeMountsConfig.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/FreeMountsConfig.java new file mode 100644 index 0000000..5d52200 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/FreeMountsConfig.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.config.custom; + +import org.l2jmobius.commons.util.ConfigReader; + +/** + * This class loads all the custom free mount related configurations. + * @author Mobius + */ +public class FreeMountsConfig +{ + // File + private static final String FREE_MOUNTS_CONFIG_FILE = "./config/Custom/FreeMounts.ini"; + + // Constants + public static boolean ENABLE_FREE_STRIDER; + public static boolean ENABLE_FREE_WYVERN; + + public static void load() + { + final ConfigReader config = new ConfigReader(FREE_MOUNTS_CONFIG_FILE); + ENABLE_FREE_STRIDER = config.getBoolean("EnableFreeStrider", false); + ENABLE_FREE_WYVERN = config.getBoolean("EnableFreeWyvern", false); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/MerchantZeroSellPriceConfig.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/MerchantZeroSellPriceConfig.java new file mode 100644 index 0000000..a8bc7e7 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/MerchantZeroSellPriceConfig.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.config.custom; + +import org.l2jmobius.commons.util.ConfigReader; + +/** + * This class loads all the custom merchant zero sell price related configurations. + * @author Mobius + */ +public class MerchantZeroSellPriceConfig +{ + // File + private static final String MERCHANT_ZERO_SELL_PRICE_CONFIG_FILE = "./config/Custom/MerchantZeroSellPrice.ini"; + + // Constants + public static boolean MERCHANT_ZERO_SELL_PRICE; + + public static void load() + { + final ConfigReader config = new ConfigReader(MERCHANT_ZERO_SELL_PRICE_CONFIG_FILE); + MERCHANT_ZERO_SELL_PRICE = config.getBoolean("MerchantZeroSellPrice", false); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/MultilingualSupportConfig.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/MultilingualSupportConfig.java new file mode 100644 index 0000000..e03709c --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/MultilingualSupportConfig.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.config.custom; + +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Logger; + +import org.l2jmobius.commons.util.ConfigReader; +import org.l2jmobius.gameserver.config.GeneralConfig; + +/** + * This class loads all the custom multilingual support related configurations. + * @author Mobius + */ +public class MultilingualSupportConfig +{ + private static final Logger LOGGER = Logger.getLogger(MultilingualSupportConfig.class.getName()); + + // File + private static final String MULTILANGUAL_SUPPORT_CONFIG_FILE = "./config/Custom/MultilingualSupport.ini"; + + // Constants + public static String MULTILANG_DEFAULT; + public static boolean MULTILANG_ENABLE; + public static List MULTILANG_ALLOWED = new ArrayList<>(); + public static boolean MULTILANG_VOICED_ALLOW; + + public static void load() + { + final ConfigReader config = new ConfigReader(MULTILANGUAL_SUPPORT_CONFIG_FILE); + MULTILANG_DEFAULT = config.getString("MultiLangDefault", "en").toLowerCase(); + + MULTILANG_ENABLE = config.getBoolean("MultiLangEnable", false); + if (MULTILANG_ENABLE) + { + GeneralConfig.CHECK_HTML_ENCODING = false; + } + + final String[] allowed = config.getString("MultiLangAllowed", MULTILANG_DEFAULT).split(";"); + MULTILANG_ALLOWED = new ArrayList<>(allowed.length); + for (String lang : allowed) + { + MULTILANG_ALLOWED.add(lang.toLowerCase()); + } + + if (!MULTILANG_ALLOWED.contains(MULTILANG_DEFAULT)) + { + LOGGER.warning("MultiLang[MultilingualSupportConfig.load()]: default language: " + MULTILANG_DEFAULT + " is not in allowed list!"); + } + + MULTILANG_VOICED_ALLOW = config.getBoolean("MultiLangVoiceCommand", true); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/NoblessMasterConfig.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/NoblessMasterConfig.java new file mode 100644 index 0000000..8c52058 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/NoblessMasterConfig.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.config.custom; + +import org.l2jmobius.commons.util.ConfigReader; + +/** + * This class loads all the custom nobless master related configurations. + * @author Mobius + */ +public class NoblessMasterConfig +{ + // File + private static final String NOBLESS_MASTER_CONFIG_FILE = "./config/Custom/NoblessMaster.ini"; + + // Constants + public static boolean NOBLESS_MASTER_ENABLED; + public static int NOBLESS_MASTER_NPCID; + public static int NOBLESS_MASTER_LEVEL_REQUIREMENT; + public static int NOBLESS_MASTER_ITEM_ID; + public static int NOBLESS_MASTER_ITEM_COUNT; + public static boolean NOBLESS_MASTER_REWARD_TIARA; + + public static void load() + { + final ConfigReader config = new ConfigReader(NOBLESS_MASTER_CONFIG_FILE); + NOBLESS_MASTER_ENABLED = config.getBoolean("Enabled", false); + NOBLESS_MASTER_NPCID = config.getInt("NpcId", 1003000); + NOBLESS_MASTER_LEVEL_REQUIREMENT = config.getInt("LevelRequirement", 80); + NOBLESS_MASTER_ITEM_ID = config.getInt("ItemId", 57); + NOBLESS_MASTER_ITEM_COUNT = config.getInt("ItemCount", 0); + NOBLESS_MASTER_REWARD_TIARA = config.getBoolean("RewardTiara", false); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/NpcStatMultipliersConfig.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/NpcStatMultipliersConfig.java new file mode 100644 index 0000000..ab192fc --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/NpcStatMultipliersConfig.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.config.custom; + +import org.l2jmobius.commons.util.ConfigReader; + +/** + * This class loads all the custom NPC stat multiplier related configurations. + * @author Mobius + */ +public class NpcStatMultipliersConfig +{ + // File + private static final String NPC_STAT_MULTIPLIERS_CONFIG_FILE = "./config/Custom/NpcStatMultipliers.ini"; + + // Constants + public static boolean ENABLE_NPC_STAT_MULTIPLIERS; + public static double MONSTER_HP_MULTIPLIER; + public static double MONSTER_MP_MULTIPLIER; + public static double MONSTER_PATK_MULTIPLIER; + public static double MONSTER_MATK_MULTIPLIER; + public static double MONSTER_PDEF_MULTIPLIER; + public static double MONSTER_MDEF_MULTIPLIER; + public static double MONSTER_AGRRO_RANGE_MULTIPLIER; + public static double MONSTER_CLAN_HELP_RANGE_MULTIPLIER; + public static double RAIDBOSS_HP_MULTIPLIER; + public static double RAIDBOSS_MP_MULTIPLIER; + public static double RAIDBOSS_PATK_MULTIPLIER; + public static double RAIDBOSS_MATK_MULTIPLIER; + public static double RAIDBOSS_PDEF_MULTIPLIER; + public static double RAIDBOSS_MDEF_MULTIPLIER; + public static double RAIDBOSS_AGRRO_RANGE_MULTIPLIER; + public static double RAIDBOSS_CLAN_HELP_RANGE_MULTIPLIER; + public static double GUARD_HP_MULTIPLIER; + public static double GUARD_MP_MULTIPLIER; + public static double GUARD_PATK_MULTIPLIER; + public static double GUARD_MATK_MULTIPLIER; + public static double GUARD_PDEF_MULTIPLIER; + public static double GUARD_MDEF_MULTIPLIER; + public static double GUARD_AGRRO_RANGE_MULTIPLIER; + public static double GUARD_CLAN_HELP_RANGE_MULTIPLIER; + public static double DEFENDER_HP_MULTIPLIER; + public static double DEFENDER_MP_MULTIPLIER; + public static double DEFENDER_PATK_MULTIPLIER; + public static double DEFENDER_MATK_MULTIPLIER; + public static double DEFENDER_PDEF_MULTIPLIER; + public static double DEFENDER_MDEF_MULTIPLIER; + public static double DEFENDER_AGRRO_RANGE_MULTIPLIER; + public static double DEFENDER_CLAN_HELP_RANGE_MULTIPLIER; + + public static void load() + { + final ConfigReader config = new ConfigReader(NPC_STAT_MULTIPLIERS_CONFIG_FILE); + ENABLE_NPC_STAT_MULTIPLIERS = config.getBoolean("EnableNpcStatMultipliers", false); + MONSTER_HP_MULTIPLIER = config.getDouble("MonsterHP", 1.0); + MONSTER_MP_MULTIPLIER = config.getDouble("MonsterMP", 1.0); + MONSTER_PATK_MULTIPLIER = config.getDouble("MonsterPAtk", 1.0); + MONSTER_MATK_MULTIPLIER = config.getDouble("MonsterMAtk", 1.0); + MONSTER_PDEF_MULTIPLIER = config.getDouble("MonsterPDef", 1.0); + MONSTER_MDEF_MULTIPLIER = config.getDouble("MonsterMDef", 1.0); + MONSTER_AGRRO_RANGE_MULTIPLIER = config.getDouble("MonsterAggroRange", 1.0); + MONSTER_CLAN_HELP_RANGE_MULTIPLIER = config.getDouble("MonsterClanHelpRange", 1.0); + RAIDBOSS_HP_MULTIPLIER = config.getDouble("RaidbossHP", 1.0); + RAIDBOSS_MP_MULTIPLIER = config.getDouble("RaidbossMP", 1.0); + RAIDBOSS_PATK_MULTIPLIER = config.getDouble("RaidbossPAtk", 1.0); + RAIDBOSS_MATK_MULTIPLIER = config.getDouble("RaidbossMAtk", 1.0); + RAIDBOSS_PDEF_MULTIPLIER = config.getDouble("RaidbossPDef", 1.0); + RAIDBOSS_MDEF_MULTIPLIER = config.getDouble("RaidbossMDef", 1.0); + RAIDBOSS_AGRRO_RANGE_MULTIPLIER = config.getDouble("RaidbossAggroRange", 1.0); + RAIDBOSS_CLAN_HELP_RANGE_MULTIPLIER = config.getDouble("RaidbossClanHelpRange", 1.0); + GUARD_HP_MULTIPLIER = config.getDouble("GuardHP", 1.0); + GUARD_MP_MULTIPLIER = config.getDouble("GuardMP", 1.0); + GUARD_PATK_MULTIPLIER = config.getDouble("GuardPAtk", 1.0); + GUARD_MATK_MULTIPLIER = config.getDouble("GuardMAtk", 1.0); + GUARD_PDEF_MULTIPLIER = config.getDouble("GuardPDef", 1.0); + GUARD_MDEF_MULTIPLIER = config.getDouble("GuardMDef", 1.0); + GUARD_AGRRO_RANGE_MULTIPLIER = config.getDouble("GuardAggroRange", 1.0); + GUARD_CLAN_HELP_RANGE_MULTIPLIER = config.getDouble("GuardClanHelpRange", 1.0); + DEFENDER_HP_MULTIPLIER = config.getDouble("DefenderHP", 1.0); + DEFENDER_MP_MULTIPLIER = config.getDouble("DefenderMP", 1.0); + DEFENDER_PATK_MULTIPLIER = config.getDouble("DefenderPAtk", 1.0); + DEFENDER_MATK_MULTIPLIER = config.getDouble("DefenderMAtk", 1.0); + DEFENDER_PDEF_MULTIPLIER = config.getDouble("DefenderPDef", 1.0); + DEFENDER_MDEF_MULTIPLIER = config.getDouble("DefenderMDef", 1.0); + DEFENDER_AGRRO_RANGE_MULTIPLIER = config.getDouble("DefenderAggroRange", 1.0); + DEFENDER_CLAN_HELP_RANGE_MULTIPLIER = config.getDouble("DefenderClanHelpRange", 1.0); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/OfflinePlayConfig.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/OfflinePlayConfig.java new file mode 100644 index 0000000..e52445f --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/OfflinePlayConfig.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.config.custom; + +import java.util.ArrayList; +import java.util.List; + +import org.l2jmobius.commons.util.ConfigReader; +import org.l2jmobius.gameserver.mechanics.skill.AbnormalVisualEffect; + +/** + * This class loads all the custom offline play related configurations. + * @author Mobius + */ +public class OfflinePlayConfig +{ + // File + private static final String OFFLINE_PLAY_CONFIG_FILE = "./config/Custom/OfflinePlay.ini"; + + // Constants + public static boolean ENABLE_OFFLINE_PLAY_COMMAND; + public static boolean RESTORE_AUTO_PLAY_OFFLINERS; + public static boolean OFFLINE_PLAY_PREMIUM; + public static boolean OFFLINE_PLAY_LOGOUT_ON_DEATH; + public static boolean OFFLINE_PLAY_DISCONNECT_SAME_ACCOUNT; + public static String OFFLINE_PLAY_LOGIN_MESSAGE; + public static boolean OFFLINE_PLAY_SET_NAME_COLOR; + public static int OFFLINE_PLAY_NAME_COLOR; + public static List OFFLINE_PLAY_ABNORMAL_EFFECTS = new ArrayList<>(); + + public static void load() + { + final ConfigReader config = new ConfigReader(OFFLINE_PLAY_CONFIG_FILE); + ENABLE_OFFLINE_PLAY_COMMAND = config.getBoolean("EnableOfflinePlayCommand", false); + RESTORE_AUTO_PLAY_OFFLINERS = config.getBoolean("RestoreAutoPlayOffliners", true); + OFFLINE_PLAY_PREMIUM = config.getBoolean("OfflinePlayPremium", false); + OFFLINE_PLAY_LOGOUT_ON_DEATH = config.getBoolean("OfflinePlayLogoutOnDeath", true); + OFFLINE_PLAY_DISCONNECT_SAME_ACCOUNT = config.getBoolean("OfflinePlayDisconnectSameAccount", false); + OFFLINE_PLAY_LOGIN_MESSAGE = config.getString("OfflinePlayLoginMessage", ""); + OFFLINE_PLAY_SET_NAME_COLOR = config.getBoolean("OfflinePlaySetNameColor", false); + OFFLINE_PLAY_NAME_COLOR = Integer.decode("0x" + config.getString("OfflinePlayNameColor", "808080")); + OFFLINE_PLAY_ABNORMAL_EFFECTS.clear(); + final String offlinePlayAbnormalEffects = config.getString("OfflinePlayAbnormalEffect", "").trim(); + if (!offlinePlayAbnormalEffects.isEmpty()) + { + for (String ave : offlinePlayAbnormalEffects.split(",")) + { + OFFLINE_PLAY_ABNORMAL_EFFECTS.add(Enum.valueOf(AbnormalVisualEffect.class, ave.trim())); + } + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/OfflineTradeConfig.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/OfflineTradeConfig.java new file mode 100644 index 0000000..21f3f4b --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/OfflineTradeConfig.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.config.custom; + +import java.util.ArrayList; +import java.util.List; + +import org.l2jmobius.commons.util.ConfigReader; +import org.l2jmobius.gameserver.mechanics.skill.AbnormalVisualEffect; + +/** + * This class loads all the custom offline trade related configurations. + * @author Mobius + */ +public class OfflineTradeConfig +{ + // File + private static final String OFFLINE_TRADE_CONFIG_FILE = "./config/Custom/OfflineTrade.ini"; + + // Constants + public static boolean OFFLINE_TRADE_ENABLE; + public static boolean OFFLINE_CRAFT_ENABLE; + public static boolean OFFLINE_MODE_IN_PEACE_ZONE; + public static boolean OFFLINE_MODE_NO_DAMAGE; + public static boolean OFFLINE_SET_NAME_COLOR; + public static int OFFLINE_NAME_COLOR; + public static boolean OFFLINE_FAME; + public static boolean RESTORE_OFFLINERS; + public static int OFFLINE_MAX_DAYS; + public static boolean OFFLINE_DISCONNECT_FINISHED; + public static boolean OFFLINE_DISCONNECT_SAME_ACCOUNT; + public static boolean STORE_OFFLINE_TRADE_IN_REALTIME; + public static boolean ENABLE_OFFLINE_COMMAND; + public static List OFFLINE_ABNORMAL_EFFECTS = new ArrayList<>(); + + public static void load() + { + final ConfigReader config = new ConfigReader(OFFLINE_TRADE_CONFIG_FILE); + OFFLINE_TRADE_ENABLE = config.getBoolean("OfflineTradeEnable", false); + OFFLINE_CRAFT_ENABLE = config.getBoolean("OfflineCraftEnable", false); + OFFLINE_MODE_IN_PEACE_ZONE = config.getBoolean("OfflineModeInPeaceZone", false); + OFFLINE_MODE_NO_DAMAGE = config.getBoolean("OfflineModeNoDamage", false); + OFFLINE_SET_NAME_COLOR = config.getBoolean("OfflineSetNameColor", false); + OFFLINE_NAME_COLOR = Integer.decode("0x" + config.getString("OfflineNameColor", "808080")); + OFFLINE_FAME = config.getBoolean("OfflineFame", true); + RESTORE_OFFLINERS = config.getBoolean("RestoreOffliners", false); + OFFLINE_MAX_DAYS = config.getInt("OfflineMaxDays", 10); + OFFLINE_DISCONNECT_FINISHED = config.getBoolean("OfflineDisconnectFinished", true); + OFFLINE_DISCONNECT_SAME_ACCOUNT = config.getBoolean("OfflineDisconnectSameAccount", false); + STORE_OFFLINE_TRADE_IN_REALTIME = config.getBoolean("StoreOfflineTradeInRealtime", true); + ENABLE_OFFLINE_COMMAND = config.getBoolean("EnableOfflineCommand", true); + + OFFLINE_ABNORMAL_EFFECTS.clear(); + final String offlineAbnormalEffects = config.getString("OfflineAbnormalEffect", "").trim(); + if (!offlineAbnormalEffects.isEmpty()) + { + for (String ave : offlineAbnormalEffects.split(",")) + { + OFFLINE_ABNORMAL_EFFECTS.add(Enum.valueOf(AbnormalVisualEffect.class, ave.trim())); + } + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/OnlineInfoConfig.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/OnlineInfoConfig.java new file mode 100644 index 0000000..aae1c09 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/OnlineInfoConfig.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.config.custom; + +import org.l2jmobius.commons.util.ConfigReader; + +/** + * This class loads all the custom online info related configurations. + * @author Mobius + */ +public class OnlineInfoConfig +{ + // File + private static final String ONLINE_INFO_CONFIG_FILE = "./config/Custom/OnlineInfo.ini"; + + // Constants + public static boolean ENABLE_ONLINE_COMMAND; + + public static void load() + { + final ConfigReader config = new ConfigReader(ONLINE_INFO_CONFIG_FILE); + ENABLE_ONLINE_COMMAND = config.getBoolean("EnableOnlineCommand", false); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/PasswordChangeConfig.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/PasswordChangeConfig.java new file mode 100644 index 0000000..6a9cd92 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/PasswordChangeConfig.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.config.custom; + +import org.l2jmobius.commons.util.ConfigReader; + +/** + * This class loads all the custom password change related configurations. + * @author Mobius + */ +public class PasswordChangeConfig +{ + // File + private static final String PASSWORD_CHANGE_CONFIG_FILE = "./config/Custom/PasswordChange.ini"; + + // Constants + public static boolean ALLOW_CHANGE_PASSWORD; + + public static void load() + { + final ConfigReader config = new ConfigReader(PASSWORD_CHANGE_CONFIG_FILE); + ALLOW_CHANGE_PASSWORD = config.getBoolean("AllowChangePassword", false); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/PremiumSystemConfig.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/PremiumSystemConfig.java new file mode 100644 index 0000000..30e7988 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/PremiumSystemConfig.java @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.config.custom; + +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Logger; + +import org.l2jmobius.commons.util.ConfigReader; + +/** + * This class loads all the custom premium system related configurations. + * @author Mobius + */ +public class PremiumSystemConfig +{ + private static final Logger LOGGER = Logger.getLogger(PremiumSystemConfig.class.getName()); + + // File + private static final String PREMIUM_SYSTEM_CONFIG_FILE = "./config/Custom/PremiumSystem.ini"; + + // Constants + public static boolean PREMIUM_SYSTEM_ENABLED; + public static boolean ACCOUNT_WIDE_PREMIUM; + public static boolean PC_CAFE_ENABLED; + public static boolean PC_CAFE_ONLY_PREMIUM; + public static boolean PC_CAFE_RETAIL_LIKE; + public static int PC_CAFE_REWARD_TIME; + public static int PC_CAFE_MAX_POINTS; + public static boolean PC_CAFE_ENABLE_DOUBLE_POINTS; + public static int PC_CAFE_DOUBLE_POINTS_CHANCE; + public static int ACQUISITION_PC_CAFE_RETAIL_LIKE_POINTS; + public static double PC_CAFE_POINT_RATE; + public static boolean PC_CAFE_RANDOM_POINT; + public static boolean PC_CAFE_REWARD_LOW_EXP_KILLS; + public static int PC_CAFE_LOW_EXP_KILLS_CHANCE; + public static float PREMIUM_RATE_XP; + public static float PREMIUM_RATE_SP; + public static float PREMIUM_RATE_DROP_CHANCE; + public static float PREMIUM_RATE_DROP_AMOUNT; + public static float PREMIUM_RATE_SPOIL_CHANCE; + public static float PREMIUM_RATE_SPOIL_AMOUNT; + public static float PREMIUM_RATE_QUEST_XP; + public static float PREMIUM_RATE_QUEST_SP; + public static Map PREMIUM_RATE_DROP_CHANCE_BY_ID; + public static Map PREMIUM_RATE_DROP_AMOUNT_BY_ID; + + public static void load() + { + final ConfigReader config = new ConfigReader(PREMIUM_SYSTEM_CONFIG_FILE); + PREMIUM_SYSTEM_ENABLED = config.getBoolean("EnablePremiumSystem", false); + ACCOUNT_WIDE_PREMIUM = config.getBoolean("AccountWidePremium", true); + PC_CAFE_ENABLED = config.getBoolean("PcCafeEnabled", false); + PC_CAFE_ONLY_PREMIUM = config.getBoolean("PcCafeOnlyPremium", false); + PC_CAFE_RETAIL_LIKE = config.getBoolean("PcCafeRetailLike", true); + PC_CAFE_REWARD_TIME = config.getInt("PcCafeRewardTime", 300000); + PC_CAFE_MAX_POINTS = config.getInt("MaxPcCafePoints", 200000); + if (PC_CAFE_MAX_POINTS < 0) + { + PC_CAFE_MAX_POINTS = 0; + } + PC_CAFE_ENABLE_DOUBLE_POINTS = config.getBoolean("DoublingAcquisitionPoints", false); + PC_CAFE_DOUBLE_POINTS_CHANCE = config.getInt("DoublingAcquisitionPointsChance", 1); + if ((PC_CAFE_DOUBLE_POINTS_CHANCE < 0) || (PC_CAFE_DOUBLE_POINTS_CHANCE > 100)) + { + PC_CAFE_DOUBLE_POINTS_CHANCE = 1; + } + ACQUISITION_PC_CAFE_RETAIL_LIKE_POINTS = config.getInt("AcquisitionPointsRetailLikePoints", 10); + PC_CAFE_POINT_RATE = config.getDouble("AcquisitionPointsRate", 1.0); + PC_CAFE_RANDOM_POINT = config.getBoolean("AcquisitionPointsRandom", false); + if (PC_CAFE_POINT_RATE < 0) + { + PC_CAFE_POINT_RATE = 1; + } + PC_CAFE_REWARD_LOW_EXP_KILLS = config.getBoolean("RewardLowExpKills", true); + PC_CAFE_LOW_EXP_KILLS_CHANCE = config.getInt("RewardLowExpKillsChance", 50); + if (PC_CAFE_LOW_EXP_KILLS_CHANCE < 0) + { + PC_CAFE_LOW_EXP_KILLS_CHANCE = 0; + } + if (PC_CAFE_LOW_EXP_KILLS_CHANCE > 100) + { + PC_CAFE_LOW_EXP_KILLS_CHANCE = 100; + } + PREMIUM_RATE_XP = config.getFloat("PremiumRateXp", 2); + PREMIUM_RATE_SP = config.getFloat("PremiumRateSp", 2); + PREMIUM_RATE_DROP_CHANCE = config.getFloat("PremiumRateDropChance", 2); + PREMIUM_RATE_DROP_AMOUNT = config.getFloat("PremiumRateDropAmount", 1); + PREMIUM_RATE_SPOIL_CHANCE = config.getFloat("PremiumRateSpoilChance", 2); + PREMIUM_RATE_SPOIL_AMOUNT = config.getFloat("PremiumRateSpoilAmount", 1); + PREMIUM_RATE_QUEST_XP = config.getFloat("PremiumRateQuestXp", 1); + PREMIUM_RATE_QUEST_SP = config.getFloat("PremiumRateQuestSp", 1); + final String[] premiumDropChanceMultiplier = config.getString("PremiumRateDropChanceByItemId", "").split(";"); + PREMIUM_RATE_DROP_CHANCE_BY_ID = new HashMap<>(premiumDropChanceMultiplier.length); + if (!premiumDropChanceMultiplier[0].isEmpty()) + { + for (String item : premiumDropChanceMultiplier) + { + final String[] itemSplit = item.split(","); + if (itemSplit.length != 2) + { + LOGGER.warning("PremiumSystemConfig.load(): invalid config property -> PremiumRateDropChanceByItemId \"" + item + "\""); + } + else + { + try + { + PREMIUM_RATE_DROP_CHANCE_BY_ID.put(Integer.parseInt(itemSplit[0]), Float.parseFloat(itemSplit[1])); + } + catch (NumberFormatException nfe) + { + if (!item.isEmpty()) + { + LOGGER.warning("PremiumSystemConfig.load(): invalid config property -> PremiumRateDropChanceByItemId \"" + item + "\""); + } + } + } + } + } + final String[] premiumDropAmountMultiplier = config.getString("PremiumRateDropAmountByItemId", "").split(";"); + PREMIUM_RATE_DROP_AMOUNT_BY_ID = new HashMap<>(premiumDropAmountMultiplier.length); + if (!premiumDropAmountMultiplier[0].isEmpty()) + { + for (String item : premiumDropAmountMultiplier) + { + final String[] itemSplit = item.split(","); + if (itemSplit.length != 2) + { + LOGGER.warning("PremiumSystemConfig.load(): invalid config property -> PremiumRateDropAmountByItemId \"" + item + "\""); + } + else + { + try + { + PREMIUM_RATE_DROP_AMOUNT_BY_ID.put(Integer.parseInt(itemSplit[0]), Float.parseFloat(itemSplit[1])); + } + catch (NumberFormatException nfe) + { + if (!item.isEmpty()) + { + LOGGER.warning("PremiumSystemConfig.load(): invalid config property -> PremiumRateDropAmountByItemId \"" + item + "\""); + } + } + } + } + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/PrivateStoreRangeConfig.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/PrivateStoreRangeConfig.java new file mode 100644 index 0000000..671d786 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/PrivateStoreRangeConfig.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.config.custom; + +import org.l2jmobius.commons.util.ConfigReader; + +/** + * This class loads all the custom private store range related configurations. + * @author Mobius + */ +public class PrivateStoreRangeConfig +{ + // File + private static final String PRIVATE_STORE_RANGE_CONFIG_FILE = "./config/Custom/PrivateStoreRange.ini"; + + // Constants + public static int SHOP_MIN_RANGE_FROM_PLAYER; + public static int SHOP_MIN_RANGE_FROM_NPC; + + public static void load() + { + final ConfigReader config = new ConfigReader(PRIVATE_STORE_RANGE_CONFIG_FILE); + SHOP_MIN_RANGE_FROM_PLAYER = config.getInt("ShopMinRangeFromPlayer", 50); + SHOP_MIN_RANGE_FROM_NPC = config.getInt("ShopMinRangeFromNpc", 100); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/PvpAnnounceConfig.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/PvpAnnounceConfig.java new file mode 100644 index 0000000..48ad35c --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/PvpAnnounceConfig.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.config.custom; + +import org.l2jmobius.commons.util.ConfigReader; + +/** + * This class loads all the custom PVP announce related configurations. + * @author Mobius + */ +public class PvpAnnounceConfig +{ + // File + private static final String PVP_ANNOUNCE_CONFIG_FILE = "./config/Custom/PvpAnnounce.ini"; + + // Constants + public static boolean ANNOUNCE_PK_PVP; + public static boolean ANNOUNCE_PK_PVP_NORMAL_MESSAGE; + public static String ANNOUNCE_PK_MSG; + public static String ANNOUNCE_PVP_MSG; + + public static void load() + { + final ConfigReader config = new ConfigReader(PVP_ANNOUNCE_CONFIG_FILE); + ANNOUNCE_PK_PVP = config.getBoolean("AnnouncePkPvP", false); + ANNOUNCE_PK_PVP_NORMAL_MESSAGE = config.getBoolean("AnnouncePkPvPNormalMessage", true); + ANNOUNCE_PK_MSG = config.getString("AnnouncePkMsg", "$killer has slaughtered $target"); + ANNOUNCE_PVP_MSG = config.getString("AnnouncePvpMsg", "$killer has defeated $target"); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/PvpRewardItemConfig.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/PvpRewardItemConfig.java new file mode 100644 index 0000000..93c22a4 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/PvpRewardItemConfig.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.config.custom; + +import org.l2jmobius.commons.util.ConfigReader; + +/** + * This class loads all the custom PVP reward item related configurations. + * @author Mobius + */ +public class PvpRewardItemConfig +{ + // File + private static final String PVP_REWARD_ITEM_CONFIG_FILE = "./config/Custom/PvpRewardItem.ini"; + + // Constants + public static boolean REWARD_PVP_ITEM; + public static int REWARD_PVP_ITEM_ID; + public static int REWARD_PVP_ITEM_AMOUNT; + public static boolean REWARD_PVP_ITEM_MESSAGE; + public static boolean REWARD_PK_ITEM; + public static int REWARD_PK_ITEM_ID; + public static int REWARD_PK_ITEM_AMOUNT; + public static boolean REWARD_PK_ITEM_MESSAGE; + public static boolean DISABLE_REWARDS_IN_INSTANCES; + public static boolean DISABLE_REWARDS_IN_PVP_ZONES; + + public static void load() + { + final ConfigReader config = new ConfigReader(PVP_REWARD_ITEM_CONFIG_FILE); + REWARD_PVP_ITEM = config.getBoolean("RewardPvpItem", false); + REWARD_PVP_ITEM_ID = config.getInt("RewardPvpItemId", 57); + REWARD_PVP_ITEM_AMOUNT = config.getInt("RewardPvpItemAmount", 1000); + REWARD_PVP_ITEM_MESSAGE = config.getBoolean("RewardPvpItemMessage", true); + REWARD_PK_ITEM = config.getBoolean("RewardPkItem", false); + REWARD_PK_ITEM_ID = config.getInt("RewardPkItemId", 57); + REWARD_PK_ITEM_AMOUNT = config.getInt("RewardPkItemAmount", 500); + REWARD_PK_ITEM_MESSAGE = config.getBoolean("RewardPkItemMessage", true); + DISABLE_REWARDS_IN_INSTANCES = config.getBoolean("DisableRewardsInInstances", true); + DISABLE_REWARDS_IN_PVP_ZONES = config.getBoolean("DisableRewardsInPvpZones", true); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/PvpTitleColorConfig.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/PvpTitleColorConfig.java new file mode 100644 index 0000000..a0fb6e5 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/PvpTitleColorConfig.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.config.custom; + +import org.l2jmobius.commons.util.ConfigReader; + +/** + * This class loads all the custom PVP title color related configurations. + * @author Mobius + */ +public class PvpTitleColorConfig +{ + // File + private static final String PVP_TITLE_CONFIG_FILE = "./config/Custom/PvpTitleColor.ini"; + + // Constants + public static boolean PVP_COLOR_SYSTEM_ENABLED; + public static int PVP_AMOUNT1; + public static int PVP_AMOUNT2; + public static int PVP_AMOUNT3; + public static int PVP_AMOUNT4; + public static int PVP_AMOUNT5; + public static int NAME_COLOR_FOR_PVP_AMOUNT1; + public static int NAME_COLOR_FOR_PVP_AMOUNT2; + public static int NAME_COLOR_FOR_PVP_AMOUNT3; + public static int NAME_COLOR_FOR_PVP_AMOUNT4; + public static int NAME_COLOR_FOR_PVP_AMOUNT5; + public static String TITLE_FOR_PVP_AMOUNT1; + public static String TITLE_FOR_PVP_AMOUNT2; + public static String TITLE_FOR_PVP_AMOUNT3; + public static String TITLE_FOR_PVP_AMOUNT4; + public static String TITLE_FOR_PVP_AMOUNT5; + + public static void load() + { + final ConfigReader config = new ConfigReader(PVP_TITLE_CONFIG_FILE); + PVP_COLOR_SYSTEM_ENABLED = config.getBoolean("EnablePvPColorSystem", false); + PVP_AMOUNT1 = config.getInt("PvpAmount1", 500); + PVP_AMOUNT2 = config.getInt("PvpAmount2", 1000); + PVP_AMOUNT3 = config.getInt("PvpAmount3", 1500); + PVP_AMOUNT4 = config.getInt("PvpAmount4", 2500); + PVP_AMOUNT5 = config.getInt("PvpAmount5", 5000); + NAME_COLOR_FOR_PVP_AMOUNT1 = Integer.decode("0x" + config.getString("ColorForAmount1", "00FF00")); + NAME_COLOR_FOR_PVP_AMOUNT2 = Integer.decode("0x" + config.getString("ColorForAmount2", "00FF00")); + NAME_COLOR_FOR_PVP_AMOUNT3 = Integer.decode("0x" + config.getString("ColorForAmount3", "00FF00")); + NAME_COLOR_FOR_PVP_AMOUNT4 = Integer.decode("0x" + config.getString("ColorForAmount4", "00FF00")); + NAME_COLOR_FOR_PVP_AMOUNT5 = Integer.decode("0x" + config.getString("ColorForAmount5", "00FF00")); + TITLE_FOR_PVP_AMOUNT1 = config.getString("PvPTitleForAmount1", "Title"); + TITLE_FOR_PVP_AMOUNT2 = config.getString("PvPTitleForAmount2", "Title"); + TITLE_FOR_PVP_AMOUNT3 = config.getString("PvPTitleForAmount3", "Title"); + TITLE_FOR_PVP_AMOUNT4 = config.getString("PvPTitleForAmount4", "Title"); + TITLE_FOR_PVP_AMOUNT5 = config.getString("PvPTitleForAmount5", "Title"); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/RandomSpawnsConfig.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/RandomSpawnsConfig.java new file mode 100644 index 0000000..6a1aec7 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/RandomSpawnsConfig.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.config.custom; + +import java.util.HashSet; +import java.util.Set; + +import org.l2jmobius.commons.util.ConfigReader; + +/** + * This class loads all the custom random spawn related configurations. + * @author Mobius + */ +public class RandomSpawnsConfig +{ + // File + private static final String RANDOM_SPAWNS_CONFIG_FILE = "./config/Custom/RandomSpawns.ini"; + + // Constants + public static boolean ENABLE_RANDOM_MONSTER_SPAWNS; + public static int MOB_MAX_SPAWN_RANGE; + public static int MOB_MIN_SPAWN_RANGE; + public static Set MOBS_LIST_NOT_RANDOM; + + public static void load() + { + final ConfigReader config = new ConfigReader(RANDOM_SPAWNS_CONFIG_FILE); + ENABLE_RANDOM_MONSTER_SPAWNS = config.getBoolean("EnableRandomMonsterSpawns", false); + MOB_MAX_SPAWN_RANGE = config.getInt("MaxSpawnMobRange", 150); + MOB_MIN_SPAWN_RANGE = MOB_MAX_SPAWN_RANGE * -1; + if (ENABLE_RANDOM_MONSTER_SPAWNS) + { + final String[] mobsIds = config.getString("MobsSpawnNotRandom", "18812,18813,18814,22138").split(","); + MOBS_LIST_NOT_RANDOM = new HashSet<>(mobsIds.length); + for (String id : mobsIds) + { + MOBS_LIST_NOT_RANDOM.add(Integer.parseInt(id)); + } + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/RebirthConfig.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/RebirthConfig.java new file mode 100644 index 0000000..0b30b7f --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/RebirthConfig.java @@ -0,0 +1,225 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.config.custom; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.commons.util.ConfigReader; + +/** + * Loads and stores rebirth system configuration values from the custom rebirth configuration file.
    + * Provides feature toggles, limits, rewards, visual effects, announcements and configured skill lists. + *
      + *
    • Loads all rebirth-related settings from Rebirth.ini.
    • + *
    • Stores parsed skill list definitions for mage and fighter rebirth skills.
    • + *
    • Stores rebirth skill removal rules and whitelist entries.
    • + *
    • Exposes configuration values through public static fields for runtime access.
    • + *
    + * @author BazookaRpm + */ +public class RebirthConfig +{ + // Logger. + private static final Logger LOGGER = Logger.getLogger(RebirthConfig.class.getName()); + + // Configuration file. + private static final String REBIRTH_CONFIG_FILE = "config/Custom/Rebirth.ini"; + + // Core settings. + public static boolean REBIRTH_ALLOW_REBIRTH; + public static int REBIRTH_MIN_LEVEL; + public static int REBIRTH_MAX_COUNT; + public static boolean REBIRTH_INHERIT_SKILLS_TO_SUBCLASSES; + + // Skill removal settings. + public static boolean REBIRTH_DELETE_ALL_SKILLS; + public static Set REBIRTH_SKILL_DELETE_WHITELIST = new HashSet<>(); + public static Set REBIRTH_DELETE_PROTECTED_SKILL_GROUPS = new HashSet<>(); + public static boolean REBIRTH_DELETE_RESTORE_TEMPORARY_SKILLS; + + // Cost and reward settings. + public static int REBIRTH_ITEM_ID; + public static int REBIRTH_ITEM_AMOUNT; + public static int REBIRTH_MAX_SELECTED_SKILLS; + public static int REBIRTH_REWARD_ITEM_ID; + public static int REBIRTH_REWARD_ITEM_AMOUNT; + public static String REBIRTH_SKILL_REFUND_MODE; + + // Skill pools. + public static List REBIRTH_MAGE_SKILLS = new ArrayList<>(); + public static List REBIRTH_FIGHTER_SKILLS = new ArrayList<>(); + + // Finish visual settings. + public static int REBIRTH_FINISH_SOCIAL_ID; + public static int REBIRTH_FINISH_EFFECT_SKILL_ID; + public static int REBIRTH_FINISH_EFFECT_SKILL_LVL; + + // Selection visual settings. + public static int REBIRTH_SELECT_SOCIAL_ID; + public static int REBIRTH_SELECT_EFFECT_SKILL_ID; + public static int REBIRTH_SELECT_EFFECT_SKILL_LVL; + + // Screen message settings. + public static boolean REBIRTH_SCREEN_MESSAGE_ENABLED; + public static String REBIRTH_SCREEN_MESSAGE; + public static String REBIRTH_SCREEN_MESSAGE_POSITION; + public static int REBIRTH_SCREEN_MESSAGE_TIME; + + // Global announcement settings. + public static boolean REBIRTH_GLOBAL_ANNOUNCEMENT_ENABLED; + public static String REBIRTH_GLOBAL_ANNOUNCEMENT; + public static boolean REBIRTH_GLOBAL_ANNOUNCEMENT_CRITICAL; + + /** + * Loads all rebirth configuration values from the rebirth configuration file. + */ + public static void load() + { + final ConfigReader config = new ConfigReader(REBIRTH_CONFIG_FILE); + REBIRTH_ALLOW_REBIRTH = config.getBoolean("RebirthAllow", true); + REBIRTH_MIN_LEVEL = config.getInt("RebirthMin", 80); + REBIRTH_MAX_COUNT = config.getInt("RebirthMaxCount", 3); + REBIRTH_INHERIT_SKILLS_TO_SUBCLASSES = config.getBoolean("RebirthInheritSkillsToSubclasses", true); + REBIRTH_DELETE_ALL_SKILLS = config.getBoolean("RebirthDeleteAllSkills", true); + REBIRTH_SKILL_DELETE_WHITELIST = parseIntegerSet(config.getString("RebirthSkillDeleteWhitelist", "")); + REBIRTH_DELETE_PROTECTED_SKILL_GROUPS = parseProtectedSkillGroups(config.getString("RebirthDeleteProtectedSkillGroups", "")); + REBIRTH_DELETE_RESTORE_TEMPORARY_SKILLS = config.getBoolean("RebirthDeleteRestoreTemporarySkills", true); + REBIRTH_ITEM_ID = config.getInt("RebirthItemId", 57); + REBIRTH_ITEM_AMOUNT = config.getInt("RebirthItemAmount", 3000); + REBIRTH_MAX_SELECTED_SKILLS = config.getInt("RebirthMaxSelectedSkills", 3); + REBIRTH_REWARD_ITEM_ID = config.getInt("RebirthRewardItemId", 57); + REBIRTH_REWARD_ITEM_AMOUNT = config.getInt("RebirthRewardItemAmount", 1); + REBIRTH_SKILL_REFUND_MODE = config.getString("RebirthSkillRefundMode", "MID").trim().toUpperCase(); + REBIRTH_MAGE_SKILLS = parseSkillList(config.getString("RebirthMageSkills", "")); + REBIRTH_FIGHTER_SKILLS = parseSkillList(config.getString("RebirthFighterSkills", "")); + REBIRTH_FINISH_SOCIAL_ID = config.getInt("RebirthFinishSocialId", 3); + REBIRTH_FINISH_EFFECT_SKILL_ID = config.getInt("RebirthFinishEffectSkillId", 2024); + REBIRTH_FINISH_EFFECT_SKILL_LVL = config.getInt("RebirthFinishEffectSkillLevel", 1); + REBIRTH_SELECT_SOCIAL_ID = config.getInt("RebirthSelectSocialId", 3); + REBIRTH_SELECT_EFFECT_SKILL_ID = config.getInt("RebirthSelectEffectSkillId", 2024); + REBIRTH_SELECT_EFFECT_SKILL_LVL = config.getInt("RebirthSelectEffectSkillLevel", 1); + REBIRTH_SCREEN_MESSAGE_ENABLED = config.getBoolean("RebirthScreenMessageEnabled", true); + REBIRTH_SCREEN_MESSAGE = config.getString("RebirthScreenMessage", "Congratulations %player%! Rebirth status acquired. [%rebirth_count%/%max_rebirth%]"); + REBIRTH_SCREEN_MESSAGE_POSITION = config.getString("RebirthScreenMessagePosition", "TOP_CENTER").trim().toUpperCase(); + REBIRTH_SCREEN_MESSAGE_TIME = config.getInt("RebirthScreenMessageTime", 5000); + REBIRTH_GLOBAL_ANNOUNCEMENT_ENABLED = config.getBoolean("RebirthGlobalAnnouncementEnabled", true); + REBIRTH_GLOBAL_ANNOUNCEMENT = config.getString("RebirthGlobalAnnouncement", "Player %player% has reached Rebirth %rebirth_count% of %max_rebirth%!"); + REBIRTH_GLOBAL_ANNOUNCEMENT_CRITICAL = config.getBoolean("RebirthGlobalAnnouncementCritical", false); + } + + /** + * Parses a semicolon-separated skill list string into a list of entries. + * @param value + * @return The parsed skill list, or an empty list if the value is blank. + */ + private static List parseSkillList(String value) + { + if ((value == null) || value.trim().isEmpty()) + { + return new ArrayList<>(); + } + + return Arrays.asList(value.split(";")); + } + + /** + * Parses a comma-separated integer list into a skill id set. + * @param value + * @return The parsed skill id set. + */ + private static Set parseIntegerSet(String value) + { + final Set skillIds = new HashSet<>(); + if ((value == null) || value.trim().isEmpty()) + { + return skillIds; + } + + for (String token : value.split(",")) + { + final String trimmedToken = token.trim(); + if (trimmedToken.isEmpty()) + { + continue; + } + + try + { + skillIds.add(Integer.valueOf(Integer.parseInt(trimmedToken))); + } + catch (NumberFormatException e) + { + LOGGER.log(Level.WARNING, "Invalid rebirth whitelist skill id: rawValue=" + trimmedToken + " fullValue=" + value, e); + } + } + + return skillIds; + } + + /** + * Parses a comma-separated group list into a protected skill group set. + * @param value + * @return The parsed protected skill group set. + */ + private static Set parseProtectedSkillGroups(String value) + { + final Set skillGroups = new HashSet<>(); + if ((value == null) || value.trim().isEmpty()) + { + return skillGroups; + } + + for (String token : value.split(",")) + { + final String normalizedToken = token.trim().toUpperCase(Locale.ROOT); + if (normalizedToken.isEmpty()) + { + continue; + } + + switch (normalizedToken) + { + case "STARTING_CLASS": + case "FISHING": + case "NOBLE": + case "CLAN": + case "HERO": + { + skillGroups.add(normalizedToken); + break; + } + default: + { + LOGGER.warning("Ignoring invalid rebirth protected skill group: " + normalizedToken); + break; + } + } + } + return skillGroups; + } +} \ No newline at end of file diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/SchemeBufferConfig.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/SchemeBufferConfig.java new file mode 100644 index 0000000..7a632c6 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/SchemeBufferConfig.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.config.custom; + +import org.l2jmobius.commons.util.ConfigReader; + +/** + * This class loads all the custom scheme buffer related configurations. + * @author Mobius + */ +public class SchemeBufferConfig +{ + // File + private static final String SCHEME_BUFFER_CONFIG_FILE = "./config/Custom/SchemeBuffer.ini"; + + // Constants + public static int BUFFER_MAX_SCHEMES; + public static int BUFFER_ITEM_ID; + public static int BUFFER_STATIC_BUFF_COST; + + public static void load() + { + final ConfigReader config = new ConfigReader(SCHEME_BUFFER_CONFIG_FILE); + BUFFER_MAX_SCHEMES = config.getInt("BufferMaxSchemesPerChar", 4); + BUFFER_ITEM_ID = config.getInt("BufferItemId", 57); + BUFFER_STATIC_BUFF_COST = config.getInt("BufferStaticCostPerBuff", -1); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/ScreenWelcomeMessageConfig.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/ScreenWelcomeMessageConfig.java new file mode 100644 index 0000000..ccccc74 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/ScreenWelcomeMessageConfig.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.config.custom; + +import org.l2jmobius.commons.util.ConfigReader; + +/** + * This class loads all the custom screen welcome message related configurations. + * @author Mobius + */ +public class ScreenWelcomeMessageConfig +{ + // File + private static final String SCREEN_WELCOME_MESSAGE_CONFIG_FILE = "./config/Custom/ScreenWelcomeMessage.ini"; + + // Constants + public static boolean WELCOME_MESSAGE_ENABLED; + public static String WELCOME_MESSAGE_TEXT; + public static int WELCOME_MESSAGE_TIME; + + public static void load() + { + final ConfigReader config = new ConfigReader(SCREEN_WELCOME_MESSAGE_CONFIG_FILE); + WELCOME_MESSAGE_ENABLED = config.getBoolean("ScreenWelcomeMessageEnable", false); + WELCOME_MESSAGE_TEXT = config.getString("ScreenWelcomeMessageText", "Welcome to our server!"); + WELCOME_MESSAGE_TIME = config.getInt("ScreenWelcomeMessageTime", 10) * 1000; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/SellBuffsConfig.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/SellBuffsConfig.java new file mode 100644 index 0000000..cb61fe4 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/SellBuffsConfig.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.config.custom; + +import org.l2jmobius.commons.util.ConfigReader; + +/** + * This class loads all the custom sell buff related configurations. + * @author Mobius + */ +public class SellBuffsConfig +{ + // File + private static final String SELL_BUFFS_CONFIG_FILE = "./config/Custom/SellBuffs.ini"; + + // Constants + public static boolean SELLBUFF_ENABLED; + public static int SELLBUFF_MP_MULTIPLER; + public static int SELLBUFF_PAYMENT_ID; + public static long SELLBUFF_MIN_PRICE; + public static long SELLBUFF_MAX_PRICE; + public static int SELLBUFF_MAX_BUFFS; + + public static void load() + { + final ConfigReader config = new ConfigReader(SELL_BUFFS_CONFIG_FILE); + SELLBUFF_ENABLED = config.getBoolean("SellBuffEnable", false); + SELLBUFF_MP_MULTIPLER = config.getInt("MpCostMultipler", 1); + SELLBUFF_PAYMENT_ID = config.getInt("PaymentID", 57); + SELLBUFF_MIN_PRICE = config.getLong("MinimumPrice", 100000); + SELLBUFF_MAX_PRICE = config.getLong("MaximumPrice", 100000000); + SELLBUFF_MAX_BUFFS = config.getInt("MaxBuffs", 15); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/ServerTimeConfig.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/ServerTimeConfig.java new file mode 100644 index 0000000..a16b8c0 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/ServerTimeConfig.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.config.custom; + +import org.l2jmobius.commons.util.ConfigReader; + +/** + * This class loads all the custom server time related configurations. + * @author Mobius + */ +public class ServerTimeConfig +{ + // File + private static final String SERVER_TIME_CONFIG_FILE = "./config/Custom/ServerTime.ini"; + + // Constant + public static boolean DISPLAY_SERVER_TIME; + + public static void load() + { + final ConfigReader config = new ConfigReader(SERVER_TIME_CONFIG_FILE); + DISPLAY_SERVER_TIME = config.getBoolean("DisplayServerTime", false); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/StartingLocationConfig.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/StartingLocationConfig.java new file mode 100644 index 0000000..13d2d14 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/StartingLocationConfig.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.config.custom; + +import org.l2jmobius.commons.util.ConfigReader; + +/** + * This class loads all the custom starting location related configurations. + * @author Mobius + */ +public class StartingLocationConfig +{ + // File + private static final String STARTING_LOCATION_CONFIG_FILE = "./config/Custom/StartingLocation.ini"; + + // Constants + public static boolean CUSTOM_STARTING_LOC; + public static int CUSTOM_STARTING_LOC_X; + public static int CUSTOM_STARTING_LOC_Y; + public static int CUSTOM_STARTING_LOC_Z; + + public static void load() + { + final ConfigReader config = new ConfigReader(STARTING_LOCATION_CONFIG_FILE); + CUSTOM_STARTING_LOC = config.getBoolean("CustomStartingLocation", false); + CUSTOM_STARTING_LOC_X = config.getInt("CustomStartingLocX", 50821); + CUSTOM_STARTING_LOC_Y = config.getInt("CustomStartingLocY", 186527); + CUSTOM_STARTING_LOC_Z = config.getInt("CustomStartingLocZ", -3625); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/StartingTitleConfig.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/StartingTitleConfig.java new file mode 100644 index 0000000..8d29389 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/StartingTitleConfig.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.config.custom; + +import org.l2jmobius.commons.util.ConfigReader; + +/** + * This class loads all the custom starting title related configurations. + * @author Mobius + */ +public class StartingTitleConfig +{ + // File + private static final String STARTING_TITLE_CONFIG_FILE = "./config/Custom/StartingTitle.ini"; + + // Constants + public static boolean ENABLE_CUSTOM_STARTING_TITLE; + public static String CUSTOM_STARTING_TITLE; + + public static void load() + { + final ConfigReader config = new ConfigReader(STARTING_TITLE_CONFIG_FILE); + ENABLE_CUSTOM_STARTING_TITLE = config.getBoolean("EnableStartingTitle", false); + CUSTOM_STARTING_TITLE = config.getString("StartingTitle", "Newbie"); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/TransmogConfig.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/TransmogConfig.java new file mode 100644 index 0000000..4189962 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/TransmogConfig.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.config.custom; + +import java.util.HashSet; +import java.util.Set; + +import org.l2jmobius.commons.util.ConfigReader; + +/** + * This class loads all the custom transmog related configurations. + * @author Mobius + */ +public class TransmogConfig +{ + // File + private static final String TRANSMOG_CONFIG_FILE = "./config/Custom/Transmog.ini"; + + // Constants + public static boolean ENABLE_TRANSMOG; + public static boolean TRANSMOG_SHARE_ACCOUNT; + public static int TRANSMOG_APPLY_COST; + public static int TRANSMOG_REMOVE_COST; + public static Set TRANSMOG_BANNED_ITEM_IDS = new HashSet<>(); + + public static void load() + { + final ConfigReader config = new ConfigReader(TRANSMOG_CONFIG_FILE); + ENABLE_TRANSMOG = config.getBoolean("TransmogEnabled", false); + TRANSMOG_SHARE_ACCOUNT = config.getBoolean("TransmogShareAccount", false); + TRANSMOG_APPLY_COST = config.getInt("TransmogApplyCost", 0); + TRANSMOG_REMOVE_COST = config.getInt("TransmogRemoveCost", 0); + TRANSMOG_BANNED_ITEM_IDS.clear(); + final String transmogBannedItemIds = config.getString("TransmogBannedItemIds", ""); + if (!transmogBannedItemIds.isEmpty()) + { + for (String s : transmogBannedItemIds.split(",")) + { + TRANSMOG_BANNED_ITEM_IDS.add(Integer.parseInt(s.trim())); + } + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/WalkerBotProtectionConfig.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/WalkerBotProtectionConfig.java new file mode 100644 index 0000000..16782ec --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/WalkerBotProtectionConfig.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.config.custom; + +import org.l2jmobius.commons.util.ConfigReader; + +/** + * This class loads all the custom Walker bot protection related configurations. + * @author Mobius + */ +public class WalkerBotProtectionConfig +{ + // File + private static final String WALKER_BOT_PROTECTION_CONFIG_FILE = "./config/Custom/WalkerBotProtection.ini"; + + // Constants + public static boolean L2WALKER_PROTECTION; + + public static void load() + { + final ConfigReader config = new ConfigReader(WALKER_BOT_PROTECTION_CONFIG_FILE); + L2WALKER_PROTECTION = config.getBoolean("L2WalkerProtection", false); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/WarehouseSortingConfig.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/WarehouseSortingConfig.java new file mode 100644 index 0000000..d361a11 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/WarehouseSortingConfig.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.config.custom; + +import org.l2jmobius.commons.util.ConfigReader; + +/** + * This class loads all the custom warehouse sorting related configurations. + * @author Mobius + */ +public class WarehouseSortingConfig +{ + // File + private static final String WAREHOUSE_SORTING_CONFIG_FILE = "./config/Custom/WarehouseSorting.ini"; + + // Constants + public static boolean ENABLE_WAREHOUSESORTING_CLAN; + public static boolean ENABLE_WAREHOUSESORTING_PRIVATE; + + public static void load() + { + final ConfigReader config = new ConfigReader(WAREHOUSE_SORTING_CONFIG_FILE); + ENABLE_WAREHOUSESORTING_CLAN = config.getBoolean("EnableWarehouseSortingClan", false); + ENABLE_WAREHOUSESORTING_PRIVATE = config.getBoolean("EnableWarehouseSortingPrivate", false); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/WeddingConfig.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/WeddingConfig.java new file mode 100644 index 0000000..9fcb127 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/config/custom/WeddingConfig.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.config.custom; + +import org.l2jmobius.commons.util.ConfigReader; + +/** + * This class loads all the custom wedding related configurations. + * @author Mobius + */ +public class WeddingConfig +{ + // File + private static final String WEDDING_CONFIG_FILE = "./config/Custom/Wedding.ini"; + + // Constants + public static boolean ALLOW_WEDDING; + public static int WEDDING_PRICE; + public static boolean WEDDING_PUNISH_INFIDELITY; + public static boolean WEDDING_TELEPORT; + public static int WEDDING_TELEPORT_PRICE; + public static int WEDDING_TELEPORT_DURATION; + public static boolean WEDDING_SAMESEX; + public static boolean WEDDING_FORMALWEAR; + public static int WEDDING_DIVORCE_COSTS; + + public static void load() + { + final ConfigReader config = new ConfigReader(WEDDING_CONFIG_FILE); + ALLOW_WEDDING = config.getBoolean("AllowWedding", false); + WEDDING_PRICE = config.getInt("WeddingPrice", 250000000); + WEDDING_PUNISH_INFIDELITY = config.getBoolean("WeddingPunishInfidelity", true); + WEDDING_TELEPORT = config.getBoolean("WeddingTeleport", true); + WEDDING_TELEPORT_PRICE = config.getInt("WeddingTeleportPrice", 50000); + WEDDING_TELEPORT_DURATION = config.getInt("WeddingTeleportDuration", 60); + WEDDING_SAMESEX = config.getBoolean("WeddingAllowSameSex", false); + WEDDING_FORMALWEAR = config.getBoolean("WeddingFormalWear", true); + WEDDING_DIVORCE_COSTS = config.getInt("WeddingDivorceCosts", 20); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/AugmentationData.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/AugmentationData.java new file mode 100644 index 0000000..d6912bf --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/AugmentationData.java @@ -0,0 +1,777 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.data; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.xml.parsers.DocumentBuilderFactory; + +import org.w3c.dom.Document; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; + +import org.l2jmobius.commons.util.Rnd; +import org.l2jmobius.gameserver.config.PlayerConfig; +import org.l2jmobius.gameserver.config.ServerConfig; +import org.l2jmobius.gameserver.entity.item.enums.BodyPart; +import org.l2jmobius.gameserver.entity.item.instance.Item; +import org.l2jmobius.gameserver.mechanics.options.Augmentation; +import org.l2jmobius.gameserver.mechanics.skill.holders.SkillHolder; +import org.l2jmobius.gameserver.network.clientpackets.AbstractRefinePacket; + +/** + * Loads augmentation bonuses and skills. + * @author durgus, Gigiikun + */ +public class AugmentationData +{ + private static final Logger LOGGER = Logger.getLogger(AugmentationData.class.getName()); + + // stats + private static final int STAT_BLOCKSIZE = 3640; + private static final int STAT_SUBBLOCKSIZE = 91; + public static final int MIN_SKILL_ID = STAT_BLOCKSIZE * 4; + + // skills + private static final int BLUE_START = 14561; + private static final int SKILLS_BLOCKSIZE = 178; + + // basestats + private static final int BASESTAT_STR = 16341; + private static final int BASESTAT_MEN = 16344; + + private final List> _blueSkills = new ArrayList<>(); + private final List> _purpleSkills = new ArrayList<>(); + private final List> _redSkills = new ArrayList<>(); + + private final List _augmentationChances = new ArrayList<>(); + + private final Map _allSkills = new HashMap<>(); + + protected AugmentationData() + { + for (int i = 0; i < 10; i++) + { + _blueSkills.add(new ArrayList<>()); + _purpleSkills.add(new ArrayList<>()); + _redSkills.add(new ArrayList<>()); + } + + load(); + if (!PlayerConfig.RETAIL_LIKE_AUGMENTATION) + { + for (int i = 0; i < 10; i++) + { + LOGGER.info(getClass().getSimpleName() + ": Loaded " + _blueSkills.get(i).size() + " blue, " + _purpleSkills.get(i).size() + " purple and " + _redSkills.get(i).size() + " red skills for lifeStoneLevel " + i); + } + } + else + { + LOGGER.log(Level.INFO, getClass().getSimpleName() + ": Loaded " + _augmentationChances.size() + " augmentations."); + } + } + + public class AugmentationChance + { + private final String _weaponType; + private final int _stoneId; + private final int _variationId; + private final int _categoryChance; + private final int _augmentId; + private final float _augmentChance; + + public AugmentationChance(String weaponType, int stoneId, int variationId, int categoryChance, int augmentId, float augmentChance) + { + _weaponType = weaponType; + _stoneId = stoneId; + _variationId = variationId; + _categoryChance = categoryChance; + _augmentId = augmentId; + _augmentChance = augmentChance; + } + + public String getWeaponType() + { + return _weaponType; + } + + public int getStoneId() + { + return _stoneId; + } + + public int getVariationId() + { + return _variationId; + } + + public int getCategoryChance() + { + return _categoryChance; + } + + public int getAugmentId() + { + return _augmentId; + } + + public float getAugmentChance() + { + return _augmentChance; + } + } + + public class augmentationChanceAcc + { + private final String _weaponType; + private final int _stoneId; + private final int _variationId; + private final int _categoryChance; + private final int _augmentId; + private final float _augmentChance; + + public augmentationChanceAcc(String weaponType, int stoneId, int variationId, int categoryChance, int augmentId, float augmentChance) + { + _weaponType = weaponType; + _stoneId = stoneId; + _variationId = variationId; + _categoryChance = categoryChance; + _augmentId = augmentId; + _augmentChance = augmentChance; + } + + public String getWeaponType() + { + return _weaponType; + } + + public int getStoneId() + { + return _stoneId; + } + + public int getVariationId() + { + return _variationId; + } + + public int getCategoryChance() + { + return _categoryChance; + } + + public int getAugmentId() + { + return _augmentId; + } + + public float getAugmentChance() + { + return _augmentChance; + } + } + + private void load() + { + // Load the skillmap + // Note: the skillmap data is only used when generating new augmentations the client expects a different id in order to display the skill in the items description... + if (!PlayerConfig.RETAIL_LIKE_AUGMENTATION) + { + try + { + int badAugmantData = 0; + final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setValidating(false); + factory.setIgnoringComments(true); + + final File file = new File(ServerConfig.DATAPACK_ROOT + "/data/stats/augmentation/augmentation_skillmap.xml"); + if (!file.exists()) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": ERROR The augmentation skillmap file is missing."); + return; + } + + final Document document = factory.newDocumentBuilder().parse(file); + for (Node n = document.getFirstChild(); n != null; n = n.getNextSibling()) + { + if ("list".equalsIgnoreCase(n.getNodeName())) + { + for (Node d = n.getFirstChild(); d != null; d = d.getNextSibling()) + { + if ("augmentation".equalsIgnoreCase(d.getNodeName())) + { + NamedNodeMap attrs = d.getAttributes(); + int skillId = 0; + final int augmentationId = Integer.parseInt(attrs.getNamedItem("id").getNodeValue()); + int skillLevel = 0; + String type = "blue"; + for (Node cd = d.getFirstChild(); cd != null; cd = cd.getNextSibling()) + { + if ("skillId".equalsIgnoreCase(cd.getNodeName())) + { + attrs = cd.getAttributes(); + skillId = Integer.parseInt(attrs.getNamedItem("val").getNodeValue()); + } + else if ("skillLevel".equalsIgnoreCase(cd.getNodeName())) + { + attrs = cd.getAttributes(); + skillLevel = Integer.parseInt(attrs.getNamedItem("val").getNodeValue()); + } + else if ("type".equalsIgnoreCase(cd.getNodeName())) + { + attrs = cd.getAttributes(); + type = attrs.getNamedItem("val").getNodeValue(); + } + } + + if (skillId == 0) + { + badAugmantData++; + continue; + } + else if (skillLevel == 0) + { + badAugmantData++; + continue; + } + + final int k = (augmentationId - BLUE_START) / SKILLS_BLOCKSIZE; + if (type.equalsIgnoreCase("blue")) + { + _blueSkills.get(k).add(augmentationId); + } + else if (type.equalsIgnoreCase("purple")) + { + _purpleSkills.get(k).add(augmentationId); + } + else + { + _redSkills.get(k).add(augmentationId); + } + + _allSkills.put(augmentationId, new SkillHolder(skillId, skillLevel)); + } + } + } + } + + if (badAugmantData != 0) + { + LOGGER.info(getClass().getSimpleName() + ": " + badAugmantData + " negative effect skill(s) were skipped."); + } + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": ERROR parsing augmentation_skillmap.xml.", e); + return; + } + } + else + { + final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setValidating(false); + factory.setIgnoringComments(true); + + final File file = new File(ServerConfig.DATAPACK_ROOT + "/data/stats/augmentation/retailchances.xml"); + if (file.exists()) + { + Document document = null; + try + { + document = factory.newDocumentBuilder().parse(file); + } + catch (Exception e) + { + LOGGER.warning("Problem with AugmentationData: " + e.getMessage()); + return; + } + + String weaponType = null; + int stoneId = 0; + int variationId = 0; + int categoryChance = 0; + int augmentId = 0; + float augmentChance = 0; + for (Node node = document.getFirstChild(); node != null; node = node.getNextSibling()) + { + if (node.getNodeName().equals("list")) + { + NamedNodeMap nodeAttributes = null; + + // System.out.println("We're going through the list now."); + for (Node n = node.getFirstChild(); n != null; n = n.getNextSibling()) + { + if (n.getNodeName().equals("weapon")) + { + nodeAttributes = n.getAttributes(); + weaponType = nodeAttributes.getNamedItem("type").getNodeValue(); + + // System.out.println("Now showing Augmentations for " + aWeaponType + " Weapons."); + for (Node c = n.getFirstChild(); c != null; c = c.getNextSibling()) + { + if (c.getNodeName().equals("stone")) + { + nodeAttributes = c.getAttributes(); + stoneId = Integer.parseInt(nodeAttributes.getNamedItem("id").getNodeValue()); + for (Node v = c.getFirstChild(); v != null; v = v.getNextSibling()) + { + if (v.getNodeName().equals("variation")) + { + nodeAttributes = v.getAttributes(); + variationId = Integer.parseInt(nodeAttributes.getNamedItem("id").getNodeValue()); + for (Node j = v.getFirstChild(); j != null; j = j.getNextSibling()) + { + if (j.getNodeName().equals("category")) + { + nodeAttributes = j.getAttributes(); + categoryChance = Integer.parseInt(nodeAttributes.getNamedItem("probability").getNodeValue()); + + // System.out.println("Stone Id: " + aStoneId + ", Variation Id: " + aVariationId + ", Category Chances: " + aCategoryChance); + for (Node e = j.getFirstChild(); e != null; e = e.getNextSibling()) + { + if (e.getNodeName().equals("augment")) + { + nodeAttributes = e.getAttributes(); + augmentId = Integer.parseInt(nodeAttributes.getNamedItem("id").getNodeValue()); + augmentChance = Float.parseFloat(nodeAttributes.getNamedItem("chance").getNodeValue()); + _augmentationChances.add(new AugmentationChance(weaponType, stoneId, variationId, categoryChance, augmentId, augmentChance)); + } + } + } + } + } + } + } + } + } + } + } + } + } + else + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": ERROR The retailchances.xml data file is missing."); + return; + } + } + } + + /** + * Generate a new random augmentation + * @param level + * @param lifeStoneGrade + * @param bodyPart + * @param lifeStoneId + * @param item + * @return + */ + public Augmentation generateRandomAugmentation(int level, int lifeStoneGrade, BodyPart bodyPart, int lifeStoneId, Item item) + { + int stat12 = 0; + int stat34 = 0; + if (PlayerConfig.RETAIL_LIKE_AUGMENTATION) + { + if (item.getTemplate().isMagicWeapon()) + { + final List selectedChances12 = new ArrayList<>(); + final List selectedChances34 = new ArrayList<>(); + for (AugmentationChance ac : _augmentationChances) + { + if (ac.getWeaponType().equals("mage") && (ac.getStoneId() == lifeStoneId)) + { + if (ac.getVariationId() == 1) + { + selectedChances12.add(ac); + } + else + { + selectedChances34.add(ac); + } + } + } + + int r = Rnd.get(10000); + float s = 10000; + for (AugmentationChance ac : selectedChances12) + { + if (s > r) + { + s -= (ac.getAugmentChance() * 100); + stat12 = ac.getAugmentId(); + } + } + + int[] gradeChance = null; + switch (lifeStoneGrade) + { + case AbstractRefinePacket.GRADE_NONE: + { + gradeChance = PlayerConfig.RETAIL_LIKE_AUGMENTATION_NG_CHANCE; + break; + } + case AbstractRefinePacket.GRADE_MID: + { + gradeChance = PlayerConfig.RETAIL_LIKE_AUGMENTATION_MID_CHANCE; + break; + } + case AbstractRefinePacket.GRADE_HIGH: + { + gradeChance = PlayerConfig.RETAIL_LIKE_AUGMENTATION_HIGH_CHANCE; + break; + } + case AbstractRefinePacket.GRADE_TOP: + { + gradeChance = PlayerConfig.RETAIL_LIKE_AUGMENTATION_TOP_CHANCE; + break; + } + default: + { + gradeChance = PlayerConfig.RETAIL_LIKE_AUGMENTATION_NG_CHANCE; + } + } + + int c = Rnd.get(100); + if (c < gradeChance[0]) + { + c = 55; + } + else if (c < (gradeChance[0] + gradeChance[1])) + { + c = 35; + } + else if (c < (gradeChance[0] + gradeChance[1] + gradeChance[2])) + { + c = 7; + } + else + { + c = 3; + } + + final List selectedChances34final = new ArrayList<>(); + for (AugmentationChance ac : selectedChances34) + { + if (ac.getCategoryChance() == c) + { + selectedChances34final.add(ac); + } + } + + r = Rnd.get(10000); + s = 10000; + for (AugmentationChance ac : selectedChances34final) + { + if (s > r) + { + s -= (ac.getAugmentChance() * 100); + stat34 = ac.getAugmentId(); + } + } + } + else + { + final List selectedChances12 = new ArrayList<>(); + final List selectedChances34 = new ArrayList<>(); + for (AugmentationChance ac : _augmentationChances) + { + if (ac.getWeaponType().equals("warrior") && (ac.getStoneId() == lifeStoneId)) + { + if (ac.getVariationId() == 1) + { + selectedChances12.add(ac); + } + else + { + selectedChances34.add(ac); + } + } + } + + int r = Rnd.get(10000); + float s = 10000; + for (AugmentationChance ac : selectedChances12) + { + if (s > r) + { + s -= (ac.getAugmentChance() * 100); + stat12 = ac.getAugmentId(); + } + } + + int[] gradeChance = null; + switch (lifeStoneGrade) + { + case AbstractRefinePacket.GRADE_NONE: + { + gradeChance = PlayerConfig.RETAIL_LIKE_AUGMENTATION_NG_CHANCE; + break; + } + case AbstractRefinePacket.GRADE_MID: + { + gradeChance = PlayerConfig.RETAIL_LIKE_AUGMENTATION_MID_CHANCE; + break; + } + case AbstractRefinePacket.GRADE_HIGH: + { + gradeChance = PlayerConfig.RETAIL_LIKE_AUGMENTATION_HIGH_CHANCE; + break; + } + case AbstractRefinePacket.GRADE_TOP: + { + gradeChance = PlayerConfig.RETAIL_LIKE_AUGMENTATION_TOP_CHANCE; + break; + } + default: + { + gradeChance = PlayerConfig.RETAIL_LIKE_AUGMENTATION_NG_CHANCE; + } + } + + int c = Rnd.get(100); + if (c < gradeChance[0]) + { + c = 55; + } + else if (c < (gradeChance[0] + gradeChance[1])) + { + c = 35; + } + else if (c < (gradeChance[0] + gradeChance[1] + gradeChance[2])) + { + c = 7; + } + else + { + c = 3; + } + + final List selectedChances34final = new ArrayList<>(); + for (AugmentationChance ac : selectedChances34) + { + if (ac.getCategoryChance() == c) + { + selectedChances34final.add(ac); + } + } + + r = Rnd.get(10000); + s = 10000; + for (AugmentationChance ac : selectedChances34final) + { + if (s > r) + { + s -= (ac.getAugmentChance() * 100); + stat34 = ac.getAugmentId(); + } + } + } + + return new Augmentation(((stat34 << 16) + stat12)); + } + + boolean generateSkill = false; + boolean generateGlow = false; + + // life stone level is used for stat Id and skill level, but here the max level is 9 + int lifeStoneLevel = Math.min(level, 9); + + switch (lifeStoneGrade) + { + case AbstractRefinePacket.GRADE_NONE: + { + if (Rnd.get(1, 100) <= PlayerConfig.AUGMENTATION_NG_SKILL_CHANCE) + { + generateSkill = true; + } + + if (Rnd.get(1, 100) <= PlayerConfig.AUGMENTATION_NG_GLOW_CHANCE) + { + generateGlow = true; + } + break; + } + case AbstractRefinePacket.GRADE_MID: + { + if (Rnd.get(1, 100) <= PlayerConfig.AUGMENTATION_MID_SKILL_CHANCE) + { + generateSkill = true; + } + + if (Rnd.get(1, 100) <= PlayerConfig.AUGMENTATION_MID_GLOW_CHANCE) + { + generateGlow = true; + } + break; + } + case AbstractRefinePacket.GRADE_HIGH: + { + if (Rnd.get(1, 100) <= PlayerConfig.AUGMENTATION_HIGH_SKILL_CHANCE) + { + generateSkill = true; + } + + if (Rnd.get(1, 100) <= PlayerConfig.AUGMENTATION_HIGH_GLOW_CHANCE) + { + generateGlow = true; + } + break; + } + case AbstractRefinePacket.GRADE_TOP: + { + if (Rnd.get(1, 100) <= PlayerConfig.AUGMENTATION_TOP_SKILL_CHANCE) + { + generateSkill = true; + } + + if (Rnd.get(1, 100) <= PlayerConfig.AUGMENTATION_TOP_GLOW_CHANCE) + { + generateGlow = true; + } + break; + } + } + + if (!generateSkill && (Rnd.get(1, 100) <= PlayerConfig.AUGMENTATION_BASESTAT_CHANCE)) + { + stat34 = Rnd.get(BASESTAT_STR, BASESTAT_MEN); + } + + // Second: decide which grade the augmentation result is going to have: + // 0:yellow, 1:blue, 2:purple, 3:red + // The chances used here are most likely custom, + // what's known is: you can't have yellow with skill(or baseStatModifier) + // noGrade stone can not have glow, mid only with skill, high has a chance(custom), top allways glow + int resultColor = Rnd.get(0, 100); + if ((stat34 == 0) && !generateSkill) + { + if (resultColor <= ((15 * lifeStoneGrade) + 40)) + { + resultColor = 1; + } + else + { + resultColor = 0; + } + } + else + { + if ((resultColor <= ((10 * lifeStoneGrade) + 5)) || (stat34 != 0)) + { + resultColor = 3; + } + else if (resultColor <= ((10 * lifeStoneGrade) + 10)) + { + resultColor = 1; + } + else + { + resultColor = 2; + } + } + + // generate a skill if necessary + if (generateSkill) + { + switch (resultColor) + { + case 1: // blue skill + { + stat34 = _blueSkills.get(lifeStoneLevel).get(Rnd.get(0, _blueSkills.get(lifeStoneLevel).size() - 1)); + break; + } + case 2: // purple skill + { + stat34 = _purpleSkills.get(lifeStoneLevel).get(Rnd.get(0, _purpleSkills.get(lifeStoneLevel).size() - 1)); + break; + } + case 3: // red skill + { + stat34 = _redSkills.get(lifeStoneLevel).get(Rnd.get(0, _redSkills.get(lifeStoneLevel).size() - 1)); + break; + } + } + } + + // Third: Calculate the subblock offset for the chosen color, + // and the level of the lifeStone + // from large number of retail augmentations: + // no skill part + // Id for stat12: + // A:1-910 B:911-1820 C:1821-2730 D:2731-3640 E:3641-4550 F:4551-5460 G:5461-6370 H:6371-7280 + // Id for stat34(this defines the color): + // I:7281-8190(yellow) K:8191-9100(blue) L:10921-11830(yellow) M:11831-12740(blue) + // you can combine I-K with A-D and L-M with E-H + // using C-D or G-H Id you will get a glow effect + // there seems no correlation in which grade use which Id except for the glowing restriction + // skill part + // Id for stat12: + // same for no skill part + // A same as E, B same as F, C same as G, D same as H + // A - no glow, no grade LS + // B - weak glow, mid grade LS? + // C - glow, high grade LS? + // D - strong glow, top grade LS? + + // Is neither a skill nor basestat used for stat34? Then generate a normal stat. + int offset; + if (stat34 == 0) + { + final int temp = Rnd.get(2, 3); + final int colorOffset = (resultColor * (10 * STAT_SUBBLOCKSIZE)) + (temp * STAT_BLOCKSIZE) + 1; + offset = (lifeStoneLevel * STAT_SUBBLOCKSIZE) + colorOffset; + stat34 = Rnd.get(offset, (offset + STAT_SUBBLOCKSIZE) - 1); + if (generateGlow && (lifeStoneGrade >= 2)) + { + offset = (lifeStoneLevel * STAT_SUBBLOCKSIZE) + ((temp - 2) * STAT_BLOCKSIZE) + (lifeStoneGrade * (10 * STAT_SUBBLOCKSIZE)) + 1; + } + else + { + offset = (lifeStoneLevel * STAT_SUBBLOCKSIZE) + ((temp - 2) * STAT_BLOCKSIZE) + (Rnd.get(0, 1) * (10 * STAT_SUBBLOCKSIZE)) + 1; + } + } + else + { + if (!generateGlow) + { + offset = (lifeStoneLevel * STAT_SUBBLOCKSIZE) + (Rnd.get(0, 1) * STAT_BLOCKSIZE) + 1; + } + else + { + offset = (lifeStoneLevel * STAT_SUBBLOCKSIZE) + (Rnd.get(0, 1) * STAT_BLOCKSIZE) + (((lifeStoneGrade + resultColor) / 2) * (10 * STAT_SUBBLOCKSIZE)) + 1; + } + } + + stat12 = Rnd.get(offset, (offset + STAT_SUBBLOCKSIZE) - 1); + return new Augmentation(((stat34 << 16) + stat12)); + } + + public static AugmentationData getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final AugmentationData INSTANCE = new AugmentationData(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/HeroSkillTable.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/HeroSkillTable.java new file mode 100644 index 0000000..f9956bc --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/HeroSkillTable.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.data; + +import org.l2jmobius.gameserver.data.xml.SkillData; +import org.l2jmobius.gameserver.mechanics.skill.Skill; + +/** + * @author BiTi + */ +public class HeroSkillTable +{ + private static final Skill[] _heroSkills = new Skill[5]; + private static final int[] _heroSkillsId = + { + 395, + 396, + 1374, + 1375, + 1376 + }; + + HeroSkillTable() + { + for (int i = 0; i < _heroSkillsId.length; i++) + { + _heroSkills[i] = SkillData.getInstance().getSkill(_heroSkillsId[i], 1); + } + } + + public static HeroSkillTable getInstance() + { + return SingletonHolder._instance; + } + + public static Skill[] getHeroSkills() + { + return _heroSkills; + } + + public static boolean isHeroSkill(int skillId) + { + /* + * Do not perform checks directly on L2Skill array, it will cause errors due to SkillTable not initialized + */ + for (int id : _heroSkillsId) + { + if (id == skillId) + { + return true; + } + } + + return false; + } + + private static class SingletonHolder + { + protected static final HeroSkillTable _instance = new HeroSkillTable(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/MerchantPriceConfigTable.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/MerchantPriceConfigTable.java new file mode 100644 index 0000000..4c78a95 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/MerchantPriceConfigTable.java @@ -0,0 +1,287 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.data; + +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.xml.sax.SAXException; + +import org.l2jmobius.gameserver.config.ServerConfig; +import org.l2jmobius.gameserver.entity.actor.instance.Merchant; +import org.l2jmobius.gameserver.managers.CastleManager; +import org.l2jmobius.gameserver.managers.ZoneManager; +import org.l2jmobius.gameserver.mechanics.siege.Castle; + +/** + * @author KenM + */ +public class MerchantPriceConfigTable +{ + // Zoey76: TODO: Implement using IXmlReader. + private static final Logger LOGGER = Logger.getLogger(MerchantPriceConfigTable.class.getName()); + + public static MerchantPriceConfigTable getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static final String MPCS_FILE = "MerchantPriceConfig.xml"; + + private final Map _mpcs = new HashMap<>(); + private MerchantPriceConfig _defaultMpc; + + public MerchantPriceConfig getMerchantPriceConfig(Merchant npc) + { + for (MerchantPriceConfig mpc : _mpcs.values()) + { + if (ZoneManager.getInstance().getRegion(npc).getZones().containsKey(mpc.getZoneId())) + { + return mpc; + } + } + + return _defaultMpc; + } + + public MerchantPriceConfig getMerchantPriceConfig(int id) + { + return _mpcs.get(id); + } + + public void loadXML() throws SAXException, IOException, ParserConfigurationException + { + final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setValidating(false); + factory.setIgnoringComments(true); + final File file = new File(ServerConfig.DATAPACK_ROOT + "/data/" + MPCS_FILE); + if (file.exists()) + { + int defaultPriceConfigId; + final Document document = factory.newDocumentBuilder().parse(file); + + Node n = document.getDocumentElement(); + final Node dpcNode = n.getAttributes().getNamedItem("defaultPriceConfig"); + if (dpcNode == null) + { + throw new IllegalStateException("merchantPriceConfig must define an 'defaultPriceConfig'"); + } + + defaultPriceConfigId = Integer.parseInt(dpcNode.getNodeValue()); + + MerchantPriceConfig mpc; + for (n = n.getFirstChild(); n != null; n = n.getNextSibling()) + { + mpc = parseMerchantPriceConfig(n); + if (mpc != null) + { + _mpcs.put(mpc.getId(), mpc); + } + } + + final MerchantPriceConfig defaultMpc = getMerchantPriceConfig(defaultPriceConfigId); + if (defaultMpc == null) + { + throw new IllegalStateException("'defaultPriceConfig' points to an non-loaded priceConfig"); + } + + _defaultMpc = defaultMpc; + } + } + + private MerchantPriceConfig parseMerchantPriceConfig(Node n) + { + if (n.getNodeName().equals("priceConfig")) + { + final int id; + final int baseTax; + int castleId = -1; + int zoneId = -1; + final String name; + + Node node = n.getAttributes().getNamedItem("id"); + if (node == null) + { + throw new IllegalStateException("Must define the priceConfig 'id'"); + } + + id = Integer.parseInt(node.getNodeValue()); + + node = n.getAttributes().getNamedItem("name"); + if (node == null) + { + throw new IllegalStateException("Must define the priceConfig 'name'"); + } + + name = node.getNodeValue(); + + node = n.getAttributes().getNamedItem("baseTax"); + if (node == null) + { + throw new IllegalStateException("Must define the priceConfig 'baseTax'"); + } + + baseTax = Integer.parseInt(node.getNodeValue()); + + node = n.getAttributes().getNamedItem("castleId"); + if (node != null) + { + castleId = Integer.parseInt(node.getNodeValue()); + } + + node = n.getAttributes().getNamedItem("zoneId"); + if (node != null) + { + zoneId = Integer.parseInt(node.getNodeValue()); + } + + return new MerchantPriceConfig(id, name, baseTax, castleId, zoneId); + } + + return null; + } + + public void loadInstances() + { + try + { + loadXML(); + LOGGER.info(getClass().getSimpleName() + ": Loaded " + _mpcs.size() + " merchant price configs."); + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, getClass().getSimpleName() + ": Failed loading MerchantPriceConfigTable. Reason: " + e.getMessage(), e); + } + } + + public void updateReferences() + { + for (MerchantPriceConfig mpc : _mpcs.values()) + { + mpc.updateReferences(); + } + } + + public static class MerchantPriceConfig + { + private final int _id; + private final String _name; + private final int _baseTax; + private final int _castleId; + private Castle _castle; + private final int _zoneId; + + public MerchantPriceConfig(int id, String name, int baseTax, int castleId, int zoneId) + { + _id = id; + _name = name; + _baseTax = baseTax; + _castleId = castleId; + _zoneId = zoneId; + } + + /** + * @return Returns the id. + */ + public int getId() + { + return _id; + } + + /** + * @return Returns the name. + */ + public String getName() + { + return _name; + } + + /** + * @return Returns the baseTax. + */ + public int getBaseTax() + { + return _baseTax; + } + + /** + * @return Returns the baseTax / 100.0. + */ + public double getBaseTaxRate() + { + return _baseTax / 100.0; + } + + /** + * @return Returns the castle. + */ + public Castle getCastle() + { + return _castle; + } + + /** + * @return Returns the zoneId. + */ + public int getZoneId() + { + return _zoneId; + } + + public boolean hasCastle() + { + return _castle != null; + } + + public double getCastleTaxRate() + { + return hasCastle() ? _castle.getTaxRate() : 0.0; + } + + public int getTotalTax() + { + return hasCastle() ? (_castle.getTaxPercent() + _baseTax) : _baseTax; + } + + public double getTotalTaxRate() + { + return getTotalTax() / 100.0; + } + + public void updateReferences() + { + if (_castleId > 0) + { + _castle = CastleManager.getInstance().getCastleById(_castleId); + } + } + } + + private static class SingletonHolder + { + protected static final MerchantPriceConfigTable INSTANCE = new MerchantPriceConfigTable(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/SchemeBufferTable.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/SchemeBufferTable.java new file mode 100644 index 0000000..3874fe9 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/SchemeBufferTable.java @@ -0,0 +1,485 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.data; + +import java.io.File; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.TreeMap; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentSkipListMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.xml.parsers.DocumentBuilderFactory; + +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; + +import org.l2jmobius.commons.database.DatabaseFactory; +import org.l2jmobius.gameserver.config.custom.SchemeBufferConfig; +import org.l2jmobius.gameserver.entity.actor.holders.npc.BuffSkillHolder; + +/** + * Loads available scheme buffer skills and manages player schemes in memory and database.
    + * Provides category-aware lookups to support duplicated skill IDs across different categories. + *
      + *
    • Loads skills from {@value #SKILLS_XML_PATH}.
    • + *
    • Loads and saves player schemes from/to database.
    • + *
    • Resolves category-aware skill holders for manual pages.
    • + *
    + * @author Mobius, BazookaRpm + */ +public class SchemeBufferTable +{ + private static final Logger LOGGER = Logger.getLogger(SchemeBufferTable.class.getName()); + + // Constants. + private static final String SKILLS_XML_PATH = "./data/SchemeBufferSkills.xml"; + private static final String LOAD_SCHEMES = "SELECT * FROM buffer_schemes"; + private static final String DELETE_SCHEMES = "TRUNCATE TABLE buffer_schemes"; + private static final String INSERT_SCHEME = "INSERT INTO buffer_schemes (object_id, scheme_name, skills) VALUES (?,?,?)"; + private static final String TYPE_MAGE_GROUP = "MAGE_GROUP"; + private static final String TYPE_FIGHTER_GROUP = "FIGHTER_GROUP"; + private static final String DB_COLUMN_OBJECT_ID = "object_id"; + private static final String DB_COLUMN_SCHEME_NAME = "scheme_name"; + private static final String DB_COLUMN_SKILLS = "skills"; + private static final String SKILL_SEPARATOR = ","; + + // Player schemes storage. + private final Map>> _schemesTable = new ConcurrentHashMap<>(); + + // Skills registry. + private final Map _availableBuffs = new LinkedHashMap<>(); + private final Map> _skillIdsByType = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + private final Map> _availableBuffsByType = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + private final List _skillTypesOrder = new ArrayList<>(); + + /** + * Initializes skills registry and loads player schemes. + */ + public SchemeBufferTable() + { + loadAvailableBuffs(); + loadPlayerSchemes(); + LOGGER.info("SchemeBufferTable: Loaded " + _schemesTable.size() + " players and " + _availableBuffs.size() + " available skills."); + } + + /** + * Saves all loaded schemes to database by truncating and inserting current in-memory data. + */ + public void saveSchemes() + { + try (Connection connection = DatabaseFactory.getConnection()) + { + try (PreparedStatement deleteStatement = connection.prepareStatement(DELETE_SCHEMES)) + { + deleteStatement.execute(); + } + + try (PreparedStatement insertStatement = connection.prepareStatement(INSERT_SCHEME)) + { + for (Entry>> playerEntry : _schemesTable.entrySet()) + { + for (Entry> schemeEntry : playerEntry.getValue().entrySet()) + { + final StringBuilder skillsBuilder = new StringBuilder(); + for (int skillId : schemeEntry.getValue()) + { + skillsBuilder.append(skillId).append(SKILL_SEPARATOR); + } + + if (!skillsBuilder.isEmpty()) + { + skillsBuilder.setLength(skillsBuilder.length() - SKILL_SEPARATOR.length()); + } + + insertStatement.setInt(1, playerEntry.getKey()); + insertStatement.setString(2, schemeEntry.getKey()); + insertStatement.setString(3, skillsBuilder.toString()); + insertStatement.addBatch(); + } + } + insertStatement.executeBatch(); + } + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "SchemeBufferTable: Error while saving schemes. Players: " + _schemesTable.size() + ".", e); + } + } + + /** + * Registers or replaces a scheme for the given player. + * @param playerId + * @param schemeName + * @param skillIds + */ + public void setScheme(int playerId, String schemeName, List skillIds) + { + final Map> schemes = _schemesTable.computeIfAbsent(playerId, key -> new ConcurrentSkipListMap<>(String.CASE_INSENSITIVE_ORDER)); + if ((schemes.size() >= SchemeBufferConfig.BUFFER_MAX_SCHEMES) && !schemes.containsKey(schemeName)) + { + return; + } + + final List safeList = (skillIds != null) ? List.copyOf(skillIds) : Collections.emptyList(); + schemes.put(schemeName, safeList); + } + + /** + * Deletes a scheme for the given player. + * @param playerId + * @param schemeName + * @return true if removed. + */ + public boolean deleteScheme(int playerId, String schemeName) + { + final Map> schemes = _schemesTable.get(playerId); + if (schemes == null) + { + return false; + } + + final boolean removed = schemes.remove(schemeName) != null; + if (schemes.isEmpty()) + { + _schemesTable.remove(playerId); + } + return removed; + } + + /** + * Adds a skill to a scheme (copy-on-write). No-op if scheme doesn't exist. + * @param playerId + * @param schemeName + * @param skillId + * @return true if added. + */ + public boolean addSkillToScheme(int playerId, String schemeName, int skillId) + { + final Map> schemes = _schemesTable.get(playerId); + if (schemes == null) + { + return false; + } + + final List current = schemes.get(schemeName); + if (current == null) + { + return false; + } + + if (current.contains(skillId)) + { + return false; + } + + final List updated = new ArrayList<>(current); + updated.add(skillId); + schemes.put(schemeName, Collections.unmodifiableList(updated)); + return true; + } + + /** + * Removes a skill from a scheme (copy-on-write). No-op if scheme doesn't exist. + * @param playerId + * @param schemeName + * @param skillId + * @return true if removed. + */ + public boolean removeSkillFromScheme(int playerId, String schemeName, int skillId) + { + final Map> schemes = _schemesTable.get(playerId); + if (schemes == null) + { + return false; + } + + final List current = schemes.get(schemeName); + if (current == null) + { + return false; + } + + if (!current.contains(skillId)) + { + return false; + } + + final List updated = new ArrayList<>(current); + updated.remove(Integer.valueOf(skillId)); + schemes.put(schemeName, Collections.unmodifiableList(updated)); + return true; + } + + /** + * Returns the scheme map for a player (read-only view). + * @param playerId + * @return the schemes map or null. + */ + public Map> getPlayerSchemes(int playerId) + { + final Map> schemes = _schemesTable.get(playerId); + return (schemes != null) ? Collections.unmodifiableMap(schemes) : null; + } + + /** + * Returns the skill ID list for a specific player scheme. + * @param playerId + * @param schemeName + * @return the scheme skill list or an empty list. + */ + public List getScheme(int playerId, String schemeName) + { + final Map> schemes = _schemesTable.get(playerId); + if ((schemes == null) || (schemes.get(schemeName) == null)) + { + return Collections.emptyList(); + } + return schemes.get(schemeName); + } + + /** + * Returns the skill ID list for a category/type without copying.
    + * The returned list is immutable and safe to share across threads. + * @param groupType + * @return the category skill IDs. + */ + public List getSkillsIdsByType(String groupType) + { + return _skillIdsByType.getOrDefault(groupType, Collections.emptyList()); + } + + /** + * Returns available category/type names for scheme editing.
    + * Internal groups (MAGE_GROUP / FIGHTER_GROUP) are hidden from the edit UI. + * @return the list of visible category names. + */ + public List getSkillTypes() + { + final List skillTypes = new ArrayList<>(_skillTypesOrder.size()); + for (String type : _skillTypesOrder) + { + if (type.equalsIgnoreCase(TYPE_MAGE_GROUP) || type.equalsIgnoreCase(TYPE_FIGHTER_GROUP)) + { + continue; + } + skillTypes.add(type); + } + return skillTypes; + } + + /** + * Returns the global holder for a skill ID. + * @param skillId + * @return the global holder or null. + */ + public BuffSkillHolder getAvailableBuff(int skillId) + { + return _availableBuffs.get(skillId); + } + + /** + * Returns the category-aware holder for a skill ID. + * @param groupType + * @param skillId + * @return the resolved holder or null. + */ + public BuffSkillHolder getAvailableBuff(String groupType, int skillId) + { + final Map holdersByType = _availableBuffsByType.get(groupType); + return (holdersByType != null) ? holdersByType.getOrDefault(skillId, _availableBuffs.get(skillId)) : _availableBuffs.get(skillId); + } + + /** + * Returns the singleton instance. + * @return the instance. + */ + public static SchemeBufferTable getInstance() + { + return SingletonHolder.INSTANCE; + } + + private void loadAvailableBuffs() + { + int categoryCount = 0; + int skillCount = 0; + + try + { + final Node rootNode = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new File(SKILLS_XML_PATH)).getDocumentElement(); + for (Node categoryNode = (rootNode != null) ? rootNode.getFirstChild() : null; categoryNode != null; categoryNode = categoryNode.getNextSibling()) + { + if (!"category".equalsIgnoreCase(categoryNode.getNodeName())) + { + continue; + } + + final NamedNodeMap categoryAttributes = categoryNode.getAttributes(); + if (categoryAttributes == null) + { + continue; + } + + final Node typeNode = categoryAttributes.getNamedItem("type"); + if (typeNode == null) + { + continue; + } + + final String category = typeNode.getNodeValue(); + if (!_skillIdsByType.containsKey(category)) + { + _skillIdsByType.put(category, new ArrayList<>()); + _availableBuffsByType.put(category, new LinkedHashMap<>()); + _skillTypesOrder.add(category); + categoryCount++; + } + + for (Node buffNode = categoryNode.getFirstChild(); buffNode != null; buffNode = buffNode.getNextSibling()) + { + if (!"buff".equalsIgnoreCase(buffNode.getNodeName())) + { + continue; + } + + final NamedNodeMap attributes = buffNode.getAttributes(); + if (attributes == null) + { + continue; + } + + final Node idNode = attributes.getNamedItem("id"); + final Node levelNode = attributes.getNamedItem("level"); + final Node priceNode = attributes.getNamedItem("price"); + if ((idNode == null) || (levelNode == null) || (priceNode == null)) + { + continue; + } + + final int skillId = Integer.parseInt(idNode.getNodeValue()); + final int level = Integer.parseInt(levelNode.getNodeValue()); + final int price = Integer.parseInt(priceNode.getNodeValue()); + final Node descNode = attributes.getNamedItem("desc"); + final String description = (descNode != null) ? descNode.getNodeValue() : ""; + + final List skillIds = _skillIdsByType.get(category); + if (!skillIds.contains(skillId)) + { + skillIds.add(skillId); + } + + final BuffSkillHolder holder = new BuffSkillHolder(skillId, level, price, category, description); + _availableBuffsByType.get(category).put(skillId, holder); + + final BuffSkillHolder existing = _availableBuffs.get(skillId); + if ((existing == null) || (holder.getLevel() > existing.getLevel())) + { + _availableBuffs.put(skillId, holder); + } + + skillCount++; + } + } + + for (Entry> entry : _skillIdsByType.entrySet()) + { + entry.setValue(Collections.unmodifiableList(entry.getValue())); + } + + LOGGER.info("SchemeBufferTable: Loaded " + categoryCount + " categories and " + skillCount + " entries."); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "SchemeBufferTable: Failed to load skills XML: " + SKILLS_XML_PATH + ".", e); + } + } + + private void loadPlayerSchemes() + { + int entryCount = 0; + + try (Connection connection = DatabaseFactory.getConnection(); + PreparedStatement statement = connection.prepareStatement(LOAD_SCHEMES); + ResultSet resultSet = statement.executeQuery()) + { + while (resultSet.next()) + { + final String schemeName = resultSet.getString(DB_COLUMN_SCHEME_NAME); + final String skills = resultSet.getString(DB_COLUMN_SKILLS); + if ((schemeName == null) || (skills == null)) + { + continue; + } + + final int objectId = resultSet.getInt(DB_COLUMN_OBJECT_ID); + final String[] split = skills.split(SKILL_SEPARATOR); + final List schemeSkillIds = new ArrayList<>(split.length); + + for (String token : split) + { + if (token.isEmpty()) + { + continue; + } + + final int skillId; + try + { + skillId = Integer.parseInt(token); + } + catch (NumberFormatException e) + { + LOGGER.warning("SchemeBufferTable: Invalid skill id '" + token + "' for scheme '" + schemeName + "' (objectId: " + objectId + ")."); + continue; + } + + if (_availableBuffs.containsKey(skillId)) + { + schemeSkillIds.add(skillId); + } + } + + setScheme(objectId, schemeName, schemeSkillIds); + entryCount++; + } + + LOGGER.info("SchemeBufferTable: Loaded " + entryCount + " scheme entries from database."); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "SchemeBufferTable: Failed to load buff schemes from database.", e); + } + } + + private static class SingletonHolder + { + protected static final SchemeBufferTable INSTANCE = new SchemeBufferTable(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/SpawnGroupTable.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/SpawnGroupTable.java new file mode 100644 index 0000000..65b141b --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/SpawnGroupTable.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.data; + +import java.util.Collection; +import java.util.concurrent.CopyOnWriteArrayList; + +import org.l2jmobius.gameserver.entity.spawns.SpawnGroup; + +/** + * Registry of the spawn groups declared by the datapack. + * @author Altur + */ +public class SpawnGroupTable +{ + private final Collection _groups = new CopyOnWriteArrayList<>(); + + protected SpawnGroupTable() + { + } + + public Collection getGroups() + { + return _groups; + } + + public void addGroup(SpawnGroup group) + { + _groups.add(group); + } + + public void clear() + { + _groups.clear(); + } + + /** + * Stops the unit replacement of every group, used by the unspawn admin commands. + */ + public void deactivateAll() + { + for (SpawnGroup group : _groups) + { + group.setActive(false); + } + } + + public static SpawnGroupTable getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final SpawnGroupTable INSTANCE = new SpawnGroupTable(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/SpawnTable.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/SpawnTable.java new file mode 100644 index 0000000..9232286 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/SpawnTable.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.data; + +import java.util.Collections; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import org.l2jmobius.gameserver.entity.spawns.Spawn; + +/** + * @author Mobius + */ +public class SpawnTable +{ + private final Map> _npcSpawns = new ConcurrentHashMap<>(); + + protected SpawnTable() + { + } + + /** + * Retrieves the complete spawn table containing all NPC spawns. + * @return a map where keys are NPC IDs and values are sets of spawns for those NPCs. + */ + public Map> getSpawnTable() + { + return _npcSpawns; + } + + /** + * Retrieves the set of spawns for the specified NPC ID. + * @param npcId the ID of the NPC to get spawns for. + * @return a set of spawns for the given NPC ID, or an empty set if no spawns are present. + */ + public Set getSpawns(int npcId) + { + return _npcSpawns.getOrDefault(npcId, Collections.emptySet()); + } + + /** + * Gets the count of spawns for the specified NPC ID. + * @param npcId the ID of the NPC to get the spawn count for. + * @return the number of spawns associated with the given NPC ID. + */ + public int getSpawnCount(int npcId) + { + return getSpawns(npcId).size(); + } + + /** + * Retrieves any spawn for the specified NPC ID. + * @param npcId the ID of the NPC to get a spawn for. + * @return a spawn associated with the given NPC ID, or {@code null} if none are present. + */ + public Spawn getAnySpawn(int npcId) + { + return getSpawns(npcId).stream().findFirst().orElse(null); + } + + /** + * Adds a spawn to the spawn table.
    + * If the spawn set for the specified NPC ID does not exist, a new set is created and the spawn is added to it. + * @param spawn the NPC spawn to add. + */ + public void addSpawn(Spawn spawn) + { + _npcSpawns.computeIfAbsent(spawn.getId(), _ -> ConcurrentHashMap.newKeySet(1)).add(spawn); + } + + /** + * Removes a spawn from the spawn table.
    + * If the spawn set becomes empty after removal, the set itself is also removed from the table. + * @param spawn the NPC spawn to remove. + */ + public void removeSpawn(Spawn spawn) + { + final Set set = _npcSpawns.get(spawn.getId()); + if (set != null) + { + set.remove(spawn); + if (set.isEmpty()) + { + _npcSpawns.remove(spawn.getId()); + } + } + } + + public static SpawnTable getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final SpawnTable INSTANCE = new SpawnTable(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/enums/AbsorbCrystalType.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/enums/AbsorbCrystalType.java new file mode 100644 index 0000000..2b577d3 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/enums/AbsorbCrystalType.java @@ -0,0 +1,29 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.data.enums; + +/** + * Learning skill types. + * @author Zoey76 + */ +public enum AbsorbCrystalType +{ + LAST_HIT, + FULL_PARTY, + PARTY_ONE_RANDOM, + PARTY_RANDOM +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/enums/CategoryType.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/enums/CategoryType.java new file mode 100644 index 0000000..80225c5 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/enums/CategoryType.java @@ -0,0 +1,203 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.data.enums; + +/** + * This class defines all category types. + * @author xban1x + */ +public enum CategoryType +{ + FIGHTER_GROUP, + MAGE_GROUP, + WIZARD_GROUP, + CLERIC_GROUP, + ATTACKER_GROUP, + TANKER_GROUP, + FIRST_CLASS_GROUP, + SECOND_CLASS_GROUP, + THIRD_CLASS_GROUP, + FOURTH_CLASS_GROUP, + BOUNTY_HUNTER_GROUP, + WARSMITH_GROUP, + SUMMON_NPC_GROUP, + KNIGHT_GROUP, + WHITE_MAGIC_GROUP, + HEAL_GROUP, + ASSIST_MAGIC_GROUP, + WARRIOR_GROUP, + HUMAN_2ND_GROUP, + ELF_2ND_GROUP, + DELF_2ND_GROUP, + ORC_2ND_GROUP, + DWARF_2ND_GROUP, + STRIDER, + STRIDER_GROUP, + HATCHLING_GROUP, + BABY_PET_GROUP, + WYVERN_GROUP, + WOLF, + SIN_EATER_GROUP, + PET_GROUP, + ITEM_EQUIP_PET_GROUP, + SUBJOB_GROUP_DAGGER, + SUBJOB_GROUP_BOW, + SUBJOB_GROUP_KNIGHT, + SUBJOB_GROUP_SUMMONER, + SUBJOB_GROUP_HALF_HEALER, + SUBJOB_GROUP_DANCE, + SUBJOB_GROUP_WIZARD, + HUMAN_FALL_CLASS, + HUMAN_WALL_CLASS, + HUMAN_MALL_CLASS, + HUMAN_CALL_CLASS, + ELF_FALL_CLASS, + ELF_MALL_CLASS, + ELF_WALL_CLASS, + ELF_CALL_CLASS, + DELF_FALL_CLASS, + DELF_MALL_CLASS, + DELF_WALL_CLASS, + DELF_CALL_CLASS, + ORC_FALL_CLASS, + ORC_MALL_CLASS, + DWARF_ALL_CLASS, + DWARF_BOUNTY_CLASS, + DWARF_SMITH_CLASS, + BEGINNER_FIGHTER, + BEGINNER_MAGE, + ARCHER_GROUP, + SHIELD_MASTER, + BARD, + FORCE_MASTER, + WEAPON_MASTER, + BOW_MASTER, + DAGGER_MASTER, + HEAL_MASTER, + WIZARD_MASTER, + BUFF_MASTER, + SUMMON_MASTER, + WARRIOR_CLOACK, + ROGUE_CLOACK, + MAGE_CLOACK, + SHIELD_MASTER2_3, + BARD2_3, + FORCE_MASTER2_3, + WEAPON_MASTER2_3, + BOW_MASTER2_3, + DAGGER_MASTER2_3, + HEAL_MASTER2_3, + WIZARD_MASTER2_3, + BUFF_MASTER2_3, + SUMMON_MASTER2_3, + ATTRIBUTE_GROUP_SUMMONER, + SUB_GROUP_WARRIOR, + SUB_GROUP_ROGUE, + SUB_GROUP_KNIGHT, + SUB_GROUP_SUMMONER, + SUB_GROUP_WIZARD, + SUB_GROUP_HEALER, + SUB_GROUP_ENCHANTER, + SUB_GROUP_HEC, + SUB_GROUP_HEW, + SUB_GROUP_HEF, + SUB_GROUP_ORC, + SUB_GROUP_WARE, + SUB_GROUP_BLACK, + SUB_GROUP_DE, + LIGHT_TANKER_GROUP, + DARK_TANKER_GROUP, + MELEE_ATTACKER, + RECOM_KNIGHT_GROUP, + RECOM_MAGIC_GROUP, + RECOM_WARRIOR_GROUP, + RECOM_ROGUE_GROUP, + RECOM_ORCF_GROUP, + RECOM_ORCM_GROUP, + TANKER_CATEGORY, + WARRIOR_CATEGORY, + ROGUE_CATEGORY, + ARCHER_CATEGORY, + WIZARD_CATEGORY, + SUMMONER_CATEGORY, + HEALER_CATEGORY, + APM_ZERO_WARRIOR_GROUP, + APM_ZERO_MAGE_GROUP, + APM_1ST_KNIGHT_GROUP, + APM_1ST_WARRIOR_GROUP, + APM_1ST_WIZARD_GROUP, + APM_1ST_HEALER_GROUP, + APM_2ND_KNIGHT_GROUP, + APM_2ND_WARRIOR_GROUP, + APM_2ND_ROGUE_GROUP, + APM_2ND_WIZARD_GROUP, + APM_2ND_ARCHER_GROUP, + APM_2ND_BUFFER_GROUP, + APM_2ND_SUMMONER_GROUP, + APM_2ND_ONLY_GROUP, + APM_3RD_KNIGHT_GROUP, + APM_3RD_WARRIOR_GROUP, + APM_3RD_ROGUE_GROUP, + APM_3RD_WIZARD_GROUP, + APM_3RD_ARCHER_GROUP, + APM_3RD_BUFFER_GROUP, + APM_3RD_SUMMONER_GROUP, + APM_3RD_ONLY_GROUP, + SUBCLASS_TANKER, + SUBCLASS_WARRIOR, + SUBCLASS_ROUGE, + SUBCLASS_ARCHER, + SUBCLASS_WIZARD, + SUBCLASS_SUMMON, + SUBCLASS_HEAL, + SUBCLASS_ENCHANTER, + SUBCLASS_SPEACIAL, + DIVISION_TANKER, + DIVISION_WARRIOR, + DIVISION_ROUGE, + DIVISION_ARCHER, + DIVISION_WIZARD, + DIVISION_SUMMON, + DIVISION_ENCHANTER, + DIVISION_HEAL, + HUMAN_FOURTH_CLASS_GROUP, + ELF_FOURTH_CLASS_GROUP, + DARKELF_FOURTH_CLASS_GROUP, + ORC_FOURTH_CLASS_GROUP, + DWARF_FOURTH_CLASS_GROUP, + HEAVY_ARMOR_CLASS, + LIGHT_ARMOR_CLASS, + ROBE_CLASS; + + /** + * Finds category by it's name + * @param categoryName + * @return A {@code CategoryType} if category was found, {@code null} if category was not found + */ + public static CategoryType findByName(String categoryName) + { + for (CategoryType type : values()) + { + if (type.name().equalsIgnoreCase(categoryName)) + { + return type; + } + } + + return null; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/enums/StatType.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/enums/StatType.java new file mode 100644 index 0000000..dbc29b6 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/enums/StatType.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.data.enums; + +public enum StatType +{ + HP, + MP, + XP, + SP, + GIM // grab item modifier: + + // GIM: the default function uses only the skilllevel to determine + // how many item is grabbed in each step + // with this stat changer you can multiple this +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/holders/AccessLevel.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/holders/AccessLevel.java new file mode 100644 index 0000000..45e5f82 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/holders/AccessLevel.java @@ -0,0 +1,217 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.data.holders; + +import org.l2jmobius.gameserver.data.xml.AdminData; +import org.l2jmobius.gameserver.util.StatSet; + +/** + * @author HorridoJoho + */ +public class AccessLevel +{ + /** The access level. */ + private int _accessLevel = 0; + /** The access level name. */ + private String _name = null; + /** Child access levels. */ + AccessLevel _childsAccessLevel = null; + /** Child access levels. */ + private int _child = 0; + /** The name color for the access level. */ + private int _nameColor = 0; + /** The title color for the access level. */ + private int _titleColor = 0; + /** Flag to determine if the access level has GM access. */ + private boolean _isGm = false; + /** Flag for peace zone attack */ + private boolean _allowPeaceAttack = false; + /** Flag for fixed res */ + private boolean _allowFixedRes = false; + /** Flag for transactions */ + private boolean _allowTransaction = false; + /** Flag for AltG commands */ + private boolean _allowAltG = false; + /** Flag to give damage */ + private boolean _giveDamage = false; + /** Flag to take aggro */ + private boolean _takeAggro = false; + /** Flag to gain exp in party */ + private boolean _gainExp = false; + + public AccessLevel(StatSet set) + { + _accessLevel = set.getInt("level"); + _name = set.getString("name"); + _nameColor = Integer.decode("0x" + set.getString("nameColor", "FFFFFF")); + _titleColor = Integer.decode("0x" + set.getString("titleColor", "FFFFFF")); + _child = set.getInt("childAccess", 0); + _isGm = set.getBoolean("isGM", false); + _allowPeaceAttack = set.getBoolean("allowPeaceAttack", false); + _allowFixedRes = set.getBoolean("allowFixedRes", false); + _allowTransaction = set.getBoolean("allowTransaction", true); + _allowAltG = set.getBoolean("allowAltg", false); + _giveDamage = set.getBoolean("giveDamage", true); + _takeAggro = set.getBoolean("takeAggro", true); + _gainExp = set.getBoolean("gainExp", true); + } + + public AccessLevel() + { + _accessLevel = 0; + _name = "User"; + _nameColor = Integer.decode("0xFFFFFF"); + _titleColor = Integer.decode("0xFFFFFF"); + _child = 0; + _isGm = false; + _allowPeaceAttack = false; + _allowFixedRes = false; + _allowTransaction = true; + _allowAltG = false; + _giveDamage = true; + _takeAggro = true; + _gainExp = true; + } + + /** + * Returns the access level + * @return int: access level + */ + public int getLevel() + { + return _accessLevel; + } + + /** + * Returns the access level name + * @return String: access level name + */ + public String getName() + { + return _name; + } + + /** + * Returns the name color of the access level + * @return int: the name color for the access level + */ + public int getNameColor() + { + return _nameColor; + } + + /** + * Returns the title color color of the access level + * @return int: the title color for the access level + */ + public int getTitleColor() + { + return _titleColor; + } + + /** + * Retuns if the access level has GM access or not + * @return boolean: true if access level have GM access, otherwise false + */ + public boolean isGm() + { + return _isGm; + } + + /** + * Returns if the access level is allowed to attack in peace zone or not + * @return boolean: true if the access level is allowed to attack in peace zone, otherwise false + */ + public boolean allowPeaceAttack() + { + return _allowPeaceAttack; + } + + /** + * Retruns if the access level is allowed to use fixed res or not + * @return true if the access level is allowed to use fixed res, otherwise false + */ + public boolean allowFixedRes() + { + return _allowFixedRes; + } + + /** + * Returns if the access level is allowed to perform transactions or not + * @return boolean: true if access level is allowed to perform transactions, otherwise false + */ + public boolean allowTransaction() + { + return _allowTransaction; + } + + /** + * Returns if the access level is allowed to use AltG commands or not + * @return boolean: true if access level is allowed to use AltG commands, otherwise false + */ + public boolean allowAltG() + { + return _allowAltG; + } + + /** + * Returns if the access level can give damage or not + * @return boolean: true if the access level can give damage, otherwise false + */ + public boolean canGiveDamage() + { + return _giveDamage; + } + + /** + * Returns if the access level can take aggro or not + * @return boolean: true if the access level can take aggro, otherwise false + */ + public boolean canTakeAggro() + { + return _takeAggro; + } + + /** + * Returns if the access level can gain exp or not + * @return boolean: true if the access level can gain exp, otherwise false + */ + public boolean canGainExp() + { + return _gainExp; + } + + /** + * Returns if the access level contains allowedAccess as child + * @param accessLevel as AccessLevel + * @return boolean: true if a child access level is equals to allowedAccess, otherwise false + */ + public boolean hasChildAccess(AccessLevel accessLevel) + { + if (_childsAccessLevel == null) + { + if (_child <= 0) + { + return false; + } + + _childsAccessLevel = AdminData.getInstance().getAccessLevel(_child); + } + + return (_childsAccessLevel != null) && ((_childsAccessLevel.getLevel() == accessLevel.getLevel()) || _childsAccessLevel.hasChildAccess(accessLevel)); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/holders/AccessRight.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/holders/AccessRight.java new file mode 100644 index 0000000..b9cb141 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/holders/AccessRight.java @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.data.holders; + +import org.l2jmobius.gameserver.data.xml.AdminData; +import org.l2jmobius.gameserver.util.StatSet; + +/** + * @author HorridoJoho, Mobius + */ +public class AccessRight +{ + private final String _command; + private final String _description; + private final int _level; + private final boolean _confirmDlg; + + /** + * Initializes the access right for an admin command using a StatSet configuration. + * @param set the StatSet containing configuration for the admin command + */ + public AccessRight(StatSet set) + { + _command = "admin_" + set.getString("command"); + _description = set.getString("description", ""); + _confirmDlg = set.getBoolean("confirmDlg", false); + _level = set.getInt("accessLevel", 100); + } + + /** + * Initializes the access right for an admin command with specific values. + * @param command the command name (with "admin_" prefix) + * @param description the description associated with this command + * @param confirm whether the command requires confirmation before execution + * @param level the access level required to execute the command + */ + public AccessRight(String command, String description, boolean confirm, int level) + { + _command = command; + _description = description; + _confirmDlg = confirm; + _level = level; + } + + /** + * Gets the command associated with this access right. + * @return the full command string, prefixed with "admin_" + */ + public String getCommand() + { + return _command; + } + + /** + * Gets the description associated with this command. + * @return the description string + */ + public String getDescription() + { + return _description; + } + + /** + * Checks whether the admin command requires confirmation before execution. + * @return {@code true} if confirmation is required, {@code false} otherwise + */ + public boolean requireConfirm() + { + return _confirmDlg; + } + + /** + * Checks whether the provided access level grants permission to use the admin command. + * @param playerAccessLevel the access level of the player attempting to use the command + * @return {@code true} if the character's access level matches or exceeds the required access level, {@code false} otherwise + */ + public boolean hasAccess(AccessLevel playerAccessLevel) + { + // Check if the required access level is null. + final AccessLevel accessLevel = AdminData.getInstance().getAccessLevel(_level); + if (accessLevel == null) + { + return false; + } + + // Check if the character's access level matches the required access level. + if (accessLevel.getLevel() == playerAccessLevel.getLevel()) + { + return true; + } + + // Check if the character's access level has child access to the required access level. + if (playerAccessLevel.hasChildAccess(accessLevel)) + { + return true; + } + + // If none of the above conditions are met, return false. + return false; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/holders/ArmorSet.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/holders/ArmorSet.java new file mode 100644 index 0000000..535fa62 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/holders/ArmorSet.java @@ -0,0 +1,349 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.data.holders; + +import java.util.ArrayList; +import java.util.List; + +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.item.instance.Item; +import org.l2jmobius.gameserver.entity.itemcontainer.Inventory; +import org.l2jmobius.gameserver.mechanics.skill.holders.SkillHolder; + +/** + * @author Luno + */ +public class ArmorSet +{ + private int _chestId; + private final List _legs; + private final List _head; + private final List _gloves; + private final List _feet; + private final List _shield; + + private final List _skills; + private final List _shieldSkills; + private final List _enchant6Skill; + + private int _con; + private int _dex; + private int _str; + private int _men; + private int _wit; + private int _int; + + public ArmorSet() + { + _legs = new ArrayList<>(); + _head = new ArrayList<>(); + _gloves = new ArrayList<>(); + _feet = new ArrayList<>(); + _shield = new ArrayList<>(); + _skills = new ArrayList<>(); + _shieldSkills = new ArrayList<>(); + _enchant6Skill = new ArrayList<>(); + } + + public void addChest(int id) + { + _chestId = id; + } + + public void addLegs(int id) + { + _legs.add(id); + } + + public void addHead(int id) + { + _head.add(id); + } + + public void addGloves(int id) + { + _gloves.add(id); + } + + public void addFeet(int id) + { + _feet.add(id); + } + + public void addShield(int id) + { + _shield.add(id); + } + + public void addSkill(SkillHolder holder) + { + _skills.add(holder); + } + + public void addShieldSkill(SkillHolder holder) + { + _shieldSkills.add(holder); + } + + public void addEnchant6Skill(SkillHolder holder) + { + _enchant6Skill.add(holder); + } + + public void addCon(int value) + { + _con = value; + } + + public void addDex(int value) + { + _dex = value; + } + + public void addStr(int value) + { + _str = value; + } + + public void addMen(int value) + { + _men = value; + } + + public void addWit(int value) + { + _wit = value; + } + + public void addInt(int value) + { + _int = value; + } + + /** + * Checks if player have equipped all items from set (not checking shield) + * @param player whose inventory is being checked + * @return True if player equips whole set + */ + public boolean containAll(Player player) + { + final Inventory inv = player.getInventory(); + final Item legsItem = inv.getPaperdollItem(Inventory.PAPERDOLL_LEGS); + final Item headItem = inv.getPaperdollItem(Inventory.PAPERDOLL_HEAD); + final Item glovesItem = inv.getPaperdollItem(Inventory.PAPERDOLL_GLOVES); + final Item feetItem = inv.getPaperdollItem(Inventory.PAPERDOLL_FEET); + int legs = 0; + int head = 0; + int gloves = 0; + int feet = 0; + if (legsItem != null) + { + legs = legsItem.getId(); + } + + if (headItem != null) + { + head = headItem.getId(); + } + + if (glovesItem != null) + { + gloves = glovesItem.getId(); + } + + if (feetItem != null) + { + feet = feetItem.getId(); + } + + return containAll(_chestId, legs, head, gloves, feet); + } + + public boolean containAll(int chest, int legs, int head, int gloves, int feet) + { + if ((_chestId != 0) && (_chestId != chest)) + { + return false; + } + + if (!_legs.isEmpty() && !_legs.contains(legs)) + { + return false; + } + + if (!_head.isEmpty() && !_head.contains(head)) + { + return false; + } + + if (!_gloves.isEmpty() && !_gloves.contains(gloves)) + { + return false; + } + + if (!_feet.isEmpty() && !_feet.contains(feet)) + { + return false; + } + + return true; + } + + public boolean containItem(int slot, int itemId) + { + switch (slot) + { + case Inventory.PAPERDOLL_CHEST: + { + return _chestId == itemId; + } + case Inventory.PAPERDOLL_LEGS: + { + return _legs.contains(itemId); + } + case Inventory.PAPERDOLL_HEAD: + { + return _head.contains(itemId); + } + case Inventory.PAPERDOLL_GLOVES: + { + return _gloves.contains(itemId); + } + case Inventory.PAPERDOLL_FEET: + { + return _feet.contains(itemId); + } + default: + { + return false; + } + } + } + + public int getChestId() + { + return _chestId; + } + + public List getSkills() + { + return _skills; + } + + public boolean containShield(Player player) + { + final Inventory inv = player.getInventory(); + final Item shieldItem = inv.getPaperdollItem(Inventory.PAPERDOLL_LHAND); + return ((shieldItem != null) && _shield.contains(shieldItem.getId())); + } + + public boolean containShield(int shieldId) + { + if (_shield.isEmpty()) + { + return false; + } + + return _shield.contains(shieldId); + } + + public List getShieldSkillId() + { + return _shieldSkills; + } + + public List getEnchant6skillId() + { + return _enchant6Skill; + } + + /** + * @param player + * @return true if all parts of set are enchanted to +6 or more + */ + public boolean isEnchanted6(Player player) + { + // Player don't have full set. + if (!containAll(player)) + { + return false; + } + + final Inventory inv = player.getInventory(); + final Item chestItem = inv.getPaperdollItem(Inventory.PAPERDOLL_CHEST); + if ((chestItem == null) || (chestItem.getEnchantLevel() < 6)) + { + return false; + } + + final Item legsItem = inv.getPaperdollItem(Inventory.PAPERDOLL_LEGS); + if (!_legs.isEmpty() && ((legsItem == null) || (legsItem.getEnchantLevel() < 6))) + { + return false; + } + + final Item glovesItem = inv.getPaperdollItem(Inventory.PAPERDOLL_GLOVES); + if (!_gloves.isEmpty() && ((glovesItem == null) || (glovesItem.getEnchantLevel() < 6))) + { + return false; + } + + final Item headItem = inv.getPaperdollItem(Inventory.PAPERDOLL_HEAD); + if (!_head.isEmpty() && ((headItem == null) || (headItem.getEnchantLevel() < 6))) + { + return false; + } + + final Item feetItem = inv.getPaperdollItem(Inventory.PAPERDOLL_FEET); + if (!_feet.isEmpty() && ((feetItem == null) || (feetItem.getEnchantLevel() < 6))) + { + return false; + } + + return true; + } + + public int getCON() + { + return _con; + } + + public int getDEX() + { + return _dex; + } + + public int getSTR() + { + return _str; + } + + public int getMEN() + { + return _men; + } + + public int getWIT() + { + return _wit; + } + + public int getINT() + { + return _int; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/holders/FakePlayerChatHolder.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/holders/FakePlayerChatHolder.java new file mode 100644 index 0000000..0dfcfee --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/holders/FakePlayerChatHolder.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.data.holders; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * @author Mobius + */ +public class FakePlayerChatHolder +{ + private final String _fpcName; + private final String _searchMethod; + private final List _searchText; + private final List _answers; + + public FakePlayerChatHolder(String fpcName, String searchMethod, String searchText, String answers) + { + _fpcName = fpcName; + _searchMethod = searchMethod; + _searchText = new ArrayList<>(Arrays.asList(searchText.split(";"))); + _answers = new ArrayList<>(Arrays.asList(answers.split(";"))); + } + + public String getFpcName() + { + return _fpcName; + } + + public String getSearchMethod() + { + return _searchMethod; + } + + public List getSearchText() + { + return _searchText; + } + + public List getAnswers() + { + return _answers; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/holders/InstanceReenterTimeHolder.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/holders/InstanceReenterTimeHolder.java new file mode 100644 index 0000000..f9b2d4e --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/holders/InstanceReenterTimeHolder.java @@ -0,0 +1,67 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.data.holders; + +import java.time.DayOfWeek; + +/** + * Simple class for storing Reenter Data for Instances. + * @author FallenAngel + */ +public class InstanceReenterTimeHolder +{ + private final DayOfWeek _day; + private final int _hour; + private final int _minute; + private final long _time; + + public InstanceReenterTimeHolder(long time) + { + _time = time; + _day = null; + _hour = -1; + _minute = -1; + } + + public InstanceReenterTimeHolder(DayOfWeek day, int hour, int minute) + { + _time = -1; + _day = day; + _hour = hour; + _minute = minute; + } + + public long getTime() + { + return _time; + } + + public DayOfWeek getDay() + { + return _day; + } + + public int getHour() + { + return _hour; + } + + public int getMinute() + { + return _minute; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/holders/LevelingSoulCrystalInfo.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/holders/LevelingSoulCrystalInfo.java new file mode 100644 index 0000000..e9d638b --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/holders/LevelingSoulCrystalInfo.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.data.holders; + +import org.l2jmobius.gameserver.data.enums.AbsorbCrystalType; + +public final class LevelingSoulCrystalInfo +{ + private final AbsorbCrystalType _absorbCrystalType; + private final boolean _isSkillNeeded; + private final int _chance; + + public LevelingSoulCrystalInfo(AbsorbCrystalType absorbCrystalType, boolean isSkillNeeded, int chance) + { + _absorbCrystalType = absorbCrystalType; + _isSkillNeeded = isSkillNeeded; + _chance = chance; + } + + public AbsorbCrystalType getAbsorbCrystalType() + { + return _absorbCrystalType; + } + + public int getChance() + { + return _chance; + } + + public boolean isSkillNeeded() + { + return _isSkillNeeded; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/holders/NpcRoutesHolder.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/holders/NpcRoutesHolder.java new file mode 100644 index 0000000..eeacb00 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/holders/NpcRoutesHolder.java @@ -0,0 +1,72 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.data.holders; + +import java.util.HashMap; +import java.util.Map; + +import org.l2jmobius.gameserver.entity.Location; +import org.l2jmobius.gameserver.entity.actor.Npc; +import org.l2jmobius.gameserver.interfaces.ILocational; + +/** + * Holds depending between NPC's spawn point and route + * @author GKR + */ +public class NpcRoutesHolder +{ + private final Map _correspondences; + + public NpcRoutesHolder() + { + _correspondences = new HashMap<>(); + } + + /** + * Add correspondence between specific route and specific spawn point + * @param routeName name of route + * @param loc Location of spawn point + */ + public void addRoute(String routeName, Location loc) + { + _correspondences.put(getUniqueKey(loc), routeName); + } + + /** + * @param npc + * @return route name for given NPC. + */ + public String getRouteName(Npc npc) + { + if (npc.getSpawn() != null) + { + final String key = getUniqueKey(npc.getSpawn().getLocation()); + return _correspondences.getOrDefault(key, ""); + } + + return ""; + } + + /** + * @param loc + * @return unique text string for given Location. + */ + private String getUniqueKey(ILocational loc) + { + return loc.getX() + "-" + loc.getY() + "-" + loc.getZ(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/holders/PetData.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/holders/PetData.java new file mode 100644 index 0000000..bb3965a --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/holders/PetData.java @@ -0,0 +1,274 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.data.holders; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.l2jmobius.gameserver.data.xml.SkillData; +import org.l2jmobius.gameserver.mechanics.skill.holders.SkillHolder; + +/** + * Class hold information about basic pet stats which are same on each level. + * @author JIV + */ +public class PetData +{ + private final Map _levelStats = new HashMap<>(); + private final List _skills = new ArrayList<>(); + + private final int _npcId; + private final int _itemId; + private int _load = 20000; + private int _hungryLimit = 1; + private int _minLevel = Byte.MAX_VALUE; + private int _maxLevel = 0; + private boolean _syncLevel = false; + private final Set _food = new HashSet<>(); + + public PetData(int npcId, int itemId) + { + _npcId = npcId; + _itemId = itemId; + } + + /** + * @return the npc id representing this pet. + */ + public int getNpcId() + { + return _npcId; + } + + /** + * @return the item id that could summon this pet. + */ + public int getItemId() + { + return _itemId; + } + + /** + * @param level the pet's level. + * @param data the pet's data. + */ + public void addNewStat(int level, PetLevelData data) + { + if (_minLevel > level) + { + _minLevel = level; + } + + if (_maxLevel < level) + { + _maxLevel = level; + } + + _levelStats.put(level, data); + } + + /** + * @param petLevel the pet's level. + * @return the pet data associated to that pet level. + */ + public PetLevelData getPetLevelData(int petLevel) + { + return _levelStats.get(petLevel); + } + + /** + * @return the pet's weight load. + */ + public int getLoad() + { + return _load; + } + + /** + * @return the pet's hunger limit. + */ + public int getHungryLimit() + { + return _hungryLimit; + } + + /** + * @return {@code true} if pet synchronizes it's level with his master's + */ + public boolean isSynchLevel() + { + return _syncLevel; + } + + /** + * @return the pet's minimum level. + */ + public int getMinLevel() + { + return _minLevel; + } + + /** + * @return the pet's maximum level. + */ + public int getMaxLevel() + { + return _maxLevel; + } + + /** + * @return the pet's food list. + */ + public Set getFood() + { + return _food; + } + + /** + * @param foodId the pet's food Id to add. + */ + public void addFood(Integer foodId) + { + _food.add(foodId); + } + + /** + * @param load the weight load to set. + */ + public void setLoad(int load) + { + _load = load; + } + + /** + * @param limit the hunger limit to set. + */ + public void setHungryLimit(int limit) + { + _hungryLimit = limit; + } + + /** + * @param value synchronizes level with master or not. + */ + public void setSyncLevel(boolean value) + { + _syncLevel = value; + } + + // SKILS + + /** + * @param skillId the skill Id to add. + * @param skillLevel the skill level. + * @param petLvl the pet's level when this skill is available. + */ + public void addNewSkill(int skillId, int skillLevel, int petLvl) + { + _skills.add(new PetSkillLearn(skillId, skillLevel, petLvl)); + } + + /** + * @param skillId the skill Id. + * @param petLvl the pet level. + * @return the level of the skill for the given skill Id and pet level. + */ + public int getAvailableLevel(int skillId, int petLvl) + { + int lvl = 0; + boolean found = false; + for (PetSkillLearn temp : _skills) + { + if (temp.getSkillId() != skillId) + { + continue; + } + + found = true; + if (temp.getSkillLevel() == 0) + { + if (petLvl < 70) + { + lvl = (petLvl / 10); + if (lvl <= 0) + { + lvl = 1; + } + } + else + { + lvl = (7 + ((petLvl - 70) / 5)); + } + + // Formula usable for skill that have 10 or more skill levels. + final int maxLevel = SkillData.getInstance().getMaxLevel(temp.getSkillId()); + if (lvl > maxLevel) + { + lvl = maxLevel; + } + break; + } + else if ((temp.getMinLevel() <= petLvl) && (temp.getSkillLevel() > lvl)) + { + lvl = temp.getSkillLevel(); + } + } + + if (found && (lvl == 0)) + { + return 1; + } + + return lvl; + } + + /** + * @return the list with the pet's skill data. + */ + public List getAvailableSkills() + { + return _skills; + } + + public static class PetSkillLearn extends SkillHolder + { + private final int _minLevel; + + /** + * @param id the skill Id. + * @param lvl the skill level. + * @param minLevel the minimum level when this skill is available. + */ + public PetSkillLearn(int id, int lvl, int minLevel) + { + super(id, lvl); + _minLevel = minLevel; + } + + /** + * @return the minimum level for the pet to get the skill. + */ + public int getMinLevel() + { + return _minLevel; + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/holders/PetLevelData.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/holders/PetLevelData.java new file mode 100644 index 0000000..71f268e --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/holders/PetLevelData.java @@ -0,0 +1,231 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.data.holders; + +import org.l2jmobius.gameserver.mechanics.stats.MoveType; +import org.l2jmobius.gameserver.util.StatSet; + +/** + * Stats definition for each pet level. + * @author JIV + */ +public class PetLevelData +{ + private final int _ownerExpTaken; + private final int _petFeedBattle; + private final int _petFeedNormal; + private final float _petMAtk; + private final long _petMaxExp; + private final int _petMaxFeed; + private final float _petMaxHP; + private final float _petMaxMP; + private final float _petMDef; + private final float _petPAtk; + private final float _petPDef; + private final float _petRegenHP; + private final float _petRegenMP; + private final short _petSoulShot; + private final short _petSpiritShot; + private final double _walkSpeedOnRide; + private final double _runSpeedOnRide; + private final double _slowSwimSpeedOnRide; + private final double _fastSwimSpeedOnRide; + private final double _slowFlySpeedOnRide; + private final double _fastFlySpeedOnRide; + + public PetLevelData(StatSet set) + { + _ownerExpTaken = set.getInt("get_exp_type"); + _petMaxExp = (long) set.getDouble("exp"); + _petMaxHP = set.getFloat("org_hp"); + _petMaxMP = set.getFloat("org_mp"); + _petPAtk = set.getFloat("org_pattack"); + _petPDef = set.getFloat("org_pdefend"); + _petMAtk = set.getFloat("org_mattack"); + _petMDef = set.getFloat("org_mdefend"); + _petMaxFeed = set.getInt("max_meal"); + _petFeedBattle = set.getInt("consume_meal_in_battle"); + _petFeedNormal = set.getInt("consume_meal_in_normal"); + _petRegenHP = set.getFloat("org_hp_regen"); + _petRegenMP = set.getFloat("org_mp_regen"); + _petSoulShot = set.getShort("soulshot_count"); + _petSpiritShot = set.getShort("spiritshot_count"); + _walkSpeedOnRide = set.getDouble("walkSpeedOnRide", 0); + _runSpeedOnRide = set.getDouble("runSpeedOnRide", 0); + _slowSwimSpeedOnRide = set.getDouble("slowSwimSpeedOnRide", 0); + _fastSwimSpeedOnRide = set.getDouble("fastSwimSpeedOnRide", 0); + _slowFlySpeedOnRide = set.getDouble("slowFlySpeedOnRide", 0); + _fastFlySpeedOnRide = set.getDouble("fastFlySpeedOnRide", 0); + } + + /** + * @return the owner's experience points consumed by the pet. + */ + public int getOwnerExpTaken() + { + return _ownerExpTaken; + } + + /** + * @return the pet's food consume rate at battle state. + */ + public int getPetFeedBattle() + { + return _petFeedBattle; + } + + /** + * @return the pet's food consume rate at normal state. + */ + public int getPetFeedNormal() + { + return _petFeedNormal; + } + + /** + * @return the pet's Magical Attack. + */ + public float getPetMAtk() + { + return _petMAtk; + } + + /** + * @return the pet's maximum experience points. + */ + public long getPetMaxExp() + { + return _petMaxExp; + } + + /** + * @return the pet's maximum feed points. + */ + public int getPetMaxFeed() + { + return _petMaxFeed; + } + + /** + * @return the pet's maximum HP. + */ + public float getPetMaxHP() + { + return _petMaxHP; + } + + /** + * @return the pet's maximum MP. + */ + public float getPetMaxMP() + { + return _petMaxMP; + } + + /** + * @return the pet's Magical Defense. + */ + public float getPetMDef() + { + return _petMDef; + } + + /** + * @return the pet's Physical Attack. + */ + public float getPetPAtk() + { + return _petPAtk; + } + + /** + * @return the pet's Physical Defense. + */ + public float getPetPDef() + { + return _petPDef; + } + + /** + * @return the pet's HP regeneration rate. + */ + public float getPetRegenHP() + { + return _petRegenHP; + } + + /** + * @return the pet's MP regeneration rate. + */ + public float getPetRegenMP() + { + return _petRegenMP; + } + + /** + * @return the pet's soulshot use count. + */ + public short getPetSoulShot() + { + return _petSoulShot; + } + + /** + * @return the pet's spiritshot use count. + */ + public short getPetSpiritShot() + { + return _petSpiritShot; + } + + /** + * @param mt movement type + * @return the base riding speed of given movement type. + */ + public double getSpeedOnRide(MoveType mt) + { + switch (mt) + { + case WALK: + { + return _walkSpeedOnRide; + } + case RUN: + { + return _runSpeedOnRide; + } + case SLOW_SWIM: + { + return _slowSwimSpeedOnRide; + } + case FAST_SWIM: + { + return _fastSwimSpeedOnRide; + } + case SLOW_FLY: + { + return _slowFlySpeedOnRide; + } + case FAST_FLY: + { + return _fastFlySpeedOnRide; + } + } + + return 0; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/holders/PunishmentHolder.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/holders/PunishmentHolder.java new file mode 100644 index 0000000..363cbb3 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/holders/PunishmentHolder.java @@ -0,0 +1,83 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.data.holders; + +import java.util.Collections; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.l2jmobius.gameserver.mechanics.punishment.PunishmentTask; +import org.l2jmobius.gameserver.mechanics.punishment.PunishmentType; + +/** + * @author UnAfraid + */ +public class PunishmentHolder +{ + private final Map> _holder = new ConcurrentHashMap<>(); + + /** + * Stores the punishment task in the Map. + * @param task + */ + public void addPunishment(PunishmentTask task) + { + if (!task.isExpired()) + { + _holder.computeIfAbsent(String.valueOf(task.getKey()), _ -> new ConcurrentHashMap<>()).put(task.getType(), task); + } + } + + /** + * Removes previously stopped task from the Map. + * @param task + */ + public void stopPunishment(PunishmentTask task) + { + final String key = String.valueOf(task.getKey()); + final Map punishments = _holder.get(key); + if (punishments != null) + { + task.stopPunishment(); + punishments.remove(task.getType()); + if (punishments.isEmpty()) + { + _holder.remove(key); + } + } + } + + /** + * @param key + * @param type + * @return {@code true} if Map contains the current key and type, {@code false} otherwise. + */ + public boolean hasPunishment(String key, PunishmentType type) + { + return getPunishment(key, type) != null; + } + + /** + * @param key + * @param type + * @return {@link PunishmentTask} by specified key and type if exists, null otherwise. + */ + public PunishmentTask getPunishment(String key, PunishmentType type) + { + return _holder.getOrDefault(key, Collections.emptyMap()).get(type); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/holders/RangeChanceHolder.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/holders/RangeChanceHolder.java new file mode 100644 index 0000000..4852443 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/holders/RangeChanceHolder.java @@ -0,0 +1,58 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.data.holders; + +/** + * @author UnAfraid + */ +public class RangeChanceHolder +{ + private final int _min; + private final int _max; + private final double _chance; + + public RangeChanceHolder(int min, int max, double chance) + { + _min = min; + _max = max; + _chance = chance; + } + + /** + * @return minimum value. + */ + public int getMin() + { + return _min; + } + + /** + * @return maximum value. + */ + public int getMax() + { + return _max; + } + + /** + * @return the chance. + */ + public double getChance() + { + return _chance; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/holders/RecipeHolder.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/holders/RecipeHolder.java new file mode 100644 index 0000000..93e15fe --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/holders/RecipeHolder.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.data.holders; + +/** + * This class describes a RecipeList component (1 line of the recipe : Item-Quantity needed). + */ +public class RecipeHolder +{ + /** The Identifier of the item needed in the RecipeHolder */ + private final int _itemId; + + /** The item quantity needed in the RecipeHolder */ + private final int _quantity; + + /** + * Constructor of RecipeHolder (create a new line in a RecipeList). + * @param itemId + * @param quantity + */ + public RecipeHolder(int itemId, int quantity) + { + _itemId = itemId; + _quantity = quantity; + } + + /** + * @return the Identifier of the RecipeHolder Item needed. + */ + public int getItemId() + { + return _itemId; + } + + /** + * @return the Item quantity needed of the RecipeHolder. + */ + public int getQuantity() + { + return _quantity; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/holders/RecipeStatHolder.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/holders/RecipeStatHolder.java new file mode 100644 index 0000000..c6f0638 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/holders/RecipeStatHolder.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.data.holders; + +import org.l2jmobius.gameserver.data.enums.StatType; + +/** + * This class describes a RecipeList statUse and altStatChange component. + */ +public class RecipeStatHolder +{ + /** The Identifier of the statType */ + private final StatType _type; + + /** The value of the statType */ + private final int _value; + + /** + * Constructor of RecipeStatHolder. + * @param type + * @param value + */ + public RecipeStatHolder(String type, int value) + { + _type = Enum.valueOf(StatType.class, type); + _value = value; + } + + /** + * @return the the type of the RecipeStatHolder. + */ + public StatType getType() + { + return _type; + } + + /** + * @return the value of the RecipeStatHolder. + */ + public int getValue() + { + return _value; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/holders/SellBuffHolder.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/holders/SellBuffHolder.java new file mode 100644 index 0000000..bd8da35 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/holders/SellBuffHolder.java @@ -0,0 +1,48 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.data.holders; + +/** + * Simple class for storing info for Selling Buffs system. + * @author St3eT + */ +public class SellBuffHolder +{ + private final int _skillId; + private int _price; + + public SellBuffHolder(int skillId, int price) + { + _skillId = skillId; + _price = price; + } + + public int getSkillId() + { + return _skillId; + } + + public void setPrice(int price) + { + _price = price; + } + + public int getPrice() + { + return _price; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/holders/SoulCrystal.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/holders/SoulCrystal.java new file mode 100644 index 0000000..7d0195f --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/holders/SoulCrystal.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.data.holders; + +public final class SoulCrystal +{ + private final int _level; + private final int _itemId; + private final int _leveledItemId; + + public SoulCrystal(int level, int itemId, int leveledItemId) + { + _level = level; + _itemId = itemId; + _leveledItemId = leveledItemId; + } + + public int getItemId() + { + return _itemId; + } + + public int getLevel() + { + return _level; + } + + public int getLeveledItemId() + { + return _leveledItemId; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/holders/SoulCrystalDisplay.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/holders/SoulCrystalDisplay.java new file mode 100644 index 0000000..901059b --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/holders/SoulCrystalDisplay.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.data.holders; + +import org.l2jmobius.gameserver.data.enums.AbsorbCrystalType; + +public class SoulCrystalDisplay +{ + private Integer _fromLevel = 0; + private Integer _chance = 0; + private Integer _npcLvl = 0; + private final AbsorbCrystalType _absorb; + private final String _name; + + public SoulCrystalDisplay(int fromLevel, int chance, AbsorbCrystalType absorb, int npcLvl, String name) + { + _fromLevel = fromLevel; + _chance = chance; + _absorb = absorb; + _npcLvl = npcLvl; + _name = name; + } + + public Integer getFromLevel() + { + return _fromLevel; + } + + public Integer getChance() + { + return _chance; + } + + public AbsorbCrystalType getAbsorb() + { + return _absorb; + } + + public Integer getLevel() + { + return _npcLvl; + } + + public String getName() + { + return _name; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/holders/StringStringHolder.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/holders/StringStringHolder.java new file mode 100644 index 0000000..3145e0d --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/holders/StringStringHolder.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.data.holders; + +/** + * @author Mobius + */ +public class StringStringHolder +{ + private final String _string1; + private final String _string2; + + public StringStringHolder(String string1, String string2) + { + _string1 = string1; + _string2 = string2; + } + + public String getString1() + { + return _string1; + } + + public String getString2() + { + return _string2; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/sql/AnnouncementsTable.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/sql/AnnouncementsTable.java new file mode 100644 index 0000000..977874a --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/sql/AnnouncementsTable.java @@ -0,0 +1,171 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.data.sql; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.Statement; +import java.util.Collection; +import java.util.Map; +import java.util.concurrent.ConcurrentSkipListMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.commons.database.DatabaseFactory; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.mechanics.announce.Announcement; +import org.l2jmobius.gameserver.mechanics.announce.AnnouncementType; +import org.l2jmobius.gameserver.mechanics.announce.AutoAnnouncement; +import org.l2jmobius.gameserver.mechanics.announce.IAnnouncement; +import org.l2jmobius.gameserver.network.enums.ChatType; +import org.l2jmobius.gameserver.network.serverpackets.CreatureSay; + +/** + * Loads announcements from database. + * @author UnAfraid + */ +public class AnnouncementsTable +{ + private static final Logger LOGGER = Logger.getLogger(AnnouncementsTable.class.getName()); + + private final Map _announcements = new ConcurrentSkipListMap<>(); + + protected AnnouncementsTable() + { + load(); + } + + private void load() + { + _announcements.clear(); + try (Connection con = DatabaseFactory.getConnection(); + Statement st = con.createStatement(); + ResultSet rset = st.executeQuery("SELECT * FROM announcements")) + { + while (rset.next()) + { + final AnnouncementType type = AnnouncementType.findById(rset.getInt("type")); + final Announcement announce; + switch (type) + { + case NORMAL: + case CRITICAL: + { + announce = new Announcement(rset); + break; + } + case AUTO_NORMAL: + case AUTO_CRITICAL: + { + announce = new AutoAnnouncement(rset); + break; + } + default: + { + continue; + } + } + + _announcements.put(announce.getId(), announce); + } + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Failed loading announcements:", e); + } + } + + /** + * Sending all announcements to the player + * @param player + */ + public void showAnnouncements(Player player) + { + sendAnnouncements(player, AnnouncementType.NORMAL); + sendAnnouncements(player, AnnouncementType.CRITICAL); + sendAnnouncements(player, AnnouncementType.EVENT); + } + + /** + * Sends all announcements to the player by the specified type + * @param player + * @param type + */ + private void sendAnnouncements(Player player, AnnouncementType type) + { + for (IAnnouncement announce : _announcements.values()) + { + if (announce.isValid() && (announce.getType() == type)) + { + player.sendPacket(new CreatureSay(null, type == AnnouncementType.CRITICAL ? ChatType.CRITICAL_ANNOUNCE : ChatType.ANNOUNCEMENT, player.getName(), announce.getContent())); + } + } + } + + /** + * Adds announcement + * @param announce + */ + public void addAnnouncement(IAnnouncement announce) + { + if (announce.storeMe()) + { + _announcements.put(announce.getId(), announce); + } + } + + /** + * Removes announcement by id + * @param id + * @return {@code true} if announcement exists and was deleted successfully, {@code false} otherwise. + */ + public boolean deleteAnnouncement(int id) + { + final IAnnouncement announce = _announcements.remove(id); + return (announce != null) && announce.deleteMe(); + } + + /** + * @param id + * @return {@link IAnnouncement} by id + */ + public IAnnouncement getAnnounce(int id) + { + return _announcements.get(id); + } + + /** + * @return {@link Collection} containing all announcements + */ + public Collection getAllAnnouncements() + { + return _announcements.values(); + } + + /** + * @return Single instance of {@link AnnouncementsTable} + */ + public static AnnouncementsTable getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final AnnouncementsTable INSTANCE = new AnnouncementsTable(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/sql/CharInfoTable.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/sql/CharInfoTable.java new file mode 100644 index 0000000..170660f --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/sql/CharInfoTable.java @@ -0,0 +1,254 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.data.sql; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.commons.database.DatabaseFactory; +import org.l2jmobius.gameserver.entity.actor.Player; + +public class CharInfoTable +{ + private static final Logger LOGGER = Logger.getLogger(CharInfoTable.class.getName()); + + private final Map _names = new ConcurrentHashMap<>(); + private final Map _accessLevels = new ConcurrentHashMap<>(); + + protected CharInfoTable() + { + try (Connection con = DatabaseFactory.getConnection(); + Statement s = con.createStatement(); + ResultSet rs = s.executeQuery("SELECT charId, char_name, accesslevel FROM characters")) + { + while (rs.next()) + { + final int id = rs.getInt("charId"); + _names.put(id, rs.getString("char_name")); + _accessLevels.put(id, rs.getInt("accesslevel")); + } + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Couldn't retrieve all char id/name/access: " + e.getMessage(), e); + } + + LOGGER.info(getClass().getSimpleName() + ": Loaded " + _names.size() + " char names."); + } + + public void addName(Player player) + { + if (player != null) + { + addName(player.getObjectId(), player.getName()); + _accessLevels.put(player.getObjectId(), player.getAccessLevel().getLevel()); + } + } + + private void addName(int objectId, String name) + { + if ((name != null) && !name.equals(_names.get(objectId))) + { + _names.put(objectId, name); + } + } + + public void removeName(int objId) + { + _names.remove(objId); + _accessLevels.remove(objId); + } + + public int getIdByName(String name) + { + if ((name == null) || name.isEmpty()) + { + return -1; + } + + for (Entry entry : _names.entrySet()) + { + if (entry.getValue().equalsIgnoreCase(name)) + { + return entry.getKey(); + } + } + + // Should not continue after the above? + + int id = -1; + int accessLevel = 0; + + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement("SELECT charId,accesslevel FROM characters WHERE char_name=?")) + { + ps.setString(1, name); + try (ResultSet rs = ps.executeQuery()) + { + while (rs.next()) + { + id = rs.getInt("charId"); + accessLevel = rs.getInt("accesslevel"); + } + } + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Could not check existing char name: " + e.getMessage(), e); + } + + if (id > 0) + { + _names.put(id, name); + _accessLevels.put(id, accessLevel); + return id; + } + + return -1; // Not found. + } + + public String getNameById(int id) + { + if (id <= 0) + { + return null; + } + + String name = _names.get(id); + if (name != null) + { + return name; + } + + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement("SELECT char_name,accesslevel FROM characters WHERE charId=?")) + { + ps.setInt(1, id); + try (ResultSet rset = ps.executeQuery()) + { + if (rset.next()) + { + name = rset.getString("char_name"); + _names.put(id, name); + _accessLevels.put(id, rset.getInt("accesslevel")); + return name; + } + } + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Could not check existing char id: " + e.getMessage(), e); + } + + return null; // not found + } + + public int getAccessLevelById(int objectId) + { + return getNameById(objectId) != null ? _accessLevels.get(objectId) : 0; + } + + public synchronized boolean doesCharNameExist(String name) + { + boolean result = false; + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement("SELECT COUNT(*) as count FROM characters WHERE char_name=?")) + { + ps.setString(1, name); + try (ResultSet rs = ps.executeQuery()) + { + if (rs.next()) + { + result = rs.getInt("count") > 0; + } + } + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Could not check existing charname: " + e.getMessage(), e); + } + + return result; + } + + public int getAccountCharacterCount(String account) + { + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement("SELECT COUNT(char_name) as count FROM characters WHERE account_name=?")) + { + ps.setString(1, account); + try (ResultSet rset = ps.executeQuery()) + { + if (rset.next()) + { + return rset.getInt("count"); + } + } + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "Couldn't retrieve account for id: " + e.getMessage(), e); + } + + return 0; + } + + public int accountCharNumber(String account) + { + int number = 0; + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement statement = con.prepareStatement("SELECT COUNT(char_name) FROM characters WHERE account_name=?")) + { + statement.setString(1, account); + try (ResultSet rset = statement.executeQuery()) + { + if (rset.next()) + { + number = rset.getInt(1); + } + } + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "Couldn't check existing char number: " + e.getMessage(), e); + } + + return number; + } + + public static CharInfoTable getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final CharInfoTable INSTANCE = new CharInfoTable(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/sql/CharSummonTable.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/sql/CharSummonTable.java new file mode 100644 index 0000000..ad5e362 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/sql/CharSummonTable.java @@ -0,0 +1,252 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.data.sql; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Logger; + +import org.l2jmobius.commons.database.DatabaseFactory; +import org.l2jmobius.gameserver.config.PlayerConfig; +import org.l2jmobius.gameserver.data.holders.PetData; +import org.l2jmobius.gameserver.data.xml.NpcData; +import org.l2jmobius.gameserver.data.xml.PetDataTable; +import org.l2jmobius.gameserver.data.xml.SkillData; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.instance.Pet; +import org.l2jmobius.gameserver.entity.actor.instance.Servitor; +import org.l2jmobius.gameserver.entity.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.entity.item.instance.Item; +import org.l2jmobius.gameserver.mechanics.skill.Skill; +import org.l2jmobius.gameserver.network.serverpackets.PetItemList; + +/** + * @author Nyaran + */ +public class CharSummonTable +{ + private static final Logger LOGGER = Logger.getLogger(CharSummonTable.class.getName()); + private static final Map _pets = new ConcurrentHashMap<>(); + private static final Map _servitors = new ConcurrentHashMap<>(); + + // SQL + private static final String INIT_PET = "SELECT ownerId, item_obj_id FROM pets WHERE restore = 'true'"; + private static final String INIT_SUMMONS = "SELECT ownerId, summonSkillId FROM character_summons"; + private static final String LOAD_SUMMON = "SELECT curHp, curMp, time FROM character_summons WHERE ownerId = ? AND summonSkillId = ?"; + private static final String REMOVE_SUMMON = "DELETE FROM character_summons WHERE ownerId = ?"; + private static final String SAVE_SUMMON = "REPLACE INTO character_summons (ownerId,summonSkillId,curHp,curMp,time) VALUES (?,?,?,?,?)"; + + public Map getPets() + { + return _pets; + } + + public Map getServitors() + { + return _servitors; + } + + public void init() + { + if (PlayerConfig.RESTORE_SERVITOR_ON_RECONNECT) + { + try (Connection con = DatabaseFactory.getConnection(); + Statement s = con.createStatement(); + ResultSet rs = s.executeQuery(INIT_SUMMONS)) + { + while (rs.next()) + { + _servitors.put(rs.getInt("ownerId"), rs.getInt("summonSkillId")); + } + } + catch (Exception e) + { + LOGGER.warning(getClass().getSimpleName() + ": Error while loading saved servitor: " + e); + } + } + + if (PlayerConfig.RESTORE_PET_ON_RECONNECT) + { + try (Connection con = DatabaseFactory.getConnection(); + Statement s = con.createStatement(); + ResultSet rs = s.executeQuery(INIT_PET)) + { + while (rs.next()) + { + _pets.put(rs.getInt("ownerId"), rs.getInt("item_obj_id")); + } + } + catch (Exception e) + { + LOGGER.warning(getClass().getSimpleName() + ": Error while loading saved pet: " + e); + } + } + } + + public void removeServitor(Player player) + { + _servitors.remove(player.getObjectId()); + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement(REMOVE_SUMMON)) + { + ps.setInt(1, player.getObjectId()); + ps.execute(); + } + catch (SQLException e) + { + LOGGER.warning(getClass().getSimpleName() + ": Summon cannot be removed: " + e); + } + } + + public void restorePet(Player player) + { + final Item item = player.getInventory().getItemByObjectId(_pets.get(player.getObjectId())); + if (item == null) + { + LOGGER.warning(getClass().getSimpleName() + ": Null pet summoning item for: " + player); + return; + } + + final PetData petData = PetDataTable.getInstance().getPetDataByItemId(item.getId()); + if (petData == null) + { + LOGGER.warning(getClass().getSimpleName() + ": Null pet data for: " + player + " and summoning item: " + item); + return; + } + + final NpcTemplate npcTemplate = NpcData.getInstance().getTemplate(petData.getNpcId()); + if (npcTemplate == null) + { + LOGGER.warning(getClass().getSimpleName() + ": Null pet NPC template for: " + player + " and pet Id:" + petData.getNpcId()); + return; + } + + final Pet pet = Pet.spawnPet(npcTemplate, player, item); + if (pet == null) + { + LOGGER.warning(getClass().getSimpleName() + ": Null pet instance for: " + player + " and pet NPC template:" + npcTemplate); + return; + } + + pet.setShowSummonAnimation(true); + pet.setTitle(player.getName()); + + if (!pet.isRespawned()) + { + pet.setCurrentHp(pet.getMaxHp()); + pet.setCurrentMp(pet.getMaxMp()); + pet.getStat().setExp(pet.getExpForThisLevel()); + pet.setCurrentFed(pet.getMaxFed()); + } + + pet.setRunning(); + + if (!pet.isRespawned()) + { + pet.storeMe(); + } + + item.setEnchantLevel(pet.getLevel()); + player.setPet(pet); + pet.spawnMe(player.getX() + 50, player.getY() + 100, player.getZ()); + pet.startFeed(); + pet.setFollowStatus(true); + pet.getOwner().sendPacket(new PetItemList(pet.getInventory().getItems())); + pet.broadcastStatusUpdate(); + } + + public void restoreServitor(Player player) + { + final int skillId = _servitors.get(player.getObjectId()); + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement(LOAD_SUMMON)) + { + ps.setInt(1, player.getObjectId()); + ps.setInt(2, skillId); + try (ResultSet rs = ps.executeQuery()) + { + Skill skill; + while (rs.next()) + { + skill = SkillData.getInstance().getSkill(skillId, player.getSkillLevel(skillId)); + if (skill == null) + { + removeServitor(player); + return; + } + + skill.applyEffects(player, player); + + if (player.hasServitor()) + { + final int curHp = rs.getInt("curHp"); + final int curMp = rs.getInt("curMp"); + final int time = rs.getInt("time"); + final Servitor summon = player.getSummon().asServitor(); + summon.setCurrentHp(curHp); + summon.setCurrentMp(curMp); + summon.setLifeTimeRemaining(time); + } + } + } + } + catch (SQLException e) + { + LOGGER.warning(getClass().getSimpleName() + ": Servitor cannot be restored: " + e); + } + } + + public void saveSummon(Servitor summon) + { + if ((summon == null) || (summon.getLifeTimeRemaining() <= 0)) + { + return; + } + + _servitors.put(summon.getOwner().getObjectId(), summon.getReferenceSkill()); + + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement(SAVE_SUMMON)) + { + ps.setInt(1, summon.getOwner().getObjectId()); + ps.setInt(2, summon.getReferenceSkill()); + ps.setInt(3, (int) Math.round(summon.getCurrentHp())); + ps.setInt(4, (int) Math.round(summon.getCurrentMp())); + ps.setInt(5, summon.getLifeTimeRemaining()); + ps.execute(); + } + catch (Exception e) + { + LOGGER.warning(getClass().getSimpleName() + ": Failed to store summon: " + summon + " from " + summon.getOwner() + ", error: " + e); + } + } + + public static CharSummonTable getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final CharSummonTable INSTANCE = new CharSummonTable(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/sql/ClanHallTable.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/sql/ClanHallTable.java new file mode 100644 index 0000000..2b1328e --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/sql/ClanHallTable.java @@ -0,0 +1,317 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.data.sql; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.Statement; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.commons.database.DatabaseFactory; +import org.l2jmobius.gameserver.entity.WorldObject; +import org.l2jmobius.gameserver.entity.clan.Clan; +import org.l2jmobius.gameserver.entity.residences.AuctionableHall; +import org.l2jmobius.gameserver.entity.residences.ClanHall; +import org.l2jmobius.gameserver.entity.zone.type.ClanHallZone; +import org.l2jmobius.gameserver.managers.CHSiegeManager; +import org.l2jmobius.gameserver.managers.ClanHallAuctionManager; +import org.l2jmobius.gameserver.mechanics.siege.clanhalls.SiegableHall; +import org.l2jmobius.gameserver.util.StatSet; + +/** + * @author Steuf + */ +public class ClanHallTable +{ + protected static final Logger LOGGER = Logger.getLogger(ClanHallTable.class.getName()); + + private final Map _clanHall = new ConcurrentHashMap<>(); + private final Map _freeClanHall = new ConcurrentHashMap<>(); + private final Map _allAuctionableClanHalls = new HashMap<>(); + private static Map _allClanHalls = new HashMap<>(); + private boolean _loaded = false; + + public boolean loaded() + { + return _loaded; + } + + protected ClanHallTable() + { + load(); + } + + /** Load All Clan Hall */ + private void load() + { + try (Connection con = DatabaseFactory.getConnection(); + Statement s = con.createStatement(); + ResultSet rs = s.executeQuery("SELECT * FROM clanhall ORDER BY id")) + { + int id; + int ownerId; + int lease; + while (rs.next()) + { + final StatSet set = new StatSet(); + id = rs.getInt("id"); + ownerId = rs.getInt("ownerId"); + lease = rs.getInt("lease"); + set.set("id", id); + set.set("name", rs.getString("name")); + set.set("ownerId", ownerId); + set.set("lease", lease); + set.set("desc", rs.getString("desc")); + set.set("location", rs.getString("location")); + set.set("paidUntil", rs.getLong("paidUntil")); + set.set("grade", rs.getInt("Grade")); + set.set("paid", rs.getBoolean("paid")); + final AuctionableHall ch = new AuctionableHall(set); + _allAuctionableClanHalls.put(id, ch); + addClanHall(ch); + + if (ch.getOwnerId() > 0) + { + _clanHall.put(id, ch); + continue; + } + + _freeClanHall.put(id, ch); + + if ((ClanHallAuctionManager.getInstance().getAuction(id) == null) && (lease > 0)) + { + ClanHallAuctionManager.getInstance().initNPC(id); + } + } + + LOGGER.info(getClass().getSimpleName() + ": Loaded " + _clanHall.size() + " clan halls"); + LOGGER.info(getClass().getSimpleName() + ": Loaded " + _freeClanHall.size() + " free clan halls"); + _loaded = true; + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Exception: ClanHallManager.load(): " + e.getMessage(), e); + } + } + + public static Map getAllClanHalls() + { + return _allClanHalls; + } + + /** + * @return all FreeClanHalls + */ + public Map getFreeClanHalls() + { + return _freeClanHall; + } + + /** + * @return all ClanHalls that have owner + */ + public Map getClanHalls() + { + return _clanHall; + } + + /** + * @return all ClanHalls + */ + public Map getAllAuctionableClanHalls() + { + return _allAuctionableClanHalls; + } + + public static void addClanHall(ClanHall hall) + { + _allClanHalls.put(hall.getId(), hall); + } + + /** + * @param chId + * @return true is free ClanHall + */ + public boolean isFree(int chId) + { + return _freeClanHall.containsKey(chId); + } + + /** + * Free a ClanHall + * @param chId + */ + public synchronized void setFree(int chId) + { + _freeClanHall.put(chId, _clanHall.get(chId)); + ClanTable.getInstance().getClan(_freeClanHall.get(chId).getOwnerId()).setHideoutId(0); + _freeClanHall.get(chId).free(); + _clanHall.remove(chId); + } + + /** + * Set ClanHallOwner + * @param chId + * @param clan + */ + public synchronized void setOwner(int chId, Clan clan) + { + if (!_clanHall.containsKey(chId)) + { + _clanHall.put(chId, _freeClanHall.get(chId)); + _freeClanHall.remove(chId); + } + else + { + _clanHall.get(chId).free(); + } + + ClanTable.getInstance().getClan(clan.getId()).setHideoutId(chId); + _clanHall.get(chId).setOwner(clan); + } + + /** + * @param clanHallId + * @return Clan Hall by Id + */ + public ClanHall getClanHallById(int clanHallId) + { + return _allClanHalls.get(clanHallId); + } + + public AuctionableHall getAuctionableHallById(int clanHallId) + { + return _allAuctionableClanHalls.get(clanHallId); + } + + /** + * @param x + * @param y + * @param z + * @return Clan Hall by x,y,z + */ + public ClanHall getClanHall(int x, int y, int z) + { + for (ClanHall temp : _allClanHalls.values()) + { + if (temp.checkIfInZone(x, y, z)) + { + return temp; + } + } + + return null; + } + + public ClanHall getClanHall(WorldObject activeObject) + { + return getClanHall(activeObject.getX(), activeObject.getY(), activeObject.getZ()); + } + + public AuctionableHall getNearbyClanHall(int x, int y, int maxDist) + { + ClanHallZone zone = null; + for (Entry ch : _clanHall.entrySet()) + { + zone = ch.getValue().getZone(); + if ((zone != null) && (zone.getDistanceToZone(x, y) < maxDist)) + { + return ch.getValue(); + } + } + + for (Entry ch : _freeClanHall.entrySet()) + { + zone = ch.getValue().getZone(); + if ((zone != null) && (zone.getDistanceToZone(x, y) < maxDist)) + { + return ch.getValue(); + } + } + + return null; + } + + public ClanHall getNearbyAbstractHall(int x, int y, int maxDist) + { + ClanHallZone zone = null; + for (Entry ch : _allClanHalls.entrySet()) + { + zone = ch.getValue().getZone(); + if ((zone != null) && (zone.getDistanceToZone(x, y) < maxDist)) + { + return ch.getValue(); + } + } + + return null; + } + + /** + * @param clan + * @return Clan Hall by Owner + */ + public AuctionableHall getClanHallByOwner(Clan clan) + { + for (Entry ch : _clanHall.entrySet()) + { + if (clan.getId() == ch.getValue().getOwnerId()) + { + return ch.getValue(); + } + } + + return null; + } + + public ClanHall getAbstractHallByOwner(Clan clan) + { + // Separate loops to avoid iterating over free clan halls. + for (Entry ch : _clanHall.entrySet()) + { + if (clan.getId() == ch.getValue().getOwnerId()) + { + return ch.getValue(); + } + } + + for (Entry ch : CHSiegeManager.getInstance().getConquerableHalls().entrySet()) + { + if (clan.getId() == ch.getValue().getOwnerId()) + { + return ch.getValue(); + } + } + + return null; + } + + public static ClanHallTable getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final ClanHallTable INSTANCE = new ClanHallTable(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/sql/ClanTable.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/sql/ClanTable.java new file mode 100644 index 0000000..46953f7 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/sql/ClanTable.java @@ -0,0 +1,587 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.data.sql; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.commons.database.DatabaseFactory; +import org.l2jmobius.commons.threads.ThreadPool; +import org.l2jmobius.commons.util.StringUtil; +import org.l2jmobius.gameserver.communitybbs.Manager.ForumsBBSManager; +import org.l2jmobius.gameserver.config.GeneralConfig; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.clan.Clan; +import org.l2jmobius.gameserver.entity.clan.ClanMember; +import org.l2jmobius.gameserver.entity.clan.ClanPrivileges; +import org.l2jmobius.gameserver.entity.item.enums.ItemProcessType; +import org.l2jmobius.gameserver.entity.residences.ClanHallAuction; +import org.l2jmobius.gameserver.managers.CHSiegeManager; +import org.l2jmobius.gameserver.managers.ClanHallAuctionManager; +import org.l2jmobius.gameserver.managers.IdManager; +import org.l2jmobius.gameserver.managers.SiegeManager; +import org.l2jmobius.gameserver.mechanics.events.EventDispatcher; +import org.l2jmobius.gameserver.mechanics.events.EventType; +import org.l2jmobius.gameserver.mechanics.events.holders.actor.player.clan.OnPlayerClanCreate; +import org.l2jmobius.gameserver.mechanics.events.holders.actor.player.clan.OnPlayerClanDestroy; +import org.l2jmobius.gameserver.mechanics.events.holders.clan.OnClanWarFinish; +import org.l2jmobius.gameserver.mechanics.events.holders.clan.OnClanWarStart; +import org.l2jmobius.gameserver.mechanics.siege.Siege; +import org.l2jmobius.gameserver.mechanics.siege.clanhalls.SiegableHall; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.serverpackets.PledgeShowInfoUpdate; +import org.l2jmobius.gameserver.network.serverpackets.PledgeShowMemberListAll; +import org.l2jmobius.gameserver.network.serverpackets.PledgeShowMemberListUpdate; +import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; + +/** + * This class loads the clan related data. + */ +public class ClanTable +{ + private static final Logger LOGGER = Logger.getLogger(ClanTable.class.getName()); + private final Map _clans = new ConcurrentHashMap<>(); + + protected ClanTable() + { + // Forums has to be loaded before clan data, because of last forum id used should have also memo included. + if (GeneralConfig.ENABLE_COMMUNITY_BOARD) + { + ForumsBBSManager.getInstance().initRoot(); + } + + // Get all clan ids. + final List cids = new ArrayList<>(); + try (Connection con = DatabaseFactory.getConnection(); + Statement s = con.createStatement(); + ResultSet rs = s.executeQuery("SELECT clan_id FROM clan_data")) + { + while (rs.next()) + { + cids.add(rs.getInt("clan_id")); + } + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "Error restoring ClanTable.", e); + } + + // Create clans. + for (int cid : cids) + { + final Clan clan = new Clan(cid); + _clans.put(cid, clan); + if (clan.getDissolvingExpiryTime() != 0) + { + scheduleRemoveClan(clan.getId()); + } + } + + LOGGER.info(getClass().getSimpleName() + ": Restored " + cids.size() + " clans from the database."); + allianceCheck(); + restoreClanWars(); + + ThreadPool.scheduleAtFixedRate(this::updateClanRanks, 1000, 1200000); // 20 minutes. + } + + /** + * Gets the clans. + * @return the clans + */ + public Collection getClans() + { + return _clans.values(); + } + + /** + * Gets the clan count. + * @return the clan count + */ + public int getClanCount() + { + return _clans.size(); + } + + /** + * @param clanId + * @return + */ + public Clan getClan(int clanId) + { + return _clans.get(clanId); + } + + public Clan getClanByName(String clanName) + { + for (Clan clan : _clans.values()) + { + if (clan.getName().equalsIgnoreCase(clanName)) + { + return clan; + } + } + + return null; + } + + /** + * Creates a new clan and store clan info to database + * @param player + * @param clanName + * @return NULL if clan with same name already exists + */ + public Clan createClan(Player player, String clanName) + { + if (player == null) + { + return null; + } + + if (10 > player.getLevel()) + { + player.sendPacket(SystemMessageId.YOU_DO_NOT_MEET_THE_CRITERIA_IR_ORDER_TO_CREATE_A_CLAN); + return null; + } + + if (0 != player.getClanId()) + { + player.sendPacket(SystemMessageId.YOU_HAVE_FAILED_TO_CREATE_A_CLAN); + return null; + } + + if (System.currentTimeMillis() < player.getClanCreateExpiryTime()) + { + player.sendPacket(SystemMessageId.YOU_MUST_WAIT_10_DAYS_BEFORE_CREATING_A_NEW_CLAN); + return null; + } + + if (!StringUtil.isAlphaNumeric(clanName) || (2 > clanName.length())) + { + player.sendPacket(SystemMessageId.CLAN_NAME_IS_INVALID); + return null; + } + + if (16 < clanName.length()) + { + player.sendPacket(SystemMessageId.CLAN_NAME_S_LENGTH_IS_INCORRECT); + return null; + } + + if (null != getClanByName(clanName)) + { + // Clan name is already taken. + final SystemMessage sm = new SystemMessage(SystemMessageId.S1_ALREADY_EXISTS); + sm.addString(clanName); + player.sendPacket(sm); + return null; + } + + final Clan clan = new Clan(IdManager.getInstance().getNextId(), clanName); + final ClanMember leader = new ClanMember(clan, player); + clan.setLeader(leader); + leader.setPlayer(player); + clan.store(); + player.setClan(clan); + player.setPledgeClass(ClanMember.calculatePledgeClass(player)); + + final ClanPrivileges privileges = new ClanPrivileges(); + privileges.enableAll(); + player.setClanPrivileges(privileges); + + _clans.put(clan.getId(), clan); + + // should be update packet only + player.sendPacket(new PledgeShowInfoUpdate(clan)); + player.sendPacket(new PledgeShowMemberListAll(clan, player)); + player.updateUserInfo(); + player.sendPacket(new PledgeShowMemberListUpdate(player)); + player.sendPacket(SystemMessageId.YOUR_CLAN_HAS_BEEN_CREATED); + + // Notify to scripts + if (EventDispatcher.getInstance().hasListener(EventType.ON_PLAYER_CLAN_CREATE)) + { + EventDispatcher.getInstance().notifyEventAsync(new OnPlayerClanCreate(player, clan)); + } + + return clan; + } + + public synchronized void destroyClan(int clanId) + { + final Clan clan = getClan(clanId); + if (clan == null) + { + return; + } + + clan.broadcastToOnlineMembers(new SystemMessage(SystemMessageId.CLAN_HAS_DISPERSED)); + final int castleId = clan.getCastleId(); + if (castleId == 0) + { + for (Siege siege : SiegeManager.getInstance().getSieges()) + { + siege.removeSiegeClan(clan); + } + } + + final int hallId = clan.getHideoutId(); + if (hallId == 0) + { + for (SiegableHall hall : CHSiegeManager.getInstance().getConquerableHalls().values()) + { + hall.removeAttacker(clan); + } + } + + final ClanHallAuction auction = ClanHallAuctionManager.getInstance().getAuction(clan.getAuctionBiddedAt()); + if (auction != null) + { + auction.cancelBid(clan.getId()); + } + + final ClanMember leaderMember = clan.getLeader(); + clan.getWarehouse().destroyAllItems(ItemProcessType.DESTROY, leaderMember == null ? null : clan.getLeader().getPlayer(), null); + + for (ClanMember member : clan.getMembers()) + { + clan.removeClanMember(member.getObjectId(), 0); + } + + _clans.remove(clanId); + IdManager.getInstance().releaseId(clanId); + + try (Connection con = DatabaseFactory.getConnection()) + { + try (PreparedStatement ps = con.prepareStatement("DELETE FROM clan_data WHERE clan_id=?")) + { + ps.setInt(1, clanId); + ps.execute(); + } + + try (PreparedStatement ps = con.prepareStatement("DELETE FROM clan_privs WHERE clan_id=?")) + { + ps.setInt(1, clanId); + ps.execute(); + } + + try (PreparedStatement ps = con.prepareStatement("DELETE FROM clan_skills WHERE clan_id=?")) + { + ps.setInt(1, clanId); + ps.execute(); + } + + try (PreparedStatement ps = con.prepareStatement("DELETE FROM clan_subpledges WHERE clan_id=?")) + { + ps.setInt(1, clanId); + ps.execute(); + } + + try (PreparedStatement ps = con.prepareStatement("DELETE FROM clan_wars WHERE clan1=? OR clan2=?")) + { + ps.setInt(1, clanId); + ps.setInt(2, clanId); + ps.execute(); + } + + try (PreparedStatement ps = con.prepareStatement("DELETE FROM clan_notices WHERE clan_id=?")) + { + ps.setInt(1, clanId); + ps.execute(); + } + + if (castleId != 0) + { + try (PreparedStatement ps = con.prepareStatement("UPDATE castle SET taxPercent = 0 WHERE id = ?")) + { + ps.setInt(1, castleId); + ps.execute(); + } + } + + if (hallId != 0) + { + final SiegableHall hall = CHSiegeManager.getInstance().getSiegableHall(hallId); + if ((hall != null) && (hall.getOwnerId() == clanId)) + { + hall.free(); + } + } + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, getClass().getSimpleName() + ": Error removing clan from DB.", e); + } + + // Notify to scripts + if (EventDispatcher.getInstance().hasListener(EventType.ON_PLAYER_CLAN_DESTROY)) + { + EventDispatcher.getInstance().notifyEventAsync(new OnPlayerClanDestroy(leaderMember, clan)); + } + } + + public void scheduleRemoveClan(int clanId) + { + ThreadPool.schedule(() -> + { + if (getClan(clanId) == null) + { + return; + } + + if (getClan(clanId).getDissolvingExpiryTime() != 0) + { + destroyClan(clanId); + } + }, Math.max(getClan(clanId).getDissolvingExpiryTime() - System.currentTimeMillis(), 300000)); + } + + public boolean isAllyExists(String allyName) + { + for (Clan clan : _clans.values()) + { + if ((clan.getAllyName() != null) && clan.getAllyName().equalsIgnoreCase(allyName)) + { + return true; + } + } + + return false; + } + + public void storeClanWars(int clanId1, int clanId2) + { + final Clan clan1 = getClan(clanId1); + final Clan clan2 = getClan(clanId2); + + if (EventDispatcher.getInstance().hasListener(EventType.ON_CLAN_WAR_START)) + { + EventDispatcher.getInstance().notifyEventAsync(new OnClanWarStart(clan1, clan2)); + } + + clan1.setEnemyClan(clan2); + clan2.setAttackerClan(clan1); + clan1.broadcastClanStatus(); + clan2.broadcastClanStatus(); + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement("REPLACE INTO clan_wars (clan1, clan2, wantspeace1, wantspeace2) VALUES(?,?,?,?)")) + { + ps.setInt(1, clanId1); + ps.setInt(2, clanId2); + ps.setInt(3, 0); + ps.setInt(4, 0); + ps.execute(); + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, getClass().getSimpleName() + ": Error storing clan wars data.", e); + } + + // SystemMessage msg = new SystemMessage(SystemMessageId.WAR_WITH_THE_S1_CLAN_HAS_BEGUN); + // + SystemMessage msg = new SystemMessage(SystemMessageId.A_CLAN_WAR_HAS_BEEN_DECLARED_AGAINST_THE_CLAN_S1_IF_YOU_ARE_KILLED_DURING_THE_CLAN_WAR_BY_MEMBERS_OF_THE_OPPOSING_CLAN_YOU_WILL_ONLY_LOSE_A_QUARTER_OF_THE_NORMAL_EXPERIENCE_FROM_DEATH); + msg.addString(clan2.getName()); + clan1.broadcastToOnlineMembers(msg); + + // msg = new SystemMessage(SystemMessageId.WAR_WITH_THE_S1_CLAN_HAS_BEGUN); + // msg.addString(clan1.getName()); + // clan2.broadcastToOnlineMembers(msg); + // clan1 declared clan war. + msg = new SystemMessage(SystemMessageId.THE_CLAN_S1_HAS_DECLARED_A_CLAN_WAR); + msg.addString(clan1.getName()); + clan2.broadcastToOnlineMembers(msg); + } + + public void deleteClanWars(int clanId1, int clanId2) + { + final Clan clan1 = getClan(clanId1); + final Clan clan2 = getClan(clanId2); + + if (EventDispatcher.getInstance().hasListener(EventType.ON_CLAN_WAR_FINISH)) + { + EventDispatcher.getInstance().notifyEventAsync(new OnClanWarFinish(clan1, clan2)); + } + + clan1.deleteEnemyClan(clan2); + clan2.deleteAttackerClan(clan1); + clan1.broadcastClanStatus(); + clan2.broadcastClanStatus(); + + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement("DELETE FROM clan_wars WHERE (clan1=? AND clan2=?) OR (clan2=? AND clan1=?)")) + { + ps.setInt(1, clanId1); + ps.setInt(2, clanId2); + ps.setInt(3, clanId1); + ps.setInt(4, clanId2); + ps.execute(); + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, getClass().getSimpleName() + ": Error removing clan wars data.", e); + } + + // SystemMessage msg = new SystemMessage(SystemMessageId.WAR_WITH_THE_S1_CLAN_HAS_ENDED); + SystemMessage msg = new SystemMessage(SystemMessageId.THE_WAR_AGAINST_S1_CLAN_HAS_BEEN_STOPPED); + msg.addString(clan2.getName()); + clan1.broadcastToOnlineMembers(msg); + msg = new SystemMessage(SystemMessageId.THE_CLAN_S1_HAS_DECIDED_TO_STOP_THE_WAR); + msg.addString(clan1.getName()); + clan2.broadcastToOnlineMembers(msg); + } + + public void checkSurrender(Clan clan1, Clan clan2) + { + int count = 0; + for (ClanMember member : clan1.getMembers()) + { + if ((member != null) && (member.getPlayer().getWantsPeace() == 1)) + { + count++; + } + } + + if (count == (clan1.getMembers().size() - 1)) + { + clan1.deleteEnemyClan(clan2); + clan2.deleteEnemyClan(clan1); + deleteClanWars(clan1.getId(), clan2.getId()); + } + } + + private void restoreClanWars() + { + Clan clan1; + Clan clan2; + try (Connection con = DatabaseFactory.getConnection(); + Statement statement = con.createStatement(); + ResultSet rset = statement.executeQuery("SELECT clan1, clan2 FROM clan_wars")) + { + while (rset.next()) + { + clan1 = getClan(rset.getInt("clan1")); + clan2 = getClan(rset.getInt("clan2")); + if ((clan1 != null) && (clan2 != null)) + { + clan1.setEnemyClan(rset.getInt("clan2")); + clan2.setAttackerClan(rset.getInt("clan1")); + } + else + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": restorewars one of clans is null clan1:" + clan1 + " clan2:" + clan2); + } + } + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, getClass().getSimpleName() + ": Error restoring clan wars data.", e); + } + } + + /** + * Check for nonexistent alliances + */ + private void allianceCheck() + { + for (Clan clan : _clans.values()) + { + final int allyId = clan.getAllyId(); + if ((allyId != 0) && (clan.getId() != allyId) && !_clans.containsKey(allyId)) + { + clan.setAllyId(0); + clan.setAllyName(null); + clan.changeAllyCrest(0, true); + clan.updateClanInDB(); + LOGGER.info(getClass().getSimpleName() + ": Removed alliance from clan: " + clan); + } + } + } + + public List getClanAllies(int allianceId) + { + final List clanAllies = new ArrayList<>(); + if (allianceId != 0) + { + for (Clan clan : _clans.values()) + { + if ((clan != null) && (clan.getAllyId() == allianceId)) + { + clanAllies.add(clan); + } + } + } + + return clanAllies; + } + + public void shutdown() + { + for (Clan clan : _clans.values()) + { + clan.updateClanInDB(); + } + } + + private void updateClanRanks() + { + for (Clan clan : _clans.values()) + { + clan.setRank(getClanRank(clan)); + } + } + + public int getClanRank(Clan clan) + { + if (clan.getLevel() < 3) + { + return 0; + } + + int rank = 1; + for (Clan c : _clans.values()) + { + if ((clan != c) && ((clan.getLevel() < c.getLevel()) || ((clan.getLevel() == c.getLevel()) && (clan.getReputationScore() <= c.getReputationScore())))) + { + rank++; + } + } + + return rank; + } + + public static ClanTable getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final ClanTable INSTANCE = new ClanTable(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/sql/CrestTable.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/sql/CrestTable.java new file mode 100644 index 0000000..9037db1 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/sql/CrestTable.java @@ -0,0 +1,218 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.data.sql; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.commons.database.DatabaseFactory; +import org.l2jmobius.gameserver.entity.clan.Clan; +import org.l2jmobius.gameserver.entity.clan.Crest; +import org.l2jmobius.gameserver.entity.clan.enums.CrestType; + +/** + * Loads and saves crests from database. + * @author NosBit + */ +public class CrestTable +{ + private static final Logger LOGGER = Logger.getLogger(CrestTable.class.getName()); + + private final Map _crests = new ConcurrentHashMap<>(); + private final AtomicInteger _nextId = new AtomicInteger(1); + + protected CrestTable() + { + load(); + } + + public synchronized void load() + { + _crests.clear(); + final Set crestsInUse = new HashSet<>(); + for (Clan clan : ClanTable.getInstance().getClans()) + { + if (clan.getCrestId() != 0) + { + crestsInUse.add(clan.getCrestId()); + } + + if (clan.getCrestLargeId() != 0) + { + crestsInUse.add(clan.getCrestLargeId()); + } + + if (clan.getAllyCrestId() != 0) + { + crestsInUse.add(clan.getAllyCrestId()); + } + } + + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement statement = con.prepareStatement("SELECT `crest_id`, `data`, `type` FROM `crests` ORDER BY `crest_id` DESC"); + ResultSet rs = statement.executeQuery()) + { + while (rs.next()) + { + final int id = rs.getInt("crest_id"); + if (_nextId.get() <= id) + { + _nextId.set(id + 1); + } + + // Delete all unused crests except the last one we do not want to reuse. + // a crest id because client will display wrong crest if it is reused + if (!crestsInUse.contains(id) && (id != (_nextId.get() - 1))) + { + removeCrest(id); + continue; + } + + final byte[] data = rs.getBytes("data"); + final CrestType crestType = CrestType.getById(rs.getInt("type")); + if (crestType != null) + { + _crests.put(id, new Crest(id, data, crestType)); + } + else + { + LOGGER.warning("Unknown crest type found in database. Type:" + rs.getInt("type")); + } + } + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "There was an error while loading crests from database:", e); + } + + LOGGER.info(getClass().getSimpleName() + ": Loaded " + _crests.size() + " Crests."); + + for (Clan clan : ClanTable.getInstance().getClans()) + { + if ((clan.getCrestId() != 0) && (getCrest(clan.getCrestId()) == null)) + { + LOGGER.info("Removing non-existent crest for clan " + clan.getName() + " [" + clan.getId() + "], crestId:" + clan.getCrestId()); + clan.setCrestId(0); + clan.changeClanCrest(0); + } + + if ((clan.getCrestLargeId() != 0) && (getCrest(clan.getCrestLargeId()) == null)) + { + LOGGER.info("Removing non-existent large crest for clan " + clan.getName() + " [" + clan.getId() + "], crestLargeId:" + clan.getCrestLargeId()); + clan.setCrestLargeId(0); + clan.changeLargeCrest(0); + } + + if ((clan.getAllyCrestId() != 0) && (getCrest(clan.getAllyCrestId()) == null)) + { + LOGGER.info("Removing non-existent ally crest for clan " + clan.getName() + " [" + clan.getId() + "], allyCrestId:" + clan.getAllyCrestId()); + clan.setAllyCrestId(0); + clan.changeAllyCrest(0, true); + } + } + } + + /** + * @param crestId The crest id + * @return {@code Crest} if crest is found, {@code null} if crest was not found. + */ + public Crest getCrest(int crestId) + { + return _crests.get(crestId); + } + + /** + * Creates a {@code Crest} object and inserts it in database and cache. + * @param data + * @param crestType + * @return {@code Crest} on success, {@code null} on failure. + */ + public Crest createCrest(byte[] data, CrestType crestType) + { + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement statement = con.prepareStatement("INSERT INTO `crests`(`crest_id`, `data`, `type`) VALUES(?, ?, ?)")) + { + final Crest crest = new Crest(_nextId.getAndIncrement(), data, crestType); + statement.setInt(1, crest.getId()); + statement.setBytes(2, crest.getData()); + statement.setInt(3, crest.getType().getId()); + statement.executeUpdate(); + _crests.put(crest.getId(), crest); + return crest; + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "There was an error while saving crest in database:", e); + } + + return null; + } + + /** + * Removes crest from database and cache. + * @param crestId the id of crest to be removed. + */ + public void removeCrest(int crestId) + { + _crests.remove(crestId); + + // avoid removing last crest id we do not want to lose index... + // Because client will display wrong crest if it is reused. + if (crestId == (_nextId.get() - 1)) + { + return; + } + + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement statement = con.prepareStatement("DELETE FROM `crests` WHERE `crest_id`=?")) + { + statement.setInt(1, crestId); + statement.executeUpdate(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "There was an error while deleting crest from database:", e); + } + } + + /** + * @return The next crest id. + */ + public int getNextId() + { + return _nextId.getAndIncrement(); + } + + public static CrestTable getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final CrestTable INSTANCE = new CrestTable(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/sql/OfflinePlayTable.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/sql/OfflinePlayTable.java new file mode 100644 index 0000000..2005f7a --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/sql/OfflinePlayTable.java @@ -0,0 +1,417 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.data.sql; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.Statement; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.commons.database.DatabaseFactory; +import org.l2jmobius.commons.util.Rnd; +import org.l2jmobius.gameserver.config.custom.AutoPlayConfig; +import org.l2jmobius.gameserver.config.custom.AutoPotionsConfig; +import org.l2jmobius.gameserver.config.custom.OfflinePlayConfig; +import org.l2jmobius.gameserver.config.custom.OfflineTradeConfig; +import org.l2jmobius.gameserver.entity.World; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.holders.player.AutoUseSettingsHolder; +import org.l2jmobius.gameserver.entity.groups.Party; +import org.l2jmobius.gameserver.entity.groups.PartyDistributionType; +import org.l2jmobius.gameserver.network.Disconnection; +import org.l2jmobius.gameserver.network.serverpackets.LeaveWorld; +import org.l2jmobius.gameserver.taskmanagers.AutoPlayTaskManager; +import org.l2jmobius.gameserver.taskmanagers.AutoPotionTaskManager; +import org.l2jmobius.gameserver.taskmanagers.AutoUseTaskManager; + +/** + * @author Mobius + */ +public class OfflinePlayTable +{ + private static final Logger LOGGER = Logger.getLogger(OfflinePlayTable.class.getName()); + + private static final String LOAD_PLAYER_IDS = "SELECT DISTINCT charId FROM character_offline_play"; + private static final String LOAD_PLAYER = "SELECT * FROM character_offline_play WHERE charId=?"; + private static final String SAVE_PLAYER = "INSERT INTO character_offline_play (charId, type, id) VALUES (?, ?, ?)"; + private static final String REMOVE_PLAYER = "DELETE FROM character_offline_play WHERE charId=?"; + private static final String LOAD_GROUP_LEADER_IDS = "SELECT DISTINCT leaderId FROM character_offline_play_group"; + private static final String LOAD_GROUP_MEMBERS = "SELECT charId, type FROM character_offline_play_group WHERE leaderId=?"; + private static final String SAVE_GROUP_MEMBER = "INSERT INTO character_offline_play_group (leaderId, charId, type) VALUES (?, ?, ?)"; + private static final String REMOVE_GROUP_MEMBERS = "TRUNCATE TABLE character_offline_play_group"; + + private static final int TYPE_ACTIVE_SOULSHOT = 0; + private static final int TYPE_AUTO_SUPPLY_ITEM = 1; + private static final int TYPE_AUTO_BUFF = 2; + private static final int TYPE_AUTO_SKILL = 3; + private static final int TYPE_AUTO_ACTION = 4; + private static final int TYPE_AUTO_POTION_ITEM = 5; + private static final int TYPE_CUSTOM_AUTO_POTION_SYSTEM = 100; // config\Custom\AutoPotions.ini + + protected OfflinePlayTable() + { + } + + /** + * Restores all offline play players on server start. + */ + public void restoreOfflinePlayers() + { + LOGGER.info(getClass().getSimpleName() + ": Loading offline auto players..."); + + try (Connection con = DatabaseFactory.getConnection(); + Statement statement = con.createStatement(); + ResultSet result = statement.executeQuery(LOAD_PLAYER_IDS)) + { + int nPlayers = 0; + + while (result.next()) + { + Player player = null; + + try + { + player = Player.load(result.getInt("charId")); + player.setOnlineStatus(true, false); + player.spawnMe(player.getX(), player.getY(), player.getZ()); + + try (PreparedStatement stmItems = con.prepareStatement(LOAD_PLAYER)) + { + stmItems.setInt(1, player.getObjectId()); + + try (ResultSet items = stmItems.executeQuery()) + { + final AutoUseSettingsHolder settings = player.getAutoUseSettings(); + + while (items.next()) + { + final int type = items.getInt("type"); + final int id = items.getInt("id"); + + switch (type) + { + case TYPE_ACTIVE_SOULSHOT: + { + player.addAutoSoulShot(id); + break; + } + case TYPE_AUTO_SUPPLY_ITEM: + { + settings.getAutoSupplyItems().add(id); + break; + } + case TYPE_AUTO_BUFF: + { + settings.getAutoBuffs().add(id); + break; + } + case TYPE_AUTO_SKILL: + { + settings.getAutoSkills().add(id); + break; + } + case TYPE_AUTO_ACTION: + { + settings.getAutoActions().add(id); + break; + } + case TYPE_AUTO_POTION_ITEM: + { + settings.setAutoPotionItem(id); + break; + } + case TYPE_CUSTOM_AUTO_POTION_SYSTEM: + { + if (AutoPotionsConfig.AUTO_POTIONS_ENABLED) + { + AutoPotionTaskManager.getInstance().add(player); + } + break; + } + } + } + } + } + + player.setOfflinePlay(true); + player.setOnlineStatus(true, true); + player.restoreEffects(); + player.setRunning(); + + if (OfflineTradeConfig.OFFLINE_SET_NAME_COLOR) + { + player.getAppearance().setNameColor(OfflinePlayConfig.OFFLINE_PLAY_NAME_COLOR); + } + + if (!OfflinePlayConfig.OFFLINE_PLAY_ABNORMAL_EFFECTS.isEmpty()) + { + player.startAbnormalVisualEffect(true, OfflinePlayConfig.OFFLINE_PLAY_ABNORMAL_EFFECTS.get(Rnd.get(OfflinePlayConfig.OFFLINE_PLAY_ABNORMAL_EFFECTS.size()))); + } + + // player.broadcastUserInfo(); + + // Start auto play and auto use. + AutoPlayTaskManager.getInstance().startAutoPlay(player); + AutoUseTaskManager.getInstance().startAutoUseTask(player); + + nPlayers++; + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Error loading auto player " + player, e); + if (player != null) + { + Disconnection.of(player).storeAndDeleteWith(LeaveWorld.STATIC_PACKET); + } + } + } + + LOGGER.info(getClass().getSimpleName() + ": Loaded " + nPlayers + " offline auto players."); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Error while loading offline auto players. " + e.getMessage(), e); + } + + // Restore parties. + if (AutoPlayConfig.ENABLE_AUTO_ASSIST) + { + try (Connection con = DatabaseFactory.getConnection(); + Statement statement = con.createStatement(); + ResultSet result = statement.executeQuery(LOAD_GROUP_LEADER_IDS)) + { + int nParties = 0; + + while (result.next()) + { + final int leaderId = result.getInt("leaderId"); + final Player leader = World.getPlayer(leaderId); + if (leader != null) + { + nParties++; + + try (PreparedStatement stmtMembers = con.prepareStatement(LOAD_GROUP_MEMBERS)) + { + stmtMembers.setInt(1, leaderId); + + try (ResultSet members = stmtMembers.executeQuery()) + { + Party party = null; + + while (members.next()) + { + final int charId = members.getInt("charId"); + final Player member = World.getPlayer(charId); + if (member != null) + { + if (party == null) + { + party = new Party(leader, PartyDistributionType.findById(members.getInt("type"))); + } + + member.joinParty(party); + } + } + } + } + } + } + + LOGGER.info(getClass().getSimpleName() + ": Loaded " + nParties + " offline groups."); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Error while restoring offline groups. " + e.getMessage(), e); + } + } + + // Clear all party data in one operation. + try (Connection con = DatabaseFactory.getConnection(); + Statement statement = con.createStatement()) + { + statement.executeUpdate(REMOVE_GROUP_MEMBERS); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Error clearing offline group data. " + e.getMessage(), e); + } + } + + /** + * Stores the player entering offline play mode. + * @param player the player + */ + public void storeOfflinePlay(Player player) + { + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement statement1 = con.prepareStatement(REMOVE_PLAYER); + PreparedStatement statement2 = con.prepareStatement(SAVE_PLAYER)) + { + // First remove existing entries. + final int playerObjectId = player.getObjectId(); + statement1.setInt(1, playerObjectId); + statement1.execute(); + + try + { + // Store active soulshot items. + for (int shotId : player.getAutoSoulShot()) + { + statement2.setInt(1, playerObjectId); + statement2.setInt(2, TYPE_ACTIVE_SOULSHOT); + statement2.setInt(3, shotId); + statement2.addBatch(); + } + + // Store auto supply items. + for (Integer itemId : player.getAutoUseSettings().getAutoSupplyItems()) + { + statement2.setInt(1, playerObjectId); + statement2.setInt(2, TYPE_AUTO_SUPPLY_ITEM); + statement2.setInt(3, itemId); + statement2.addBatch(); + } + + // Store auto buffs. + for (Integer buffId : player.getAutoUseSettings().getAutoBuffs()) + { + statement2.setInt(1, playerObjectId); + statement2.setInt(2, TYPE_AUTO_BUFF); + statement2.setInt(3, buffId); + statement2.addBatch(); + } + + // Store auto skills. + for (Integer skillId : player.getAutoUseSettings().getAutoSkills()) + { + statement2.setInt(1, playerObjectId); + statement2.setInt(2, TYPE_AUTO_SKILL); + statement2.setInt(3, skillId); + statement2.addBatch(); + } + + // Store auto actions. + for (Integer actionId : player.getAutoUseSettings().getAutoActions()) + { + statement2.setInt(1, playerObjectId); + statement2.setInt(2, TYPE_AUTO_ACTION); + statement2.setInt(3, actionId); + statement2.addBatch(); + } + + // Store auto potion item. + final int autoPotionItem = player.getAutoUseSettings().getAutoPotionItem(); + if (autoPotionItem > 0) + { + statement2.setInt(1, playerObjectId); + statement2.setInt(2, TYPE_AUTO_POTION_ITEM); + statement2.setInt(3, autoPotionItem); + statement2.addBatch(); + } + + // Custom auto potion system. + if (AutoPotionsConfig.AUTO_POTIONS_ENABLED && AutoPotionTaskManager.getInstance().hasPlayer(player)) + { + statement2.setInt(1, playerObjectId); + statement2.setInt(2, TYPE_CUSTOM_AUTO_POTION_SYSTEM); + statement2.setInt(3, 0); + statement2.addBatch(); + } + + // Execute all batched statements. + statement2.executeBatch(); + // No need to call con.commit() as HikariCP autocommit is true. + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Error while saving offline auto player " + player, e); + } + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Error while saving offline auto players. " + e.getMessage(), e); + } + } + + public void storeOfflineGroups() + { + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement statement = con.prepareStatement(SAVE_GROUP_MEMBER)) + { + for (Player player : World.getPlayers()) + { + if (player.isAutoPlaying()) + { + final Party party = player.getParty(); + if ((party != null) && !party.isLeader(player)) + { + final Player leader = party.getLeader(); + if (leader.isAutoPlaying()) + { + statement.setInt(1, leader.getObjectId()); + statement.setInt(2, player.getObjectId()); + statement.setInt(3, party.getDistributionType().getId()); + statement.addBatch(); + } + } + } + } + + // Execute all batched statements. + statement.executeBatch(); + // No need to call con.commit() as HikariCP autocommit is true. + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Error while storing offline parties. " + e.getMessage(), e); + } + } + + /** + * Handles player removal of offline play mode. + * @param player the player + */ + public void removeOfflinePlay(Player player) + { + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement statement = con.prepareStatement(REMOVE_PLAYER)) + { + statement.setInt(1, player.getObjectId()); + statement.execute(); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Error while removing offline auto players. " + e.getMessage(), e); + } + } + + public static OfflinePlayTable getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final OfflinePlayTable INSTANCE = new OfflinePlayTable(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/sql/OfflineTraderTable.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/sql/OfflineTraderTable.java new file mode 100644 index 0000000..d1e7e7f --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/sql/OfflineTraderTable.java @@ -0,0 +1,619 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.data.sql; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.Statement; +import java.util.Calendar; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.commons.database.DatabaseFactory; +import org.l2jmobius.commons.util.Rnd; +import org.l2jmobius.gameserver.config.custom.DualboxCheckConfig; +import org.l2jmobius.gameserver.config.custom.OfflineTradeConfig; +import org.l2jmobius.gameserver.data.holders.SellBuffHolder; +import org.l2jmobius.gameserver.entity.World; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.Summon; +import org.l2jmobius.gameserver.entity.actor.enums.player.PrivateStoreType; +import org.l2jmobius.gameserver.entity.item.recipe.ManufactureItem; +import org.l2jmobius.gameserver.entity.zone.ZoneId; +import org.l2jmobius.gameserver.managers.AntiFeedManager; +import org.l2jmobius.gameserver.mechanics.olympiad.Olympiad; +import org.l2jmobius.gameserver.network.Disconnection; +import org.l2jmobius.gameserver.network.GameClient; +import org.l2jmobius.gameserver.network.holders.TradeItem; +import org.l2jmobius.gameserver.network.serverpackets.LeaveWorld; + +public class OfflineTraderTable +{ + private static final Logger LOGGER = Logger.getLogger(OfflineTraderTable.class.getName()); + private static final Logger LOGGER_ACCOUNTING = Logger.getLogger("accounting"); + + // SQL DEFINITIONS + private static final String SAVE_OFFLINE_STATUS = "INSERT INTO character_offline_trade (`charId`,`time`,`type`,`title`) VALUES (?,?,?,?)"; + private static final String SAVE_ITEMS = "INSERT INTO character_offline_trade_items (`charId`,`item`,`count`,`price`) VALUES (?,?,?,?)"; + private static final String CLEAR_OFFLINE_TABLE = "DELETE FROM character_offline_trade"; + private static final String CLEAR_OFFLINE_TABLE_PLAYER = "DELETE FROM character_offline_trade WHERE `charId`=?"; + private static final String CLEAR_OFFLINE_TABLE_ITEMS = "DELETE FROM character_offline_trade_items"; + private static final String CLEAR_OFFLINE_TABLE_ITEMS_PLAYER = "DELETE FROM character_offline_trade_items WHERE `charId`=?"; + private static final String LOAD_OFFLINE_STATUS = "SELECT * FROM character_offline_trade"; + private static final String LOAD_OFFLINE_ITEMS = "SELECT * FROM character_offline_trade_items WHERE `charId`=?"; + + protected OfflineTraderTable() + { + } + + public void storeOffliners() + { + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement stm1 = con.prepareStatement(CLEAR_OFFLINE_TABLE); + PreparedStatement stm2 = con.prepareStatement(CLEAR_OFFLINE_TABLE_ITEMS); + PreparedStatement stm3 = con.prepareStatement(SAVE_OFFLINE_STATUS); + PreparedStatement stmItems = con.prepareStatement(SAVE_ITEMS)) + { + stm1.execute(); + stm2.execute(); + for (Player pc : World.getPlayers()) + { + try + { + if (pc.isInStoreMode() && ((pc.getClient() == null) || pc.getClient().isDetached())) + { + stm3.setInt(1, pc.getObjectId()); // Char Id + stm3.setLong(2, pc.getOfflineStartTime()); + stm3.setInt(3, pc.isSellingBuffs() ? PrivateStoreType.SELL_BUFFS.getId() : pc.getPrivateStoreType().getId()); // store type + String title = null; + + switch (pc.getPrivateStoreType()) + { + case BUY: + { + if (!OfflineTradeConfig.OFFLINE_TRADE_ENABLE) + { + continue; + } + + title = pc.getBuyList().getTitle(); + for (TradeItem i : pc.getBuyList().getItems()) + { + stmItems.setInt(1, pc.getObjectId()); + stmItems.setInt(2, i.getItem().getId()); + stmItems.setInt(3, i.getCount()); + stmItems.setInt(4, i.getPrice()); + stmItems.executeUpdate(); + stmItems.clearParameters(); + } + break; + } + case SELL: + case PACKAGE_SELL: + { + if (!OfflineTradeConfig.OFFLINE_TRADE_ENABLE) + { + continue; + } + + title = pc.getSellList().getTitle(); + if (pc.isSellingBuffs()) + { + for (SellBuffHolder holder : pc.getSellingBuffs()) + { + stmItems.setInt(1, pc.getObjectId()); + stmItems.setInt(2, holder.getSkillId()); + stmItems.setInt(3, 0); + stmItems.setInt(4, holder.getPrice()); + stmItems.executeUpdate(); + stmItems.clearParameters(); + } + } + else + { + for (TradeItem i : pc.getSellList().getItems()) + { + stmItems.setInt(1, pc.getObjectId()); + stmItems.setInt(2, i.getObjectId()); + stmItems.setInt(3, i.getCount()); + stmItems.setInt(4, i.getPrice()); + stmItems.executeUpdate(); + stmItems.clearParameters(); + } + } + break; + } + case MANUFACTURE: + { + if (!OfflineTradeConfig.OFFLINE_CRAFT_ENABLE) + { + continue; + } + + title = pc.getStoreName(); + for (ManufactureItem i : pc.getManufactureItems().values()) + { + stmItems.setInt(1, pc.getObjectId()); + stmItems.setInt(2, i.getRecipeId()); + stmItems.setInt(3, 0); + stmItems.setInt(4, i.getCost()); + stmItems.executeUpdate(); + stmItems.clearParameters(); + } + break; + } + } + + stm3.setString(4, title); + stm3.executeUpdate(); + stm3.clearParameters(); + // No need to call con.commit() as HikariCP autocommit is true. + } + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Error while saving offline trader: " + pc.getObjectId() + " " + e, e); + } + } + + LOGGER.info(getClass().getSimpleName() + ": Offline traders stored."); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Error while saving offline traders: " + e, e); + } + } + + public void restoreOfflineTraders() + { + LOGGER.info(getClass().getSimpleName() + ": Loading offline traders..."); + int nTraders = 0; + try (Connection con = DatabaseFactory.getConnection(); + Statement stm = con.createStatement(); + ResultSet rs = stm.executeQuery(LOAD_OFFLINE_STATUS)) + { + while (rs.next()) + { + final long time = rs.getLong("time"); + if (OfflineTradeConfig.OFFLINE_MAX_DAYS > 0) + { + final Calendar cal = Calendar.getInstance(); + cal.setTimeInMillis(time); + cal.add(Calendar.DAY_OF_YEAR, OfflineTradeConfig.OFFLINE_MAX_DAYS); + if (cal.getTimeInMillis() <= System.currentTimeMillis()) + { + continue; + } + } + + final int typeId = rs.getInt("type"); + boolean isSellBuff = false; + if (typeId == PrivateStoreType.SELL_BUFFS.getId()) + { + isSellBuff = true; + } + + final PrivateStoreType type = isSellBuff ? PrivateStoreType.PACKAGE_SELL : PrivateStoreType.findById(typeId); + if (type == null) + { + LOGGER.warning(getClass().getSimpleName() + ": PrivateStoreType with id " + rs.getInt("type") + " could not be found."); + continue; + } + + if (type == PrivateStoreType.NONE) + { + continue; + } + + Player player = null; + + try + { + player = Player.load(rs.getInt("charId")); + player.setOnlineStatus(true, false); + player.setOfflineStartTime(time); + + if (isSellBuff) + { + player.setSellingBuffs(true); + } + + player.spawnMe(player.getX(), player.getY(), player.getZ()); + try (PreparedStatement stmItems = con.prepareStatement(LOAD_OFFLINE_ITEMS)) + { + stmItems.setInt(1, player.getObjectId()); + try (ResultSet items = stmItems.executeQuery()) + { + switch (type) + { + case BUY: + { + while (items.next()) + { + if (player.getBuyList().addItemByItemId(items.getInt(2), items.getInt(3), items.getInt(4)) == null) + { + continue; + // throw new NullPointerException(); + } + } + + player.getBuyList().setTitle(rs.getString("title")); + break; + } + case SELL: + case PACKAGE_SELL: + { + if (player.isSellingBuffs()) + { + while (items.next()) + { + player.getSellingBuffs().add(new SellBuffHolder(items.getInt("item"), items.getInt("price"))); + } + } + else + { + while (items.next()) + { + if (player.getSellList().addItem(items.getInt(2), items.getInt(3), items.getInt(4)) == null) + { + continue; + // throw new NullPointerException(); + } + } + } + + player.getSellList().setTitle(rs.getString("title")); + player.getSellList().setPackaged(type == PrivateStoreType.PACKAGE_SELL); + break; + } + case MANUFACTURE: + { + while (items.next()) + { + player.getManufactureItems().put(items.getInt(2), new ManufactureItem(items.getInt(2), items.getInt(4))); + } + + player.setStoreName(rs.getString("title")); + break; + } + } + } + } + + player.sitDown(); + if (OfflineTradeConfig.OFFLINE_SET_NAME_COLOR) + { + player.getAppearance().setNameColor(OfflineTradeConfig.OFFLINE_NAME_COLOR); + } + + player.setPrivateStoreType(type); + player.setOnlineStatus(true, true); + player.restoreEffects(); + if (!OfflineTradeConfig.OFFLINE_ABNORMAL_EFFECTS.isEmpty()) + { + player.startAbnormalVisualEffect(false, OfflineTradeConfig.OFFLINE_ABNORMAL_EFFECTS.get(Rnd.get(OfflineTradeConfig.OFFLINE_ABNORMAL_EFFECTS.size()))); + } + + player.broadcastUserInfo(); + nTraders++; + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Error loading trader: " + player, e); + if (player != null) + { + Disconnection.of(player).storeAndDeleteWith(LeaveWorld.STATIC_PACKET); + } + } + } + + World.OFFLINE_TRADE_COUNT = nTraders; + LOGGER.info(getClass().getSimpleName() + ": Loaded " + nTraders + " offline traders."); + + if (!OfflineTradeConfig.STORE_OFFLINE_TRADE_IN_REALTIME) + { + try (Statement stm1 = con.createStatement()) + { + stm1.execute(CLEAR_OFFLINE_TABLE); + stm1.execute(CLEAR_OFFLINE_TABLE_ITEMS); + } + } + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Error while loading offline traders: ", e); + } + } + + public synchronized void onTransaction(Player trader, boolean finished, boolean firstCall) + { + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement stm1 = con.prepareStatement(CLEAR_OFFLINE_TABLE_ITEMS_PLAYER); + PreparedStatement stm2 = con.prepareStatement(CLEAR_OFFLINE_TABLE_PLAYER); + PreparedStatement stm3 = con.prepareStatement(SAVE_ITEMS); + PreparedStatement stm4 = con.prepareStatement(SAVE_OFFLINE_STATUS)) + { + String title = null; + stm1.setInt(1, trader.getObjectId()); // Char Id + stm1.execute(); + + // Trade is done - clear info. + if (finished) + { + stm2.setInt(1, trader.getObjectId()); // Char Id + stm2.execute(); + } + else + { + try + { + if ((trader.getClient() == null) || trader.getClient().isDetached()) + { + switch (trader.getPrivateStoreType()) + { + case BUY: + { + if (firstCall) + { + title = trader.getBuyList().getTitle(); + } + + for (TradeItem i : trader.getBuyList().getItems()) + { + stm3.setInt(1, trader.getObjectId()); + stm3.setInt(2, i.getItem().getId()); + stm3.setInt(3, i.getCount()); + stm3.setInt(4, i.getPrice()); + stm3.executeUpdate(); + stm3.clearParameters(); + } + break; + } + case SELL: + case PACKAGE_SELL: + { + if (firstCall) + { + title = trader.getSellList().getTitle(); + } + + if (trader.isSellingBuffs()) + { + for (SellBuffHolder holder : trader.getSellingBuffs()) + { + stm3.setInt(1, trader.getObjectId()); + stm3.setInt(2, holder.getSkillId()); + stm3.setInt(3, 0); + stm3.setInt(4, holder.getPrice()); + stm3.executeUpdate(); + stm3.clearParameters(); + } + } + else + { + for (TradeItem i : trader.getSellList().getItems()) + { + stm3.setInt(1, trader.getObjectId()); + stm3.setInt(2, i.getObjectId()); + stm3.setInt(3, i.getCount()); + stm3.setInt(4, i.getPrice()); + stm3.executeUpdate(); + stm3.clearParameters(); + } + } + break; + } + case MANUFACTURE: + { + if (firstCall) + { + title = trader.getStoreName(); + } + + for (ManufactureItem i : trader.getManufactureItems().values()) + { + stm3.setInt(1, trader.getObjectId()); + stm3.setInt(2, i.getRecipeId()); + stm3.setInt(3, 0); + stm3.setInt(4, i.getCost()); + stm3.executeUpdate(); + stm3.clearParameters(); + } + break; + } + } + + if (firstCall) + { + stm4.setInt(1, trader.getObjectId()); // Char Id + stm4.setLong(2, trader.getOfflineStartTime()); + stm4.setInt(3, trader.isSellingBuffs() ? PrivateStoreType.SELL_BUFFS.getId() : trader.getPrivateStoreType().getId()); // store type + stm4.setString(4, title); + stm4.executeUpdate(); + stm4.clearParameters(); + } + } + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Error while saving offline trader: " + trader.getObjectId() + " " + e, e); + } + } + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Error while saving offline traders: " + e, e); + } + } + + public synchronized void removeTrader(int traderObjId) + { + World.OFFLINE_TRADE_COUNT--; + + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement stm1 = con.prepareStatement(CLEAR_OFFLINE_TABLE_ITEMS_PLAYER); + PreparedStatement stm2 = con.prepareStatement(CLEAR_OFFLINE_TABLE_PLAYER)) + { + stm1.setInt(1, traderObjId); + stm1.execute(); + + stm2.setInt(1, traderObjId); + stm2.execute(); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Error while removing offline trader: " + traderObjId + " " + e, e); + } + } + + /** + * Check whether player is able to enter offline mode. + * @param player the player to be check. + * @return {@code true} if the player is allowed to remain as off-line shop. + */ + private boolean offlineMode(Player player) + { + if ((player == null) || player.isInOlympiadMode() || player.isRegisteredOnEvent() || player.isJailed() || (player.getVehicle() != null)) + { + return false; + } + + boolean canSetShop = false; + switch (player.getPrivateStoreType()) + { + case SELL: + case PACKAGE_SELL: + case BUY: + { + canSetShop = OfflineTradeConfig.OFFLINE_TRADE_ENABLE; + break; + } + case MANUFACTURE: + { + canSetShop = OfflineTradeConfig.OFFLINE_TRADE_ENABLE; + break; + } + default: + { + canSetShop = OfflineTradeConfig.OFFLINE_CRAFT_ENABLE && player.isCrafting(); + break; + } + } + + if (OfflineTradeConfig.OFFLINE_MODE_IN_PEACE_ZONE && !player.isInsideZone(ZoneId.PEACE)) + { + canSetShop = false; + } + + // Check whether client is null or player is already in offline mode. + final GameClient client = player.getClient(); + if ((client == null) || client.isDetached()) + { + return false; + } + + return canSetShop; + } + + /** + * Manages the disconnection process of offline traders. + * @param player + * @return {@code true} when player entered offline mode, otherwise {@code false} + */ + public boolean enteredOfflineMode(Player player) + { + if (!offlineMode(player)) + { + return false; + } + + World.OFFLINE_TRADE_COUNT++; + + final GameClient client = player.getClient(); + client.close(LeaveWorld.STATIC_PACKET); + if (!DualboxCheckConfig.DUALBOX_COUNT_OFFLINE_TRADERS) + { + AntiFeedManager.getInstance().onDisconnect(client); + } + + client.setDetached(true); + + player.leaveParty(); + Olympiad.getInstance().unRegisterNoble(player); + + // If the Player has Pet, unsummon it. + Summon pet = player.getSummon(); + if (pet != null) + { + pet.setRestoreSummon(true); + pet.unSummon(player); + pet = player.getSummon(); + + // Dead pet wasn't unsummoned, broadcast npcinfo changes (pet will be without owner name - means owner offline). + if (pet != null) + { + pet.broadcastNpcInfo(0); + } + } + + if (OfflineTradeConfig.OFFLINE_SET_NAME_COLOR) + { + player.getAppearance().setNameColor(OfflineTradeConfig.OFFLINE_NAME_COLOR); + player.broadcastUserInfo(); + } + + if (player.getOfflineStartTime() == 0) + { + player.setOfflineStartTime(System.currentTimeMillis()); + } + + // Store trade on exit, if realtime saving is enabled. + if (OfflineTradeConfig.STORE_OFFLINE_TRADE_IN_REALTIME) + { + onTransaction(player, false, true); + } + + player.storeMe(); + LOGGER_ACCOUNTING.info("Entering offline mode, " + client); + + if (!OfflineTradeConfig.OFFLINE_ABNORMAL_EFFECTS.isEmpty()) + { + player.startAbnormalVisualEffect(true, OfflineTradeConfig.OFFLINE_ABNORMAL_EFFECTS.get(Rnd.get(OfflineTradeConfig.OFFLINE_ABNORMAL_EFFECTS.size()))); + } + + return true; + } + + /** + * Gets the single instance of OfflineTradersTable. + * @return single instance of OfflineTradersTable + */ + public static OfflineTraderTable getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final OfflineTraderTable INSTANCE = new OfflineTraderTable(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/sql/PetNameTable.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/sql/PetNameTable.java new file mode 100644 index 0000000..1d43915 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/sql/PetNameTable.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.data.sql; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.commons.database.DatabaseFactory; +import org.l2jmobius.commons.util.StringUtil; +import org.l2jmobius.gameserver.config.ServerConfig; +import org.l2jmobius.gameserver.data.xml.PetDataTable; + +public class PetNameTable +{ + private static final Logger LOGGER = Logger.getLogger(PetNameTable.class.getName()); + + // SQL + private static final String CHECK_PET_NAME = "SELECT p.name FROM pets p JOIN items i ON p.item_obj_id = i.object_id WHERE p.name=? AND i.item_id=?"; + + public boolean doesPetNameExist(String name, int petNpcId) + { + boolean result = false; + final int petItemId = PetDataTable.getInstance().getPetItemsByNpc(petNpcId); + + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement(CHECK_PET_NAME)) + { + ps.setString(1, name); + ps.setInt(2, petItemId); + + try (ResultSet rs = ps.executeQuery()) + { + result = rs.next(); + } + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Could not check existing pet name: " + e.getMessage(), e); + } + + return result; + } + + public boolean isValidPetName(String name) + { + // Only allow alphanumeric names. + if (!StringUtil.isAlphaNumeric(name)) + { + return false; + } + + return ServerConfig.PET_NAME_TEMPLATE_PATTERN.matcher(name).matches(); + } + + private static class SingletonHolder + { + protected static final PetNameTable INSTANCE = new PetNameTable(); + } + + public static PetNameTable getInstance() + { + return SingletonHolder.INSTANCE; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/sql/SummonEffectTable.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/sql/SummonEffectTable.java new file mode 100644 index 0000000..5e61bb6 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/sql/SummonEffectTable.java @@ -0,0 +1,185 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.data.sql; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.Summon; +import org.l2jmobius.gameserver.entity.actor.instance.Pet; +import org.l2jmobius.gameserver.entity.actor.instance.Servitor; +import org.l2jmobius.gameserver.mechanics.skill.Skill; + +/** + * @author Nyaran + */ +public class SummonEffectTable +{ + /** Servitors **/ + + // Map tree + // -> key: charObjectId, value: classIndex Map + // --> key: classIndex, value: servitors Map + // ---> key: servitorSkillId, value: Effects list + private final Map>>> _servitorEffects = new ConcurrentHashMap<>(); + + private Map> getServitorEffects(Player owner) + { + final Map>> servitorMap = _servitorEffects.get(owner.getObjectId()); + if (servitorMap == null) + { + return null; + } + + return servitorMap.get(owner.getClassIndex()); + } + + private Collection getServitorEffects(Player owner, int referenceSkill) + { + return containsOwner(owner) ? getServitorEffects(owner).get(referenceSkill) : null; + } + + private boolean containsOwner(Player owner) + { + return _servitorEffects.getOrDefault(owner.getObjectId(), Collections.emptyMap()).containsKey(owner.getClassIndex()); + } + + private void removeEffects(Collection effects, int skillId) + { + if ((effects != null) && !effects.isEmpty()) + { + for (SummonEffect effect : effects) + { + final Skill skill = effect.getSkill(); + if ((skill != null) && (skill.getId() == skillId)) + { + effects.remove(effect); + } + } + } + } + + private void applyEffects(Summon summon, Collection summonEffects) + { + if (summonEffects == null) + { + return; + } + + for (SummonEffect se : summonEffects) + { + if (se != null) + { + se.getSkill().applyEffects(summon, summon, false, se.getEffectCurTime()); + } + } + } + + public boolean containsSkill(Player owner, int referenceSkill) + { + return containsOwner(owner) && getServitorEffects(owner).containsKey(referenceSkill); + } + + public void clearServitorEffects(Player owner, int referenceSkill) + { + if (containsOwner(owner)) + { + getServitorEffects(owner).getOrDefault(referenceSkill, Collections.emptyList()).clear(); + } + } + + public void addServitorEffect(Player owner, int referenceSkill, Skill skill, int effectCurTime) + { + _servitorEffects.putIfAbsent(owner.getObjectId(), new ConcurrentHashMap<>()); + _servitorEffects.get(owner.getObjectId()).putIfAbsent(owner.getClassIndex(), new ConcurrentHashMap<>()); + getServitorEffects(owner).putIfAbsent(referenceSkill, ConcurrentHashMap.newKeySet()); + getServitorEffects(owner).get(referenceSkill).add(new SummonEffect(skill, effectCurTime)); + } + + public void removeServitorEffects(Player owner, int referenceSkill, int skillId) + { + removeEffects(getServitorEffects(owner, referenceSkill), skillId); + } + + public void applyServitorEffects(Servitor servitor, Player owner, int referenceSkill) + { + applyEffects(servitor, getServitorEffects(owner, referenceSkill)); + } + + /** Pets **/ + private final Map> _petEffects = new ConcurrentHashMap<>(); // key: petItemObjectId, value: Effects list + + public void addPetEffect(int controlObjectId, Skill skill, int effectCurTime) + { + _petEffects.computeIfAbsent(controlObjectId, _ -> ConcurrentHashMap.newKeySet()).add(new SummonEffect(skill, effectCurTime)); + } + + public boolean containsPetId(int controlObjectId) + { + return _petEffects.containsKey(controlObjectId); + } + + public void applyPetEffects(Pet pet, int controlObjectId) + { + applyEffects(pet, _petEffects.get(controlObjectId)); + } + + public void clearPetEffects(int controlObjectId) + { + _petEffects.getOrDefault(controlObjectId, Collections.emptyList()).clear(); + } + + public void removePetEffects(int controlObjectId, int skillId) + { + removeEffects(_petEffects.get(controlObjectId), skillId); + } + + private class SummonEffect + { + Skill _skill; + int _effectCurTime; + + public SummonEffect(Skill skill, int effectCurTime) + { + _skill = skill; + _effectCurTime = effectCurTime; + } + + public Skill getSkill() + { + return _skill; + } + + public int getEffectCurTime() + { + return _effectCurTime; + } + } + + public static SummonEffectTable getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final SummonEffectTable INSTANCE = new SummonEffectTable(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/AdminData.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/AdminData.java new file mode 100644 index 0000000..aed8c55 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/AdminData.java @@ -0,0 +1,333 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.data.xml; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Logger; + +import org.w3c.dom.Document; + +import org.l2jmobius.commons.util.IXmlReader; +import org.l2jmobius.gameserver.data.holders.AccessLevel; +import org.l2jmobius.gameserver.data.holders.AccessRight; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.serverpackets.ServerPacket; +import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; +import org.l2jmobius.gameserver.util.StatSet; + +/** + * Loads administrator access levels and commands. + * @author UnAfraid, Mobius + */ +public class AdminData implements IXmlReader +{ + private static final Logger LOGGER = Logger.getLogger(AdminData.class.getName()); + + private final Map _accessLevels = new HashMap<>(); + private final Map _adminCommandAccessRights = new LinkedHashMap<>(); + private final Map _gmList = new ConcurrentHashMap<>(); + private int _highestLevel = 0; + + protected AdminData() + { + load(); + } + + @Override + public synchronized void load() + { + _accessLevels.clear(); + _adminCommandAccessRights.clear(); + + parseDatapackFile("config/AccessLevels.xml"); + LOGGER.info(getClass().getSimpleName() + ": Loaded " + _accessLevels.size() + " access levels."); + + parseDatapackFile("config/AdminCommands.xml"); + LOGGER.info(getClass().getSimpleName() + ": Loaded " + _adminCommandAccessRights.size() + " access commands."); + } + + @Override + public void parseDocument(Document document, File file) + { + forEach(document, n -> + { + if ("list".equalsIgnoreCase(n.getNodeName())) + { + forEach(n, d -> + { + switch (d.getNodeName()) + { + case "access": + { + final StatSet set = new StatSet(parseAttributes(d)); + final AccessLevel level = new AccessLevel(set); + if (level.getLevel() > _highestLevel) + { + _highestLevel = level.getLevel(); + } + + _accessLevels.put(level.getLevel(), level); + break; + } + case "admin": + { + final StatSet set = new StatSet(parseAttributes(d)); + final AccessRight command = new AccessRight(set); + _adminCommandAccessRights.put(command.getCommand(), command); + break; + } + } + }); + } + }); + } + + /** + * Returns the access level associated with a specific character access level. + * @param accessLevel the access level as an integer + * @return the {@link AccessLevel} instance corresponding to the given character access level + */ + public AccessLevel getAccessLevel(int accessLevel) + { + if (accessLevel < 0) + { + return _accessLevels.get(-1); + } + + return _accessLevels.computeIfAbsent(accessLevel, k -> new AccessLevel()); + } + + /** + * Gets the master access level, which represents the highest available access level. + * @return the {@link AccessLevel} instance representing the master access level + */ + public AccessLevel getMasterAccessLevel() + { + return _accessLevels.get(_highestLevel); + } + + /** + * Checks if an access level exists. + * @param accessLevel the access level as an integer + * @return {@code true} if the access level exists, {@code false} otherwise + */ + public boolean hasAccessLevel(int accessLevel) + { + return _accessLevels.containsKey(accessLevel); + } + + /** + * Checks if the specified access level has permission to execute a given admin command. + * @param adminCommand the admin command to check + * @param accessLevel the {@link AccessLevel} instance to verify + * @return {@code true} if the access level has access to the admin command, {@code false} otherwise + */ + public boolean hasAccess(String adminCommand, AccessLevel accessLevel) + { + AccessRight accessRight = _adminCommandAccessRights.get(adminCommand); + if (accessRight == null) + { + // Trying to avoid the spam for next time when the GM would try to use the same command. + if ((accessLevel.getLevel() > 0) && (accessLevel.getLevel() == _highestLevel)) + { + accessRight = new AccessRight(adminCommand, "", true, accessLevel.getLevel()); + _adminCommandAccessRights.put(accessRight.getCommand(), accessRight); + LOGGER.info(getClass().getSimpleName() + ": No rights defined for admin command " + adminCommand + " auto setting accesslevel: " + accessLevel.getLevel() + " !"); + } + else + { + LOGGER.info(getClass().getSimpleName() + ": No rights defined for admin command " + adminCommand + " !"); + return false; + } + } + + return accessRight.hasAccess(accessLevel); + } + + /** + * Determines if an admin command requires confirmation before execution. + * @param command the admin command to check + * @return {@code true} if the command requires confirmation, {@code false} otherwise + */ + public boolean requireConfirm(String command) + { + final AccessRight accessRight = _adminCommandAccessRights.get(command); + if (accessRight == null) + { + LOGGER.info(getClass().getSimpleName() + ": No rights defined for admin command " + command + "."); + return false; + } + + return accessRight.requireConfirm(); + } + + /** + * Retrieves a list of all Game Masters (GMs). + * @param includeHidden {@code true} to include hidden GMs, {@code false} to exclude them + * @return a {@link List} of {@link Player} instances representing the GMs + */ + public Collection getAllGms(boolean includeHidden) + { + final List result = new ArrayList<>(); + for (Entry entry : _gmList.entrySet()) + { + if (includeHidden || !entry.getValue()) + { + result.add(entry.getKey()); + } + } + + return result; + } + + /** + * Retrieves a list of all Game Master (GM) names. + * @param includeHidden {@code true} to include hidden GMs, {@code false} to exclude them + * @return a {@link List} of GM names as {@link String} + */ + public Collection getAllGmNames(boolean includeHidden) + { + final List result = new ArrayList<>(); + for (Entry entry : _gmList.entrySet()) + { + if (!entry.getValue()) + { + result.add(entry.getKey().getName()); + } + else if (includeHidden) + { + result.add(entry.getKey().getName() + " (invis)"); + } + } + + return result; + } + + /** + * Adds a GM to the GM list. + * @param player the {@link Player} instance representing the GM + * @param hidden {@code true} if the GM is hidden, {@code false} otherwise + */ + public void addGm(Player player, boolean hidden) + { + _gmList.put(player, hidden); + } + + /** + * Removes a GM from the GM list. + * @param player the {@link Player} instance to remove + */ + public void deleteGm(Player player) + { + _gmList.remove(player); + } + + /** + * Checks if any GM is currently online. + * @param includeHidden {@code true} to include hidden GMs, {@code false} to exclude them + * @return {@code true} if a GM is online, {@code false} otherwise + */ + public boolean isGmOnline(boolean includeHidden) + { + for (Entry entry : _gmList.entrySet()) + { + if (includeHidden || !entry.getValue()) + { + return true; + } + } + + return false; + } + + /** + * Sends the list of visible GMs to a specific player. + * @param player the {@link Player} instance to receive the GM list + */ + public void sendListToPlayer(Player player) + { + if (isGmOnline(player.isGM())) + { + player.sendPacket(SystemMessageId.GM_LIST); + for (String name : getAllGmNames(player.isGM())) + { + final SystemMessage sm = new SystemMessage(SystemMessageId.GM_S1); + sm.addString(name); + player.sendPacket(sm); + } + } + else + { + player.sendPacket(SystemMessageId.THERE_ARE_NO_GMS_CURRENTLY_VISIBLE_IN_THE_PUBLIC_LIST_AS_THEY_MAY_BE_PERFORMING_OTHER_FUNCTIONS_AT_THE_MOMENT); + } + } + + /** + * Broadcasts a packet to all GMs. + * @param packet the {@link ServerPacket} to broadcast + */ + public void broadcastToGMs(ServerPacket packet) + { + for (Player player : getAllGms(true)) + { + player.sendPacket(packet); + } + } + + /** + * Broadcasts a message to all GMs. + * @param message the message to broadcast as a {@link String} + */ + public void broadcastMessageToGMs(String message) + { + for (Player player : getAllGms(true)) + { + player.sendMessage(message); + } + } + + /** + * @return all admin access rights + */ + public Collection getAdminCommandAccessRights() + { + return _adminCommandAccessRights.values(); + } + + public static AdminData getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final AdminData INSTANCE = new AdminData(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/ArmorSetData.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/ArmorSetData.java new file mode 100644 index 0000000..fc05023 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/ArmorSetData.java @@ -0,0 +1,211 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.data.xml; + +import java.io.File; +import java.util.Collections; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; + +import org.w3c.dom.Document; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; + +import org.l2jmobius.commons.util.IXmlReader; +import org.l2jmobius.gameserver.data.holders.ArmorSet; +import org.l2jmobius.gameserver.mechanics.skill.holders.SkillHolder; + +/** + * Loads armor set bonuses. + * @author godson, Luno, UnAfraid + */ +public class ArmorSetData implements IXmlReader +{ + private ArmorSet[] _armorSets; + private final Map _armorSetMap = new ConcurrentHashMap<>(); + + /** + * Instantiates a new armor sets data. + */ + protected ArmorSetData() + { + load(); + } + + @Override + public void load() + { + parseDatapackDirectory("data/stats/armorsets", false); + + _armorSets = new ArmorSet[Collections.max(_armorSetMap.keySet()) + 1]; + for (Entry armorSet : _armorSetMap.entrySet()) + { + _armorSets[armorSet.getKey()] = armorSet.getValue(); + } + + LOGGER.info(getClass().getSimpleName() + ": Loaded " + _armorSetMap.size() + " armor sets."); + _armorSetMap.clear(); + } + + @Override + public void parseDocument(Document document, File file) + { + for (Node n = document.getFirstChild(); n != null; n = n.getNextSibling()) + { + if ("list".equalsIgnoreCase(n.getNodeName())) + { + for (Node d = n.getFirstChild(); d != null; d = d.getNextSibling()) + { + if ("set".equalsIgnoreCase(d.getNodeName())) + { + final ArmorSet set = new ArmorSet(); + for (Node a = d.getFirstChild(); a != null; a = a.getNextSibling()) + { + final NamedNodeMap attrs = a.getAttributes(); + switch (a.getNodeName()) + { + case "chest": + { + set.addChest(parseInteger(attrs, "id")); + break; + } + case "feet": + { + set.addFeet(parseInteger(attrs, "id")); + break; + } + case "gloves": + { + set.addGloves(parseInteger(attrs, "id")); + break; + } + case "head": + { + set.addHead(parseInteger(attrs, "id")); + break; + } + case "legs": + { + set.addLegs(parseInteger(attrs, "id")); + break; + } + case "shield": + { + set.addShield(parseInteger(attrs, "id")); + break; + } + case "skill": + { + final int skillId = parseInteger(attrs, "id"); + final int skillLevel = parseInteger(attrs, "level"); + set.addSkill(new SkillHolder(skillId, skillLevel)); + break; + } + case "shield_skill": + { + final int skillId = parseInteger(attrs, "id"); + final int skillLevel = parseInteger(attrs, "level"); + set.addShieldSkill(new SkillHolder(skillId, skillLevel)); + break; + } + case "enchant6skill": + { + final int skillId = parseInteger(attrs, "id"); + final int skillLevel = parseInteger(attrs, "level"); + set.addEnchant6Skill(new SkillHolder(skillId, skillLevel)); + break; + } + case "con": + { + set.addCon(parseInteger(attrs, "val")); + break; + } + case "dex": + { + set.addDex(parseInteger(attrs, "val")); + break; + } + case "str": + { + set.addStr(parseInteger(attrs, "val")); + break; + } + case "men": + { + set.addMen(parseInteger(attrs, "val")); + break; + } + case "wit": + { + set.addWit(parseInteger(attrs, "val")); + break; + } + case "int": + { + set.addInt(parseInteger(attrs, "val")); + break; + } + } + } + + _armorSetMap.put(set.getChestId(), set); + } + } + } + } + } + + /** + * Checks if is armor set. + * @param chestId the chest Id to verify. + * @return {@code true} if the chest Id belongs to a registered armor set, {@code false} otherwise. + */ + public boolean isArmorSet(int chestId) + { + return (_armorSets.length > chestId) && (_armorSets[chestId] != null); + } + + /** + * Gets the sets the. + * @param chestId the chest Id identifying the armor set. + * @return the armor set associated to the give chest Id. + */ + public ArmorSet getSet(int chestId) + { + if (_armorSets.length > chestId) + { + return _armorSets[chestId]; + } + + return null; + } + + /** + * Gets the single instance of ArmorSetsData. + * @return single instance of ArmorSetsData + */ + public static ArmorSetData getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final ArmorSetData INSTANCE = new ArmorSetData(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/BuyListData.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/BuyListData.java new file mode 100644 index 0000000..7121d37 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/BuyListData.java @@ -0,0 +1,206 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.data.xml; + +import java.io.File; +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.Statement; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.regex.Pattern; + +import org.w3c.dom.Document; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; + +import org.l2jmobius.commons.database.DatabaseFactory; +import org.l2jmobius.commons.util.IXmlReader; +import org.l2jmobius.gameserver.config.GeneralConfig; +import org.l2jmobius.gameserver.entity.item.ItemTemplate; +import org.l2jmobius.gameserver.mechanics.buylist.BuyListHolder; +import org.l2jmobius.gameserver.mechanics.buylist.Product; + +/** + * Loads buy lists for NPCs. + * @author NosBit + */ +public class BuyListData implements IXmlReader +{ + private static final Logger LOGGER = Logger.getLogger(BuyListData.class.getName()); + + private static final Pattern XML_FILE_PATTERN = Pattern.compile("\\d+\\.xml"); + + private final Map _buyLists = new ConcurrentHashMap<>(); + + protected BuyListData() + { + load(); + } + + @Override + public synchronized void load() + { + _buyLists.clear(); + parseDatapackDirectory("data/buylists", false); + if (GeneralConfig.CUSTOM_BUYLIST_LOAD) + { + parseDatapackDirectory("data/buylists/custom", false); + } + + LOGGER.info(getClass().getSimpleName() + ": Loaded " + _buyLists.size() + " buyLists."); + + try (Connection con = DatabaseFactory.getConnection(); + Statement statement = con.createStatement(); + ResultSet rs = statement.executeQuery("SELECT * FROM `buylists`")) + { + while (rs.next()) + { + final int buyListId = rs.getInt("buylist_id"); + final BuyListHolder buyList = getBuyList(buyListId); + if (buyList == null) + { + LOGGER.warning("BuyList found in database but not loaded from xml! BuyListId: " + buyListId); + continue; + } + + final int itemId = rs.getInt("item_id"); + final Product product = buyList.getProductByItemId(itemId); + if (product == null) + { + LOGGER.warning("ItemId found in database but not loaded from xml! BuyListId: " + buyListId + " ItemId: " + itemId); + continue; + } + + final int count = rs.getInt("count"); + if (count < product.getMaxCount()) + { + product.setCount(count); + product.restartRestockTask(rs.getLong("next_restock_time")); + } + } + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Failed to load buyList data from database.", e); + } + } + + @Override + public void parseDocument(Document document, File file) + { + try + { + final int buyListId = Integer.parseInt(file.getName().replace(".xml", "")); + for (Node node = document.getFirstChild(); node != null; node = node.getNextSibling()) + { + if ("list".equalsIgnoreCase(node.getNodeName())) + { + final BuyListHolder buyList = new BuyListHolder(buyListId); + for (Node list_node = node.getFirstChild(); list_node != null; list_node = list_node.getNextSibling()) + { + if ("item".equalsIgnoreCase(list_node.getNodeName())) + { + int itemId = -1; + int price = -1; + long restockDelay = -1; + int count = -1; + final NamedNodeMap attrs = list_node.getAttributes(); + Node attr = attrs.getNamedItem("id"); + itemId = Integer.parseInt(attr.getNodeValue()); + attr = attrs.getNamedItem("price"); + if (attr != null) + { + price = Integer.parseInt(attr.getNodeValue()); + } + + attr = attrs.getNamedItem("restock_delay"); + if (attr != null) + { + restockDelay = Long.parseLong(attr.getNodeValue()); + } + + attr = attrs.getNamedItem("count"); + if (attr != null) + { + count = Integer.parseInt(attr.getNodeValue()); + } + + final ItemTemplate item = ItemData.getInstance().getTemplate(itemId); + if (item != null) + { + final int sellPrice = item.getReferencePrice() / 2; + if (GeneralConfig.CORRECT_PRICES && (price > -1) && (sellPrice > price) && (buyList.getNpcsAllowed() != null)) + { + LOGGER.warning("Buy price " + price + " is less than sell price " + sellPrice + " for ItemID:" + itemId + " of buylist " + buyList.getListId() + "."); + buyList.addProduct(new Product(buyList.getListId(), item, sellPrice, restockDelay, count)); + } + else + { + buyList.addProduct(new Product(buyList.getListId(), item, price, restockDelay, count)); + } + } + else + { + LOGGER.warning("Item not found. BuyList:" + buyList.getListId() + " ItemID:" + itemId + " File:" + file.getName()); + } + } + else if ("npcs".equalsIgnoreCase(list_node.getNodeName())) + { + for (Node npcs_node = list_node.getFirstChild(); npcs_node != null; npcs_node = npcs_node.getNextSibling()) + { + if ("npc".equalsIgnoreCase(npcs_node.getNodeName())) + { + buyList.addAllowedNpc(Integer.parseInt(npcs_node.getTextContent())); + } + } + } + } + + _buyLists.put(buyList.getListId(), buyList); + } + } + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Failed to load buyList data from xml File:" + file.getName(), e); + } + } + + @Override + public boolean isValidXmlFile(File file) + { + return (file != null) && file.isFile() && XML_FILE_PATTERN.matcher(file.getName().toLowerCase()).matches(); + } + + public BuyListHolder getBuyList(int listId) + { + return _buyLists.get(listId); + } + + public static BuyListData getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final BuyListData INSTANCE = new BuyListData(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/CategoryData.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/CategoryData.java new file mode 100644 index 0000000..007c5e2 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/CategoryData.java @@ -0,0 +1,128 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.data.xml; + +import java.io.File; +import java.util.EnumMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.w3c.dom.Document; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; + +import org.l2jmobius.commons.util.IXmlReader; +import org.l2jmobius.gameserver.data.enums.CategoryType; + +/** + * @author NosBit, xban1x + */ +public class CategoryData implements IXmlReader +{ + private static final Logger LOGGER = Logger.getLogger(CategoryData.class.getName()); + + private final Map> _categories = new EnumMap<>(CategoryType.class); + + protected CategoryData() + { + load(); + } + + @Override + public void load() + { + _categories.clear(); + parseDatapackFile("data/CategoryData.xml"); + LOGGER.info(getClass().getSimpleName() + ": Loaded " + _categories.size() + " categories."); + } + + @Override + public void parseDocument(Document document, File file) + { + for (Node node = document.getFirstChild(); node != null; node = node.getNextSibling()) + { + if ("list".equalsIgnoreCase(node.getNodeName())) + { + for (Node list_node = node.getFirstChild(); list_node != null; list_node = list_node.getNextSibling()) + { + if ("category".equalsIgnoreCase(list_node.getNodeName())) + { + final NamedNodeMap attrs = list_node.getAttributes(); + final CategoryType categoryType = CategoryType.findByName(attrs.getNamedItem("name").getNodeValue()); + if (categoryType == null) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Can't find category by name :" + attrs.getNamedItem("name").getNodeValue()); + continue; + } + + final Set ids = new HashSet<>(); + for (Node category_node = list_node.getFirstChild(); category_node != null; category_node = category_node.getNextSibling()) + { + if ("id".equalsIgnoreCase(category_node.getNodeName())) + { + ids.add(Integer.parseInt(category_node.getTextContent())); + } + } + + _categories.put(categoryType, ids); + } + } + } + } + } + + /** + * Checks if a specified ID is present in a given category type. + * @param type the category type to check + * @param id the ID to be checked within the category + * @return {@code true} if the ID is in the specified category, {@code false} if the ID is not in the category or if the category was not found + */ + public boolean isInCategory(CategoryType type, int id) + { + final Set category = getCategoryByType(type); + if (category == null) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Can't find category type :" + type); + return false; + } + + return category.contains(id); + } + + /** + * Retrieves the category set associated with the specified category type. + * @param type the category type to retrieve + * @return a {@code Set} containing all IDs within the specified category, or {@code null} if the category does not exist + */ + public Set getCategoryByType(CategoryType type) + { + return _categories.get(type); + } + + public static CategoryData getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final CategoryData INSTANCE = new CategoryData(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/ClassListData.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/ClassListData.java new file mode 100644 index 0000000..f5ac8e4 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/ClassListData.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.data.xml; + +import java.io.File; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Logger; + +import org.w3c.dom.Document; +import org.w3c.dom.NamedNodeMap; + +import org.l2jmobius.commons.util.IXmlReader; +import org.l2jmobius.gameserver.entity.actor.enums.player.PlayerClass; +import org.l2jmobius.gameserver.entity.actor.holders.player.ClassInfoHolder; + +/** + * Loads and manages the list of player classes and their associated information. + * @author Zoey76, Mobius + */ +public class ClassListData implements IXmlReader +{ + private static final Logger LOGGER = Logger.getLogger(ClassListData.class.getName()); + + private final Map _classData = new ConcurrentHashMap<>(); + + protected ClassListData() + { + load(); + } + + @Override + public void load() + { + _classData.clear(); + parseDatapackFile("data/stats/players/classList.xml"); + LOGGER.info(getClass().getSimpleName() + ": Loaded " + _classData.size() + " class data."); + } + + @Override + public void parseDocument(Document document, File file) + { + forEach(document, "list", listNode -> forEach(listNode, "class", classNode -> + { + final NamedNodeMap attrs = classNode.getAttributes(); + final PlayerClass playerClass = PlayerClass.getPlayerClass(parseInteger(attrs, "classId")); + final PlayerClass parentPlayerClass = parseInteger(attrs, "parentClassId", -1) > 0 ? PlayerClass.getPlayerClass(parseInteger(attrs, "parentClassId")) : null; + final String className = parseString(attrs, "name"); + _classData.put(playerClass, new ClassInfoHolder(playerClass, parentPlayerClass, className)); + })); + } + + /** + * Gets the complete map of all player classes and their associated information. + * @return an unmodifiable map containing all class data, where keys are {@link PlayerClass} enum values and values are their corresponding {@link ClassInfoHolder} objects + */ + public Map getClassList() + { + return _classData; + } + + /** + * Gets the class information for a specific {@link PlayerClass}. + * @param playerClass the class identifier as a {@link PlayerClass} enum value + * @return the {@link ClassInfoHolder} containing information about the specified class, or {@code null} if the class is not found + */ + public ClassInfoHolder getClass(PlayerClass playerClass) + { + return _classData.get(playerClass); + } + + /** + * Gets the class information for a specific class ID. + * @param classId the numeric identifier of the class + * @return the {@link ClassInfoHolder} containing information about the specified class, or {@code null} if the class ID is invalid or not found + */ + public ClassInfoHolder getClass(int classId) + { + final PlayerClass id = PlayerClass.getPlayerClass(classId); + return (id != null) ? _classData.get(id) : null; + } + + public static ClassListData getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final ClassListData INSTANCE = new ClassListData(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/CubicData.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/CubicData.java new file mode 100644 index 0000000..ba00d72 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/CubicData.java @@ -0,0 +1,187 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.data.xml; + +import java.io.File; +import java.util.Collections; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Logger; + +import org.w3c.dom.Document; +import org.w3c.dom.Node; + +import org.l2jmobius.commons.util.IXmlReader; +import org.l2jmobius.gameserver.entity.actor.templates.CubicTemplate; +import org.l2jmobius.gameserver.entity.cubic.CubicSkill; +import org.l2jmobius.gameserver.entity.cubic.ICubicConditionHolder; +import org.l2jmobius.gameserver.entity.cubic.conditions.HealthCondition; +import org.l2jmobius.gameserver.entity.cubic.conditions.HpCondition; +import org.l2jmobius.gameserver.entity.cubic.conditions.HpCondition.HpConditionType; +import org.l2jmobius.gameserver.entity.cubic.conditions.RangeCondition; +import org.l2jmobius.gameserver.util.StatSet; + +/** + * Loads cubic templates from the datapack XML definitions.
    + * Provides cubic definitions grouped by cubic ID and cubic level. + *
      + *
    • Parses cubic base data.
    • + *
    • Parses cubic skills and conditions.
    • + *
    • Serves templates to SummonCubic and Cubic runtime.
    • + *
    + * @author UnAfraid + */ +public class CubicData implements IXmlReader +{ + private static final Logger LOGGER = Logger.getLogger(CubicData.class.getName()); + + private volatile Map> _cubics = Collections.emptyMap(); + private volatile Map> _loadingCubics; + + protected CubicData() + { + load(); + } + + @Override + public synchronized void load() + { + final Map> loadedCubics = new ConcurrentHashMap<>(); + _loadingCubics = loadedCubics; + try + { + parseDatapackDirectory("data/stats/cubics", true); + int count = 0; + for (Map levels : loadedCubics.values()) + { + count += levels.size(); + } + + if (count == 0) + { + LOGGER.warning(getClass().getSimpleName() + ": No cubic templates loaded. Keeping previous data."); + return; + } + + _cubics = loadedCubics; + LOGGER.info(getClass().getSimpleName() + ": Loaded " + count + " cubic templates."); + } + finally + { + _loadingCubics = null; + } + } + + @Override + public void parseDocument(Document document, File file) + { + forEach(document, "list", listNode -> forEach(listNode, "cubic", cubicNode -> parseTemplate(cubicNode, new CubicTemplate(new StatSet(parseAttributes(cubicNode)))))); + } + + private void parseTemplate(Node cubicNode, CubicTemplate template) + { + forEach(cubicNode, IXmlReader::isNode, innerNode -> + { + switch (innerNode.getNodeName()) + { + case "conditions": + { + parseConditions(innerNode, template, template); + break; + } + case "skills": + { + parseSkills(innerNode, template); + break; + } + } + }); + + final Map> loadingCubics = _loadingCubics; + if (loadingCubics == null) + { + LOGGER.warning("Skipping cubic template outside loading context. cubicId: " + template.getId() + " level: " + template.getLevel()); + return; + } + + loadingCubics.computeIfAbsent(template.getId(), key -> new ConcurrentHashMap<>()).put(template.getLevel(), template); + } + + private void parseConditions(Node conditionsNode, CubicTemplate template, ICubicConditionHolder holder) + { + forEach(conditionsNode, IXmlReader::isNode, conditionNode -> + { + switch (conditionNode.getNodeName()) + { + case "hp": + { + final HpConditionType type = parseEnum(conditionNode.getAttributes(), HpConditionType.class, "type"); + holder.addCondition(new HpCondition(type, parseInteger(conditionNode.getAttributes(), "percent"))); + break; + } + case "range": + { + holder.addCondition(new RangeCondition(parseInteger(conditionNode.getAttributes(), "value"))); + break; + } + case "healthPercent": + { + final int min = parseInteger(conditionNode.getAttributes(), "min"); + final int max = parseInteger(conditionNode.getAttributes(), "max"); + holder.addCondition(new HealthCondition(min, max)); + break; + } + default: + { + LOGGER.warning("Unsupported cubic condition: " + conditionNode.getNodeName() + " cubicId: " + template.getId() + " level: " + template.getLevel()); + break; + } + } + }); + } + + private void parseSkills(Node skillsNode, CubicTemplate template) + { + forEach(skillsNode, "skill", skillNode -> + { + final CubicSkill skill = new CubicSkill(new StatSet(parseAttributes(skillNode))); + forEach(skillNode, "conditions", conditionsNode -> parseConditions(conditionsNode, template, skill)); + template.addCubicSkill(skill); + }); + } + + /** + * Gets a cubic template by cubic ID and level. + * @param id + * @param level + * @return The cubic template or {@code null}. + */ + public CubicTemplate getCubicTemplate(int id, int level) + { + return _cubics.getOrDefault(id, Collections.emptyMap()).get(level); + } + + public static CubicData getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final CubicData INSTANCE = new CubicData(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/DoorData.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/DoorData.java new file mode 100644 index 0000000..5fb2c01 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/DoorData.java @@ -0,0 +1,258 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.data.xml; + +import java.io.File; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.w3c.dom.Document; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; + +import org.l2jmobius.commons.util.IXmlReader; +import org.l2jmobius.gameserver.entity.Location; +import org.l2jmobius.gameserver.entity.World; +import org.l2jmobius.gameserver.entity.WorldRegion; +import org.l2jmobius.gameserver.entity.actor.instance.Door; +import org.l2jmobius.gameserver.entity.actor.templates.DoorTemplate; +import org.l2jmobius.gameserver.entity.instancezone.Instance; +import org.l2jmobius.gameserver.managers.InstanceManager; +import org.l2jmobius.gameserver.util.StatSet; + +/** + * Loads doors. + * @author JIV, GodKratos, UnAfraid + */ +public class DoorData implements IXmlReader +{ + private static final Map> _groups = new HashMap<>(); + private final Map _doors = new HashMap<>(); + private final Map _templates = new HashMap<>(); + + protected DoorData() + { + load(); + } + + @Override + public void load() + { + _doors.clear(); + _groups.clear(); + parseDatapackFile("data/Doors.xml"); + } + + @Override + public void parseDocument(Document document, File file) + { + for (Node a = document.getFirstChild(); a != null; a = a.getNextSibling()) + { + if ("list".equalsIgnoreCase(a.getNodeName())) + { + for (Node b = a.getFirstChild(); b != null; b = b.getNextSibling()) + { + if ("door".equalsIgnoreCase(b.getNodeName())) + { + final NamedNodeMap attrs = b.getAttributes(); + final StatSet set = new StatSet(); + set.set("baseHpMax", 1); // Avoid doors without HP value created dead due to default value 0 in CreatureTemplate. + for (int i = 0; i < attrs.getLength(); i++) + { + final Node att = attrs.item(i); + set.set(att.getNodeName(), att.getNodeValue()); + } + + makeDoor(set); + _templates.put(set.getInt("id"), set); + } + } + } + } + + LOGGER.info(getClass().getSimpleName() + ": Loaded " + _doors.size() + " doors."); + } + + public void insertCollisionData(StatSet set) + { + int posX; + int posY; + int nodeX; + int nodeY; + int height; + height = set.getInt("height"); + String[] pos = set.getString("node1").split(","); + nodeX = Integer.parseInt(pos[0]); + nodeY = Integer.parseInt(pos[1]); + pos = set.getString("node2").split(","); + posX = Integer.parseInt(pos[0]); + posY = Integer.parseInt(pos[1]); + int collisionRadius = Math.min(Math.abs(nodeX - posX), Math.abs(nodeY - posY)); // (max) radius for movement checks + if (collisionRadius < 20) + { + collisionRadius = 20; + } + + set.set("collisionRadius", collisionRadius); + set.set("collisionHeight", height); + } + + /** + * @param set + */ + private void makeDoor(StatSet set) + { + insertCollisionData(set); + final DoorTemplate template = new DoorTemplate(set); + final Door door = new Door(template); + door.setCurrentHp(door.getMaxHp()); + door.spawnMe(template.getX(), template.getY(), template.getZ()); + _doors.put(door.getId(), door); + } + + public StatSet getDoorTemplate(int doorId) + { + return _templates.get(doorId); + } + + public Door getDoor(int doorId) + { + return _doors.get(doorId); + } + + public static void addDoorGroup(String groupName, int doorId) + { + Set set = _groups.get(groupName); + if (set == null) + { + set = new HashSet<>(); + _groups.put(groupName, set); + } + + set.add(doorId); + } + + public static Set getDoorsByGroup(String groupName) + { + return _groups.get(groupName); + } + + public Collection getDoors() + { + return _doors.values(); + } + + public boolean checkIfDoorsBetween(Location start, Location end, int instanceId) + { + return checkIfDoorsBetween(start.getX(), start.getY(), start.getZ(), end.getX(), end.getY(), end.getZ(), instanceId); + } + + public boolean checkIfDoorsBetween(int x, int y, int z, int tx, int ty, int tz, int instanceId) + { + return checkIfDoorsBetween(x, y, z, tx, ty, tz, instanceId, false); + } + + public boolean checkIfDoorsBetween(int x, int y, int z, int tx, int ty, int tz, int instanceId, boolean doubleFaceCheck) + { + final Collection doors; + if (instanceId < 1) + { + final WorldRegion region = World.getRegion(x, y, z); + if (region != null) + { + doors = region.getDoors(); + } + else + { + doors = null; + } + } + else + { + final Instance instance = InstanceManager.getInstance().getInstance(instanceId); + if (instance != null) + { + doors = instance.getDoors(); + } + else + { + doors = null; + } + } + + if ((doors == null) || doors.isEmpty()) + { + return false; + } + + for (Door doorInst : doors) + { + // Check dead and open. + if ((instanceId != doorInst.getInstanceId()) || doorInst.isDead() || doorInst.isOpen() || !doorInst.checkCollision() || (doorInst.getX(0) == 0)) + { + continue; + } + + boolean intersectFace = false; + for (int i = 0; i < 4; i++) + { + final int j = (i + 1) < 4 ? i + 1 : 0; + + // Lower part of the multiplier fraction, if it is 0 we avoid an error and also know that the lines are parallel. + final int denominator = ((ty - y) * (doorInst.getX(i) - doorInst.getX(j))) - ((tx - x) * (doorInst.getY(i) - doorInst.getY(j))); + if (denominator == 0) + { + continue; + } + + // Multipliers to the equations of the lines. If they are lower than 0 or bigger than 1, we know that segments don't intersect. + final float multiplier1 = (float) (((doorInst.getX(j) - doorInst.getX(i)) * (y - doorInst.getY(i))) - ((doorInst.getY(j) - doorInst.getY(i)) * (x - doorInst.getX(i)))) / denominator; + final float multiplier2 = (float) (((tx - x) * (y - doorInst.getY(i))) - ((ty - y) * (x - doorInst.getX(i)))) / denominator; + if ((multiplier1 >= 0) && (multiplier1 <= 1) && (multiplier2 >= 0) && (multiplier2 <= 1)) + { + final int intersectZ = Math.round(z + (multiplier1 * (tz - z))); + + // Now checking if the resulting point is between door's min and max z. + if ((intersectZ > doorInst.getZMin()) && (intersectZ < doorInst.getZMax())) + { + if (!doubleFaceCheck || intersectFace) + { + return true; + } + + intersectFace = true; + } + } + } + } + + return false; + } + + public static DoorData getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final DoorData INSTANCE = new DoorData(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/DynamicExpRateData.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/DynamicExpRateData.java new file mode 100644 index 0000000..96cf585 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/DynamicExpRateData.java @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.data.xml; + +import java.io.File; +import java.util.Arrays; +import java.util.logging.Logger; + +import org.w3c.dom.Document; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; + +import org.l2jmobius.commons.util.IXmlReader; +import org.l2jmobius.gameserver.config.PlayerConfig; + +/** + * @author MrNiceGuy + */ +public class DynamicExpRateData implements IXmlReader +{ + private static final Logger LOGGER = Logger.getLogger(DynamicExpRateData.class.getName()); + + private static float[] _expRates = new float[PlayerConfig.PLAYER_MAXIMUM_LEVEL + 1]; + private static float[] _spRates = new float[PlayerConfig.PLAYER_MAXIMUM_LEVEL + 1]; + private static boolean _enabled = false; + + protected DynamicExpRateData() + { + load(); + } + + @Override + public void load() + { + parseDatapackFile("config/DynamicExpRates.xml"); + } + + @Override + public void parseDocument(Document document, File file) + { + int count = 0; + Arrays.fill(_expRates, 1f); + Arrays.fill(_spRates, 1f); + + for (Node n = document.getFirstChild(); n != null; n = n.getNextSibling()) + { + if ("list".equals(n.getNodeName())) + { + for (Node d = n.getFirstChild(); d != null; d = d.getNextSibling()) + { + if ("dynamic".equals(d.getNodeName())) + { + final NamedNodeMap attrs = d.getAttributes(); + final int level = parseInteger(attrs, "level"); + final float exp = parseFloat(attrs, "exp"); + final float sp = parseFloat(attrs, "sp"); + if ((exp != 1) || (sp != 1)) + { + _expRates[level] = exp; + _spRates[level] = sp; + count++; + } + } + } + } + } + + _enabled = count > 0; + if (_enabled) + { + LOGGER.info(getClass().getSimpleName() + ": Loaded dynamic rates for " + count + " levels."); + } + } + + /** + * @param level + * @return the dynamic EXP rate for specified level. + */ + public float getDynamicExpRate(int level) + { + return _expRates[level]; + } + + /** + * @param level + * @return the dynamic SP rate for specified level. + */ + public float getDynamicSpRate(int level) + { + return _spRates[level]; + } + + /** + * @return if dynamic rates are enabled. + */ + public boolean isEnabled() + { + return _enabled; + } + + public static DynamicExpRateData getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final DynamicExpRateData INSTANCE = new DynamicExpRateData(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/EnchantItemData.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/EnchantItemData.java new file mode 100644 index 0000000..b34fa0a --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/EnchantItemData.java @@ -0,0 +1,140 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.data.xml; + +import java.io.File; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Level; + +import org.w3c.dom.Document; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; + +import org.l2jmobius.commons.util.IXmlReader; +import org.l2jmobius.gameserver.entity.item.enchant.EnchantScroll; +import org.l2jmobius.gameserver.entity.item.instance.Item; +import org.l2jmobius.gameserver.util.StatSet; + +/** + * Loads item enchant data. + * @author UnAfraid + */ +public class EnchantItemData implements IXmlReader +{ + private final Map _scrolls = new HashMap<>(); + + /** + * Instantiates a new enchant item data. + */ + public EnchantItemData() + { + load(); + } + + @Override + public synchronized void load() + { + _scrolls.clear(); + parseDatapackFile("data/EnchantItemData.xml"); + LOGGER.info(getClass().getSimpleName() + ": Loaded " + _scrolls.size() + " enchant scrolls."); + } + + @Override + public void parseDocument(Document document, File file) + { + StatSet set; + Node att; + NamedNodeMap attrs; + for (Node n = document.getFirstChild(); n != null; n = n.getNextSibling()) + { + if ("list".equalsIgnoreCase(n.getNodeName())) + { + for (Node d = n.getFirstChild(); d != null; d = d.getNextSibling()) + { + if ("enchant".equalsIgnoreCase(d.getNodeName())) + { + attrs = d.getAttributes(); + set = new StatSet(); + for (int i = 0; i < attrs.getLength(); i++) + { + att = attrs.item(i); + set.set(att.getNodeName(), att.getNodeValue()); + } + + try + { + final EnchantScroll item = new EnchantScroll(set); + for (Node cd = d.getFirstChild(); cd != null; cd = cd.getNextSibling()) + { + if ("item".equalsIgnoreCase(cd.getNodeName())) + { + item.addItem(parseInteger(cd.getAttributes(), "id"), parseInteger(cd.getAttributes(), "altScrollGroupId", -1)); + } + } + + _scrolls.put(item.getId(), item); + } + catch (NullPointerException e) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Unexistent enchant scroll: " + set.getString("id") + " defined in enchant data!"); + } + catch (IllegalAccessError e) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Wrong enchant scroll item type: " + set.getString("id") + " defined in enchant data!"); + } + } + } + } + } + } + + public Collection getScrolls() + { + return _scrolls.values(); + } + + /** + * Gets the enchant scroll. + * @param item the scroll item + * @return enchant template for scroll + */ + public EnchantScroll getEnchantScroll(Item item) + { + if (item == null) + { + return null; + } + + return _scrolls.get(item.getId()); + } + + /** + * Gets the single instance of EnchantItemData. + * @return single instance of EnchantItemData + */ + public static EnchantItemData getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final EnchantItemData INSTANCE = new EnchantItemData(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/EnchantItemGroupsData.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/EnchantItemGroupsData.java new file mode 100644 index 0000000..db0f34a --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/EnchantItemGroupsData.java @@ -0,0 +1,261 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.data.xml; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Level; + +import org.w3c.dom.Document; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; + +import org.l2jmobius.commons.util.IXmlReader; +import org.l2jmobius.commons.util.StringUtil; +import org.l2jmobius.gameserver.config.PlayerConfig; +import org.l2jmobius.gameserver.data.holders.RangeChanceHolder; +import org.l2jmobius.gameserver.entity.item.ItemTemplate; +import org.l2jmobius.gameserver.entity.item.enchant.EnchantItemGroup; +import org.l2jmobius.gameserver.entity.item.enchant.EnchantRateItem; +import org.l2jmobius.gameserver.entity.item.enchant.EnchantScrollGroup; +import org.l2jmobius.gameserver.entity.item.enums.BodyPart; + +/** + * @author UnAfraid + */ +public class EnchantItemGroupsData implements IXmlReader +{ + private final Map _itemGroups = new HashMap<>(); + private final Map _scrollGroups = new HashMap<>(); + private int _maxWeaponEnchant = 0; + private int _maxArmorEnchant = 0; + private int _maxAccessoryEnchant = 0; + + protected EnchantItemGroupsData() + { + load(); + } + + @Override + public synchronized void load() + { + _itemGroups.clear(); + _scrollGroups.clear(); + parseDatapackFile("data/EnchantItemGroups.xml"); + LOGGER.log(Level.INFO, getClass().getSimpleName() + ": Loaded " + _itemGroups.size() + " item group templates."); + LOGGER.log(Level.INFO, getClass().getSimpleName() + ": Loaded " + _scrollGroups.size() + " scroll group templates."); + + if (PlayerConfig.OVER_ENCHANT_PROTECTION) + { + LOGGER.info(getClass().getSimpleName() + ": Max weapon enchant is set to " + _maxWeaponEnchant + "."); + LOGGER.info(getClass().getSimpleName() + ": Max armor enchant is set to " + _maxArmorEnchant + "."); + LOGGER.info(getClass().getSimpleName() + ": Max accessory enchant is set to " + _maxAccessoryEnchant + "."); + } + } + + @Override + public void parseDocument(Document document, File file) + { + for (Node n = document.getFirstChild(); n != null; n = n.getNextSibling()) + { + if ("list".equalsIgnoreCase(n.getNodeName())) + { + for (Node d = n.getFirstChild(); d != null; d = d.getNextSibling()) + { + if ("enchantRateGroup".equalsIgnoreCase(d.getNodeName())) + { + final String name = parseString(d.getAttributes(), "name"); + final EnchantItemGroup group = new EnchantItemGroup(name); + for (Node cd = d.getFirstChild(); cd != null; cd = cd.getNextSibling()) + { + if ("current".equalsIgnoreCase(cd.getNodeName())) + { + final String range = parseString(cd.getAttributes(), "enchant"); + final double chance = parseDouble(cd.getAttributes(), "chance"); + int min = -1; + int max = 0; + if (range.contains("-")) + { + final String[] split = range.split("-"); + if ((split.length == 2) && StringUtil.isNumeric(split[0]) && StringUtil.isNumeric(split[1])) + { + min = Integer.parseInt(split[0]); + max = Integer.parseInt(split[1]); + } + } + else if (StringUtil.isNumeric(range)) + { + min = Integer.parseInt(range); + max = min; + } + + if ((min > -1) && (max > -1)) + { + group.addChance(new RangeChanceHolder(min, max, chance)); + } + + // Try to get a generic max value. + if (chance > 0) + { + if (name.contains("WEAPON")) + { + if (_maxWeaponEnchant < max) + { + _maxWeaponEnchant = max; + } + } + else if (name.contains("ACCESSORIES") || name.contains("RING") || name.contains("EARRING") || name.contains("NECK")) + { + if (_maxAccessoryEnchant < max) + { + _maxAccessoryEnchant = max; + } + } + else if (_maxArmorEnchant < max) + { + _maxArmorEnchant = max; + } + } + } + } + + _itemGroups.put(name, group); + } + else if ("enchantScrollGroup".equals(d.getNodeName())) + { + final int id = parseInteger(d.getAttributes(), "id"); + final EnchantScrollGroup group = new EnchantScrollGroup(id); + for (Node cd = d.getFirstChild(); cd != null; cd = cd.getNextSibling()) + { + if ("enchantRate".equalsIgnoreCase(cd.getNodeName())) + { + final EnchantRateItem rateGroup = new EnchantRateItem(parseString(cd.getAttributes(), "group")); + for (Node z = cd.getFirstChild(); z != null; z = z.getNextSibling()) + { + if ("item".equals(z.getNodeName())) + { + final NamedNodeMap attrs = z.getAttributes(); + if (attrs.getNamedItem("slot") != null) + { + rateGroup.addSlot(BodyPart.fromName(parseString(attrs, "slot")).getMask()); + } + + if (attrs.getNamedItem("magicWeapon") != null) + { + rateGroup.setMagicWeapon(parseBoolean(attrs, "magicWeapon")); + } + + if (attrs.getNamedItem("itemId") != null) + { + rateGroup.addItemId(parseInteger(attrs, "itemId")); + } + } + } + + group.addRateGroup(rateGroup); + } + } + + _scrollGroups.put(id, group); + } + } + } + } + + // In case there is no accessories group set. + if (_maxAccessoryEnchant == 0) + { + _maxAccessoryEnchant = _maxArmorEnchant; + } + + // Max enchant values are set to current max enchant + 1. + _maxWeaponEnchant += 1; + _maxArmorEnchant += 1; + _maxAccessoryEnchant += 1; + } + + /** + * Retrieves the enchant item group associated with the specified item template and scroll group. + * @param item the {@link ItemTemplate} representing the item for which the enchant group is requested. + * @param scrollGroup the scroll group ID to match with the item's group. + * @return the {@link EnchantItemGroup} associated with the item and scroll group, or {@code null} if not found. + */ + public EnchantItemGroup getItemGroup(ItemTemplate item, int scrollGroup) + { + final EnchantScrollGroup group = _scrollGroups.get(scrollGroup); + final EnchantRateItem rateGroup = group.getRateGroup(item); + return rateGroup != null ? _itemGroups.get(rateGroup.getName()) : null; + } + + /** + * Retrieves the enchant item group by its name. + * @param name the name of the enchant item group. + * @return the {@link EnchantItemGroup} associated with the specified name, or {@code null} if not found. + */ + public EnchantItemGroup getItemGroup(String name) + { + return _itemGroups.get(name); + } + + /** + * Retrieves the enchant scroll group by its ID. + * @param id the ID of the scroll group. + * @return the {@link EnchantScrollGroup} associated with the specified ID, or {@code null} if not found. + */ + public EnchantScrollGroup getScrollGroup(int id) + { + return _scrollGroups.get(id); + } + + /** + * Gets the maximum weapon enchant level allowed. + * @return the maximum weapon enchant level. + */ + public int getMaxWeaponEnchant() + { + return _maxWeaponEnchant; + } + + /** + * Gets the maximum armor enchant level allowed. + * @return the maximum armor enchant level. + */ + public int getMaxArmorEnchant() + { + return _maxArmorEnchant; + } + + /** + * Gets the maximum accessory enchant level allowed. + * @return the maximum accessory enchant level. + */ + public int getMaxAccessoryEnchant() + { + return _maxAccessoryEnchant; + } + + public static EnchantItemGroupsData getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final EnchantItemGroupsData INSTANCE = new EnchantItemGroupsData(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/EnchantItemHPBonusData.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/EnchantItemHPBonusData.java new file mode 100644 index 0000000..e3b8a7d --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/EnchantItemHPBonusData.java @@ -0,0 +1,161 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.data.xml; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collection; +import java.util.EnumMap; +import java.util.List; +import java.util.Map; + +import org.w3c.dom.Document; +import org.w3c.dom.Node; + +import org.l2jmobius.commons.util.IXmlReader; +import org.l2jmobius.gameserver.entity.item.ItemTemplate; +import org.l2jmobius.gameserver.entity.item.enums.BodyPart; +import org.l2jmobius.gameserver.entity.item.instance.Item; +import org.l2jmobius.gameserver.entity.item.type.CrystalType; +import org.l2jmobius.gameserver.mechanics.stats.Stat; +import org.l2jmobius.gameserver.mechanics.stats.functions.FuncTemplate; +import org.l2jmobius.gameserver.mechanics.stats.functions.StatFunction; + +/** + * This class holds the Enchant HP Bonus Data. + * @author MrPoke, Zoey76 + */ +public class EnchantItemHPBonusData implements IXmlReader +{ + private final Map> _armorHPBonuses = new EnumMap<>(CrystalType.class); + + private static final float FULL_ARMOR_MODIFIER = 1.5f; // TODO: Move it to config! + + /** + * Instantiates a new enchant hp bonus data. + */ + protected EnchantItemHPBonusData() + { + load(); + } + + @Override + public void parseDocument(Document document, File file) + { + for (Node n = document.getFirstChild(); n != null; n = n.getNextSibling()) + { + if ("list".equalsIgnoreCase(n.getNodeName())) + { + for (Node d = n.getFirstChild(); d != null; d = d.getNextSibling()) + { + if ("enchantHP".equalsIgnoreCase(d.getNodeName())) + { + final List bonuses = new ArrayList<>(12); + for (Node e = d.getFirstChild(); e != null; e = e.getNextSibling()) + { + if ("bonus".equalsIgnoreCase(e.getNodeName())) + { + bonuses.add(Integer.parseInt(e.getTextContent())); + } + } + + _armorHPBonuses.put(parseEnum(d.getAttributes(), CrystalType.class, "grade"), bonuses); + } + } + } + } + + if (!_armorHPBonuses.isEmpty()) + { + final ItemData it = ItemData.getInstance(); + + // Armors + final Collection armorIds = it.getAllArmorsId(); + for (Integer itemId : armorIds) + { + final ItemTemplate item = it.getTemplate(itemId); + if ((item != null) && (item.getCrystalType() != CrystalType.NONE)) + { + switch (item.getBodyPart()) + { + case BodyPart.CHEST: + case BodyPart.FEET: + case BodyPart.GLOVES: + case BodyPart.HEAD: + case BodyPart.LEGS: + case BodyPart.BACK: + case BodyPart.FULL_ARMOR: + case BodyPart.UNDERWEAR: + case BodyPart.L_HAND: + { + item.attach(new FuncTemplate(null, null, StatFunction.ENCHANTHP.getName(), -1, Stat.MAX_HP, 0)); + break; + } + default: + { + break; + } + } + } + } + } + } + + @Override + public void load() + { + _armorHPBonuses.clear(); + parseDatapackFile("data/stats/enchantHPBonus.xml"); + LOGGER.info(getClass().getSimpleName() + ": Loaded " + _armorHPBonuses.size() + " enchant HP bonuses."); + } + + /** + * Gets the HP bonus. + * @param item the item + * @return the HP bonus + */ + public int getHPBonus(Item item) + { + final List values = _armorHPBonuses.get(item.getTemplate().getCrystalTypePlus()); + if ((values == null) || values.isEmpty() || (item.getOlyEnchantLevel() <= 0)) + { + return 0; + } + + final int bonus = values.get(Math.min(item.getOlyEnchantLevel(), values.size()) - 1); + if (item.getTemplate().getBodyPart() == BodyPart.FULL_ARMOR) + { + return (int) (bonus * FULL_ARMOR_MODIFIER); + } + + return bonus; + } + + /** + * Gets the single instance of EnchantHPBonusData. + * @return single instance of EnchantHPBonusData + */ + public static EnchantItemHPBonusData getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final EnchantItemHPBonusData INSTANCE = new EnchantItemHPBonusData(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/EnchantSkillTreeData.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/EnchantSkillTreeData.java new file mode 100644 index 0000000..39096d2 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/EnchantSkillTreeData.java @@ -0,0 +1,212 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.data.xml; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.logging.Logger; + +import org.w3c.dom.Document; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; + +import org.l2jmobius.commons.util.IXmlReader; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.mechanics.skill.Skill; +import org.l2jmobius.gameserver.mechanics.skill.holders.EnchantSkillLearn; + +/** + * @author Mobius + */ +public class EnchantSkillTreeData implements IXmlReader +{ + private static final Logger LOGGER = Logger.getLogger(EnchantSkillTreeData.class.getName()); + + private final List _enchantSkillTrees = new ArrayList<>(); + + protected EnchantSkillTreeData() + { + load(); + } + + @Override + public void load() + { + _enchantSkillTrees.clear(); + parseDatapackFile("data/EnchantSkillTreeData.xml"); + } + + @Override + public void parseDocument(Document document, File file) + { + int prevSkillId = -1; + + for (Node n = document.getFirstChild(); n != null; n = n.getNextSibling()) + { + if ("list".equals(n.getNodeName())) + { + for (Node d = n.getFirstChild(); d != null; d = d.getNextSibling()) + { + if ("skill".equals(d.getNodeName())) + { + final NamedNodeMap attrs = d.getAttributes(); + final int id = parseInteger(attrs, "id"); + final int level = parseInteger(attrs, "level"); + final String name = parseString(attrs, "name"); + final int baseLevel = parseInteger(attrs, "baseLevel"); + final int minskillLevel = parseInteger(attrs, "minSkillLevel"); + final int sp = parseInteger(attrs, "sp"); + final int exp = parseInteger(attrs, "exp"); + final byte rate76 = parseByte(attrs, "rate76"); + final byte rate77 = parseByte(attrs, "rate77"); + final byte rate78 = parseByte(attrs, "rate78"); + final byte rate79 = parseByte(attrs, "rate79"); + final byte rate80 = parseByte(attrs, "rate80"); + if (prevSkillId != id) + { + prevSkillId = id; + } + + _enchantSkillTrees.add(new EnchantSkillLearn(id, level, minskillLevel, baseLevel, name, sp, exp, rate76, rate77, rate78, rate79, rate80)); + } + } + } + } + + LOGGER.info(getClass().getSimpleName() + ": Loaded " + _enchantSkillTrees.size() + " enchant skills."); + } + + public List getAvailableEnchantSkills(Player player) + { + if (player.getLevel() < 76) + { + return Collections.emptyList(); + } + + final List result = new ArrayList<>(); + final List skills = new ArrayList<>(); + skills.addAll(_enchantSkillTrees); + + final Collection knownSkills = player.getAllSkills(); + for (EnchantSkillLearn skillLearn : skills) + { + SEARCH: for (Skill skill : knownSkills) + { + if (skill.getId() == skillLearn.getId()) + { + if (skill.getLevel() == skillLearn.getMinSkillLevel()) + { + // This is the next level of a skill that we know. + result.add(skillLearn); + } + break SEARCH; + } + } + } + + return result; + } + + public int getSkillSpCost(Player player, Skill skill) + { + int skillCost = 100000000; + for (EnchantSkillLearn enchantSkillLearn : getAvailableEnchantSkills(player)) + { + if (enchantSkillLearn.getId() != skill.getId()) + { + continue; + } + + if (enchantSkillLearn.getLevel() != skill.getLevel()) + { + continue; + } + + if (76 > player.getLevel()) + { + continue; + } + + skillCost = enchantSkillLearn.getSpCost(); + } + + return skillCost; + } + + public int getSkillExpCost(Player player, Skill skill) + { + int skillCost = 100000000; + for (EnchantSkillLearn enchantSkillLearn : getAvailableEnchantSkills(player)) + { + if (enchantSkillLearn.getId() != skill.getId()) + { + continue; + } + + if (enchantSkillLearn.getLevel() != skill.getLevel()) + { + continue; + } + + if (76 > player.getLevel()) + { + continue; + } + + skillCost = enchantSkillLearn.getExp(); + } + + return skillCost; + } + + public byte getSkillRate(Player player, Skill skill) + { + for (EnchantSkillLearn enchantSkillLearn : getAvailableEnchantSkills(player)) + { + if (enchantSkillLearn.getId() != skill.getId()) + { + continue; + } + + if (enchantSkillLearn.getLevel() != skill.getLevel()) + { + continue; + } + + return enchantSkillLearn.getRate(player); + } + + return 0; + } + + public static EnchantSkillTreeData getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final EnchantSkillTreeData INSTANCE = new EnchantSkillTreeData(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/ExperienceData.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/ExperienceData.java new file mode 100644 index 0000000..303c215 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/ExperienceData.java @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.data.xml; + +import java.io.File; +import java.util.logging.Logger; + +import org.w3c.dom.Document; +import org.w3c.dom.NamedNodeMap; + +import org.l2jmobius.commons.util.IXmlReader; +import org.l2jmobius.gameserver.config.PlayerConfig; + +/** + * This class holds the Experience points for each level for players and pets. + *

    + * It provides functionality to load experience data from an XML file and retrieve the experience points required for a given level. It also maintains the maximum levels for players and pets. + *

    + *

    + * XML Structure: + *

    + * The XML file must have a root element containing the `maxLevel` and `maxPetLevel` attributes, along with nested `experience` elements that define the experience points for each level. + * @author Mobius + */ +public class ExperienceData implements IXmlReader +{ + private static final Logger LOGGER = Logger.getLogger(ExperienceData.class.getName()); + + private long[] _expTable; + + private byte _maxLevel; + private byte _maxPetLevel; + + protected ExperienceData() + { + load(); + } + + @Override + public void load() + { + parseDatapackFile("data/stats/players/experience.xml"); + LOGGER.info(getClass().getSimpleName() + ": Loaded " + (_expTable.length - 1) + " levels."); + LOGGER.info(getClass().getSimpleName() + ": Max Player Level is " + (_maxLevel - 1) + "."); + LOGGER.info(getClass().getSimpleName() + ": Max Pet Level is " + (_maxPetLevel - 1) + "."); + } + + @Override + public void parseDocument(Document document, File file) + { + forEach(document, tableNode -> + { + final NamedNodeMap tableAttr = tableNode.getAttributes(); + _maxLevel = (byte) (Byte.parseByte(tableAttr.getNamedItem("maxLevel").getNodeValue()) + 1); + _maxPetLevel = (byte) (Byte.parseByte(tableAttr.getNamedItem("maxPetLevel").getNodeValue()) + 1); + if (_maxLevel > PlayerConfig.PLAYER_MAXIMUM_LEVEL) + { + _maxLevel = PlayerConfig.PLAYER_MAXIMUM_LEVEL; + } + + if (_maxPetLevel > (_maxLevel + 1)) + { + _maxPetLevel = (byte) (_maxLevel + 1); // Pet level should not exceed owner level. + } + + // Initialize the array with size _maxLevel + 1 (level 0 is unused). + _expTable = new long[_maxLevel + 1]; + + forEach(tableNode, "experience", experienceNode -> + { + final NamedNodeMap attrs = experienceNode.getAttributes(); + final int level = parseInteger(attrs, "level"); + if ((level > PlayerConfig.PLAYER_MAXIMUM_LEVEL) || (level > _maxLevel)) + { + return; + } + + _expTable[level] = parseLong(attrs, "tolevel"); + }); + }); + } + + /** + * Retrieves the experience points required to reach the specified level. + * @param level the level for which experience points are required + * @return the experience points needed to reach the specified level; if the specified level exceeds {@code Config.PLAYER_MAXIMUM_LEVEL}, returns the experience points for {@code Config.PLAYER_MAXIMUM_LEVEL} + */ + public long getExpForLevel(int level) + { + if (level > PlayerConfig.PLAYER_MAXIMUM_LEVEL) + { + return _expTable[PlayerConfig.PLAYER_MAXIMUM_LEVEL]; + } + + return _expTable[level]; + } + + /** + * Returns the maximum level acquirable by a player in the game. + * @return the maximum player level as an integer + */ + public byte getMaxLevel() + { + return _maxLevel; + } + + /** + * Returns the maximum level acquirable by a pet in the game. + * @return the maximum pet level as an integer, limited by the player level + */ + public byte getMaxPetLevel() + { + return _maxPetLevel; + } + + public static ExperienceData getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final ExperienceData INSTANCE = new ExperienceData(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/ExperienceLossData.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/ExperienceLossData.java new file mode 100644 index 0000000..990c917 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/ExperienceLossData.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.data.xml; + +import java.io.File; +import java.util.Arrays; +import java.util.logging.Logger; + +import org.w3c.dom.Document; +import org.w3c.dom.NamedNodeMap; + +import org.l2jmobius.commons.util.IXmlReader; +import org.l2jmobius.gameserver.config.PlayerConfig; + +/** + * @author Mobius + */ +public class ExperienceLossData implements IXmlReader +{ + private static final Logger LOGGER = Logger.getLogger(ExperienceLossData.class.getName()); + + private final double[] _levelPercentLost = new double[PlayerConfig.PLAYER_MAXIMUM_LEVEL]; + + protected ExperienceLossData() + { + load(); + } + + @Override + public void load() + { + Arrays.fill(_levelPercentLost, 1d); + parseDatapackFile("data/stats/players/experienceLoss.xml"); + LOGGER.info(getClass().getSimpleName() + ": Loaded " + (_levelPercentLost.length - 1) + " levels."); + } + + @Override + public void parseDocument(Document document, File file) + { + forEach(document, "list", listNode -> forEach(listNode, "experience", experienceNode -> + { + final NamedNodeMap attrs = experienceNode.getAttributes(); + final int level = parseInteger(attrs, "level"); + if (level >= PlayerConfig.PLAYER_MAXIMUM_LEVEL) + { + return; + } + + _levelPercentLost[level] = parseDouble(attrs, "val"); + })); + } + + /** + * Retrieves the experience point (XP) percentage lost for a specified level. + * @param level the level for which to retrieve the XP loss percentage. + * @return the XP loss percentage for the specified level. + */ + public double getPercentLost(int level) + { + if (level >= PlayerConfig.PLAYER_MAXIMUM_LEVEL) + { + LOGGER.warning(getClass().getSimpleName() + ": Requested too high level (" + level + ")."); + return _levelPercentLost[_levelPercentLost.length - 1]; + } + + return _levelPercentLost[level]; + } + + public static ExperienceLossData getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final ExperienceLossData INSTANCE = new ExperienceLossData(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/FakePlayerData.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/FakePlayerData.java new file mode 100644 index 0000000..b3ad6c1 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/FakePlayerData.java @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.data.xml; + +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Logger; + +import org.l2jmobius.gameserver.config.custom.FakePlayersConfig; + +/** + * @author Mobius + */ +public class FakePlayerData +{ + private static final Logger LOGGER = Logger.getLogger(FakePlayerData.class.getName()); + + private final Map _fakePlayerNames = new ConcurrentHashMap<>(); + private final Map _fakePlayerIds = new ConcurrentHashMap<>(); + private final Set _talkableFakePlayerNames = ConcurrentHashMap.newKeySet(); + + protected FakePlayerData() + { + } + + public void report() + { + if (FakePlayersConfig.FAKE_PLAYERS_ENABLED) + { + LOGGER.info(getClass().getSimpleName() + ": Loaded " + _fakePlayerIds.size() + " templates."); + } + else + { + LOGGER.info(getClass().getSimpleName() + ": Disabled."); + } + } + + /** + * Adds a mapping between a fake player name and its corresponding NPC ID. + * @param name the name of the fake player + * @param npcId the NPC ID to associate with the fake player + */ + public void addFakePlayerId(String name, int npcId) + { + _fakePlayerIds.put(name, npcId); + } + + /** + * Retrieves the NPC ID associated with the given fake player name. + * @param name the name of the fake player + * @return the NPC ID corresponding to the given name, or {@code null} if no match is found + */ + public int getNpcIdByName(String name) + { + return _fakePlayerIds.get(name); + } + + /** + * Adds a mapping between a lowercase version of a fake player name and its properly formatted name. + * @param lowercaseName the lowercase version of the fake player name + * @param name the properly formatted name of the fake player + */ + public void addFakePlayerName(String lowercaseName, String name) + { + _fakePlayerNames.put(lowercaseName, name); + } + + /** + * Retrieves the properly formatted name of a fake player, given a case-insensitive name lookup. + * @param name the name of the fake player (case-insensitive) + * @return the correctly formatted name of the fake player, or {@code null} if no match is found + */ + public String getProperName(String name) + { + return _fakePlayerNames.get(name.toLowerCase()); + } + + /** + * Adds a fake player name to the set of talkable fake player names. + * @param lowercaseName the lowercase version of the fake player name + */ + public void addTalkableFakePlayerName(String lowercaseName) + { + _talkableFakePlayerNames.add(lowercaseName); + } + + /** + * Checks if a fake player with the given name is marked as talkable. + * @param name the name of the fake player (case-insensitive) + * @return {@code true} if the fake player with the given name is talkable, {@code false} otherwise + */ + public boolean isTalkable(String name) + { + return _talkableFakePlayerNames.contains(name.toLowerCase()); + } + + public static FakePlayerData getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final FakePlayerData INSTANCE = new FakePlayerData(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/FenceData.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/FenceData.java new file mode 100644 index 0000000..41317cc --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/FenceData.java @@ -0,0 +1,234 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.data.xml; + +import java.io.File; +import java.util.Collection; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Logger; + +import org.w3c.dom.Document; +import org.w3c.dom.Node; + +import org.l2jmobius.commons.util.IXmlReader; +import org.l2jmobius.gameserver.entity.World; +import org.l2jmobius.gameserver.entity.WorldRegion; +import org.l2jmobius.gameserver.entity.actor.enums.creature.FenceState; +import org.l2jmobius.gameserver.entity.actor.instance.Fence; +import org.l2jmobius.gameserver.util.StatSet; + +/** + * @author HorridoJoho, BazookaRpm + */ +public class FenceData implements IXmlReader +{ + private static final Logger LOGGER = Logger.getLogger(FenceData.class.getSimpleName()); + + private static final int MAX_Z_DIFF = 100; + + private final Map _fences = new ConcurrentHashMap<>(); + + protected FenceData() + { + load(); + } + + @Override + public void load() + { + if (!_fences.isEmpty()) + { + // Remove old fences when reloading. + _fences.values().forEach(this::removeFence); + } + + parseDatapackFile("data/FenceData.xml"); + LOGGER.info(getClass().getSimpleName() + ": Loaded " + _fences.size() + " fences."); + } + + @Override + public void parseDocument(Document document, File file) + { + forEach(document, "list", listNode -> forEach(listNode, "fence", this::spawnFence)); + } + + public int getLoadedElementsCount() + { + return _fences.size(); + } + + private void spawnFence(Node fenceNode) + { + final StatSet set = new StatSet(parseAttributes(fenceNode)); + spawnFence(set.getInt("x"), set.getInt("y"), set.getInt("z"), set.getString("name"), set.getInt("width"), set.getInt("length"), set.getInt("height"), 0, set.getEnum("state", FenceState.class, FenceState.CLOSED)); + } + + public Fence spawnFence(int x, int y, int z, int width, int length, int height, int instanceId, FenceState state) + { + return spawnFence(x, y, z, null, width, length, height, instanceId, state); + } + + public Fence spawnFence(int x, int y, int z, String name, int width, int length, int height, int instanceId, FenceState state) + { + final Fence fence = new Fence(x, y, name, width, length, height, state); + if (instanceId > 0) + { + fence.setInstanceId(instanceId); + } + + fence.spawnMe(x, y, z); + addFence(fence); + + return fence; + } + + private void addFence(Fence fence) + { + _fences.put(fence.getObjectId(), fence); + } + + public void removeFence(Fence fence) + { + _fences.remove(fence.getObjectId()); + } + + public Map getFences() + { + return _fences; + } + + public Fence getFence(int objectId) + { + return _fences.get(objectId); + } + + public boolean checkIfFenceBetween(int x, int y, int z, int tx, int ty, int tz, int instanceId) + { + final WorldRegion region = World.getRegion(x, y, z); + final Collection fences = region != null ? region.getFences() : null; + if ((fences == null) || fences.isEmpty()) + { + return false; + } + + for (Fence fence : fences) + { + // Check if fence is geodata enabled. + if (!fence.getState().isGeodataEnabled()) + { + continue; + } + + // Check if fence is within the instance we search for. + if (fence.getInstanceId() != instanceId) + { + continue; + } + + final int xMin = fence.getXMin(); + if ((x < xMin) && (tx < xMin)) + { + continue; + } + + final int xMax = fence.getXMax(); + if ((x > xMax) && (tx > xMax)) + { + continue; + } + + final int yMin = fence.getYMin(); + if ((y < yMin) && (ty < yMin)) + { + continue; + } + + final int yMax = fence.getYMax(); + if ((y > yMax) && (ty > yMax)) + { + continue; + } + + if ((x > xMin) && (tx > xMin) && (x < xMax) && (tx < xMax) && (y > yMin) && (ty > yMin) && (y < yMax) && (ty < yMax)) + { + continue; + } + + if ((crossLinePart(xMin, yMin, xMax, yMin, x, y, tx, ty, xMin, yMin, xMax, yMax) || crossLinePart(xMax, yMin, xMax, yMax, x, y, tx, ty, xMin, yMin, xMax, yMax) || crossLinePart(xMax, yMax, xMin, yMax, x, y, tx, ty, xMin, yMin, xMax, yMax) || crossLinePart(xMin, yMax, xMin, yMin, x, y, tx, ty, xMin, yMin, xMax, yMax)) && (z > (fence.getZ() - MAX_Z_DIFF)) && (z < (fence.getZ() + MAX_Z_DIFF))) + { + return true; + } + } + + return false; + } + + private boolean crossLinePart(double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4, double xMin, double yMin, double xMax, double yMax) + { + final double[] result = intersection(x1, y1, x2, y2, x3, y3, x4, y4); + if (result == null) + { + return false; + } + + final double xCross = result[0]; + if ((xCross <= xMax) && (xCross >= xMin)) + { + return true; + } + + final double yCross = result[1]; + if ((yCross <= yMax) && (yCross >= yMin)) + { + return true; + } + + return false; + } + + private double[] intersection(double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4) + { + final double d = ((x1 - x2) * (y3 - y4)) - ((y1 - y2) * (x3 - x4)); + if (d == 0) + { + return null; + } + + final double xi = (((x3 - x4) * ((x1 * y2) - (y1 * x2))) - ((x1 - x2) * ((x3 * y4) - (y3 * x4)))) / d; + final double yi = (((y3 - y4) * ((x1 * y2) - (y1 * x2))) - ((y1 - y2) * ((x3 * y4) - (y3 * x4)))) / d; + return new double[] + { + xi, + yi + }; + } + + public static FenceData getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final FenceData INSTANCE = new FenceData(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/FishData.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/FishData.java new file mode 100644 index 0000000..0c3ffe3 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/FishData.java @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.data.xml; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.w3c.dom.Document; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; + +import org.l2jmobius.commons.util.IXmlReader; +import org.l2jmobius.gameserver.mechanics.fishing.Fish; +import org.l2jmobius.gameserver.util.StatSet; + +/** + * This class holds the Fish information. + * @author nonom + */ +public class FishData implements IXmlReader +{ + private final Map _fishNormal = new HashMap<>(); + private final Map _fishEasy = new HashMap<>(); + private final Map _fishHard = new HashMap<>(); + + /** + * Instantiates a new fish data. + */ + protected FishData() + { + load(); + } + + @Override + public void load() + { + _fishEasy.clear(); + _fishNormal.clear(); + _fishHard.clear(); + parseDatapackFile("data/stats/fishing/fishes.xml"); + LOGGER.info(getClass().getSimpleName() + ": Loaded " + (_fishEasy.size() + _fishNormal.size() + _fishHard.size()) + " fishes."); + } + + @Override + public void parseDocument(Document document, File file) + { + for (Node n = document.getFirstChild(); n != null; n = n.getNextSibling()) + { + if ("list".equalsIgnoreCase(n.getNodeName())) + { + for (Node d = n.getFirstChild(); d != null; d = d.getNextSibling()) + { + if ("fish".equalsIgnoreCase(d.getNodeName())) + { + final NamedNodeMap attrs = d.getAttributes(); + final StatSet set = new StatSet(); + for (int i = 0; i < attrs.getLength(); i++) + { + final Node att = attrs.item(i); + set.set(att.getNodeName(), att.getNodeValue()); + } + + final Fish fish = new Fish(set); + switch (fish.getFishGrade()) + { + case 0: + { + _fishEasy.put(fish.getFishId(), fish); + break; + } + case 1: + { + _fishNormal.put(fish.getFishId(), fish); + break; + } + case 2: + { + _fishHard.put(fish.getFishId(), fish); + break; + } + } + } + } + } + } + } + + /** + * Gets the fish. + * @param level the fish Level + * @param group the fish Group + * @param grade the fish Grade + * @return List of Fish that can be fished + */ + public List getFish(int level, int group, int grade) + { + final List result = new ArrayList<>(); + Map fish = null; + switch (grade) + { + case 0: + { + fish = _fishEasy; + break; + } + case 1: + { + fish = _fishNormal; + break; + } + case 2: + { + fish = _fishHard; + break; + } + default: + { + LOGGER.warning(getClass().getSimpleName() + ": Unmanaged fish grade!"); + return result; + } + } + + for (Fish f : fish.values()) + { + if ((f.getFishLevel() != level) || (f.getFishGroup() != group)) + { + continue; + } + + result.add(f); + } + + if (result.isEmpty()) + { + LOGGER.warning(getClass().getSimpleName() + ": Cannot find any fish for level: " + level + " group: " + group + " and grade: " + grade + "!"); + } + + return result; + } + + /** + * Gets the single instance of FishData. + * @return single instance of FishData + */ + public static FishData getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final FishData INSTANCE = new FishData(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/FishingMonstersData.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/FishingMonstersData.java new file mode 100644 index 0000000..ccb5aa8 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/FishingMonstersData.java @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.data.xml; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; + +import org.w3c.dom.Document; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; + +import org.l2jmobius.commons.util.IXmlReader; +import org.l2jmobius.gameserver.mechanics.fishing.FishingMonster; +import org.l2jmobius.gameserver.util.StatSet; + +/** + * This class holds the Fishing Monsters information. + * @author nonom + */ +public class FishingMonstersData implements IXmlReader +{ + private final Map _fishingMonstersData = new HashMap<>(); + + /** + * Instantiates a new fishing monsters data. + */ + protected FishingMonstersData() + { + load(); + } + + @Override + public void load() + { + _fishingMonstersData.clear(); + parseDatapackFile("data/stats/fishing/fishingMonsters.xml"); + LOGGER.info(getClass().getSimpleName() + ": Loaded " + _fishingMonstersData.size() + " fishing monsters."); + } + + @Override + public void parseDocument(Document document, File file) + { + for (Node n = document.getFirstChild(); n != null; n = n.getNextSibling()) + { + if ("list".equalsIgnoreCase(n.getNodeName())) + { + for (Node d = n.getFirstChild(); d != null; d = d.getNextSibling()) + { + if ("fishingMonster".equalsIgnoreCase(d.getNodeName())) + { + final NamedNodeMap attrs = d.getAttributes(); + final StatSet set = new StatSet(); + for (int i = 0; i < attrs.getLength(); i++) + { + final Node att = attrs.item(i); + set.set(att.getNodeName(), att.getNodeValue()); + } + + final FishingMonster fishingMonster = new FishingMonster(set); + _fishingMonstersData.put(fishingMonster.getFishingMonsterId(), fishingMonster); + } + } + } + } + } + + /** + * Gets the fishing monster. + * @param level the fisherman level + * @return a fishing monster given the fisherman level + */ + public FishingMonster getFishingMonster(int level) + { + for (FishingMonster fishingMonster : _fishingMonstersData.values()) + { + if ((level >= fishingMonster.getUserMinLevel()) && (level <= fishingMonster.getUserMaxLevel())) + { + return fishingMonster; + } + } + + return null; + } + + /** + * Gets the fishing monster by Id. + * @param id the fishing monster Id + * @return the fishing monster by Id + */ + public FishingMonster getFishingMonsterById(int id) + { + return _fishingMonstersData.get(id); + } + + /** + * Gets the single instance of FishingMonsterData. + * @return single instance of FishingMonsterData + */ + public static FishingMonstersData getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final FishingMonstersData INSTANCE = new FishingMonstersData(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/FishingRodsData.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/FishingRodsData.java new file mode 100644 index 0000000..ec36660 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/FishingRodsData.java @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.data.xml; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; + +import org.w3c.dom.Document; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; + +import org.l2jmobius.commons.util.IXmlReader; +import org.l2jmobius.gameserver.mechanics.fishing.FishingRod; +import org.l2jmobius.gameserver.util.StatSet; + +/** + * This class holds the Fishing Rods information. + * @author nonom + */ +public class FishingRodsData implements IXmlReader +{ + private final Map _fishingRods = new HashMap<>(); + + /** + * Instantiates a new fishing rods data. + */ + protected FishingRodsData() + { + load(); + } + + @Override + public void load() + { + _fishingRods.clear(); + parseDatapackFile("data/stats/fishing/fishingRods.xml"); + LOGGER.info(getClass().getSimpleName() + ": Loaded " + _fishingRods.size() + " fishing rods."); + } + + @Override + public void parseDocument(Document document, File file) + { + for (Node n = document.getFirstChild(); n != null; n = n.getNextSibling()) + { + if ("list".equalsIgnoreCase(n.getNodeName())) + { + for (Node d = n.getFirstChild(); d != null; d = d.getNextSibling()) + { + if ("fishingRod".equalsIgnoreCase(d.getNodeName())) + { + final NamedNodeMap attrs = d.getAttributes(); + final StatSet set = new StatSet(); + for (int i = 0; i < attrs.getLength(); i++) + { + final Node att = attrs.item(i); + set.set(att.getNodeName(), att.getNodeValue()); + } + + final FishingRod fishingRod = new FishingRod(set); + _fishingRods.put(fishingRod.getFishingRodItemId(), fishingRod); + } + } + } + } + } + + /** + * Gets the fishing rod. + * @param itemId the item id + * @return A fishing Rod by Item Id + */ + public FishingRod getFishingRod(int itemId) + { + return _fishingRods.get(itemId); + } + + /** + * Gets the single instance of FishingRodsData. + * @return single instance of FishingRodsData + */ + public static FishingRodsData getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final FishingRodsData INSTANCE = new FishingRodsData(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/HennaData.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/HennaData.java new file mode 100644 index 0000000..46e7aae --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/HennaData.java @@ -0,0 +1,185 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.data.xml; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.w3c.dom.Document; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; + +import org.l2jmobius.commons.util.IXmlReader; +import org.l2jmobius.gameserver.entity.actor.enums.player.PlayerClass; +import org.l2jmobius.gameserver.entity.item.Henna; +import org.l2jmobius.gameserver.util.StatSet; + +/** + * This class holds the henna related information.
    + * Cost and required amount to add the henna to the player.
    + * Cost and retrieved amount for removing the henna from the player.
    + * Allowed classes to wear each henna. + * @author Zoey76 + */ +public class HennaData implements IXmlReader +{ + private final Map _hennaList = new HashMap<>(); + + /** + * Instantiates a new henna data. + */ + protected HennaData() + { + load(); + } + + @Override + public void load() + { + _hennaList.clear(); + parseDatapackFile("data/stats/hennaList.xml"); + LOGGER.info(getClass().getSimpleName() + ": Loaded " + _hennaList.size() + " henna data."); + } + + @Override + public void parseDocument(Document document, File file) + { + for (Node n = document.getFirstChild(); n != null; n = n.getNextSibling()) + { + if ("list".equals(n.getNodeName())) + { + for (Node d = n.getFirstChild(); d != null; d = d.getNextSibling()) + { + if ("henna".equals(d.getNodeName())) + { + parseHenna(d); + } + } + } + } + } + + /** + * Parses the henna. + * @param d the d + */ + private void parseHenna(Node d) + { + final StatSet set = new StatSet(); + final List wearClassIds = new ArrayList<>(); + NamedNodeMap attrs = d.getAttributes(); + Node attr; + for (int i = 0; i < attrs.getLength(); i++) + { + attr = attrs.item(i); + set.set(attr.getNodeName(), attr.getNodeValue()); + } + + for (Node c = d.getFirstChild(); c != null; c = c.getNextSibling()) + { + final String name = c.getNodeName(); + attrs = c.getAttributes(); + switch (name) + { + case "stats": + { + for (int i = 0; i < attrs.getLength(); i++) + { + attr = attrs.item(i); + set.set(attr.getNodeName(), attr.getNodeValue()); + } + break; + } + case "wear": + { + attr = attrs.getNamedItem("count"); + set.set("wear_count", attr.getNodeValue()); + attr = attrs.getNamedItem("fee"); + set.set("wear_fee", attr.getNodeValue()); + break; + } + case "cancel": + { + attr = attrs.getNamedItem("count"); + set.set("cancel_count", attr.getNodeValue()); + attr = attrs.getNamedItem("fee"); + set.set("cancel_fee", attr.getNodeValue()); + break; + } + case "classId": + { + wearClassIds.add(PlayerClass.getPlayerClass(Integer.parseInt(c.getTextContent()))); + break; + } + } + } + + final Henna henna = new Henna(set); + henna.setWearClassIds(wearClassIds); + _hennaList.put(henna.getDyeId(), henna); + } + + /** + * Gets the henna. + * @param id of the dye. + * @return the dye with that id. + */ + public Henna getHenna(int id) + { + return _hennaList.get(id); + } + + /** + * Gets the henna list. + * @param playerClass the player's class Id. + * @return the list with all the allowed dyes. + */ + public List getHennaList(PlayerClass playerClass) + { + final List list = new ArrayList<>(); + for (Henna henna : _hennaList.values()) + { + if (henna.isAllowedClass(playerClass)) + { + list.add(henna); + } + } + + return list; + } + + /** + * Gets the single instance of HennaData. + * @return single instance of HennaData + */ + public static HennaData getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final HennaData INSTANCE = new HennaData(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/HitConditionBonusData.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/HitConditionBonusData.java new file mode 100644 index 0000000..c445458 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/HitConditionBonusData.java @@ -0,0 +1,165 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.data.xml; + +import java.io.File; + +import org.w3c.dom.Document; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; + +import org.l2jmobius.commons.util.IXmlReader; +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.taskmanagers.GameTimeTaskManager; + +/** + * This class load, holds and calculates the hit condition bonuses. + * @author Nik + */ +public class HitConditionBonusData implements IXmlReader +{ + private int frontBonus = 0; + private int sideBonus = 0; + private int backBonus = 0; + private int highBonus = 0; + private int lowBonus = 0; + private int darkBonus = 0; + @SuppressWarnings("unused") + private int rainBonus = 0; + + /** + * Instantiates a new hit condition bonus. + */ + protected HitConditionBonusData() + { + load(); + } + + @Override + public void load() + { + parseDatapackFile("data/stats/hitConditionBonus.xml"); + LOGGER.info(getClass().getSimpleName() + ": Loaded hit condition bonuses."); + } + + @Override + public void parseDocument(Document document, File file) + { + for (Node d = document.getFirstChild().getFirstChild(); d != null; d = d.getNextSibling()) + { + final NamedNodeMap attrs = d.getAttributes(); + switch (d.getNodeName()) + { + case "front": + { + frontBonus = parseInteger(attrs, "val"); + break; + } + case "side": + { + sideBonus = parseInteger(attrs, "val"); + break; + } + case "back": + { + backBonus = parseInteger(attrs, "val"); + break; + } + case "high": + { + highBonus = parseInteger(attrs, "val"); + break; + } + case "low": + { + lowBonus = parseInteger(attrs, "val"); + break; + } + case "dark": + { + darkBonus = parseInteger(attrs, "val"); + break; + } + case "rain": + { + rainBonus = parseInteger(attrs, "val"); + break; + } + } + } + } + + /** + * Gets the condition bonus. + * @param attacker the attacking character. + * @param target the attacked character. + * @return the bonus of the attacker against the target. + */ + public double getConditionBonus(Creature attacker, Creature target) + { + double mod = 100; + + // Get high or low bonus. + if ((attacker.getZ() - target.getZ()) > 50) + { + mod += highBonus; + } + else if ((attacker.getZ() - target.getZ()) < -50) + { + mod += lowBonus; + } + + // Get weather bonus + if (GameTimeTaskManager.getInstance().isNight()) + { + mod += darkBonus; + // else if () No rain support yet. + // chance += hitConditionBonus.rainBonus; + } + + // Get side bonus + if (attacker.isBehind(target)) + { + mod += backBonus; + } + else if (attacker.isInFrontOf(target)) + { + mod += frontBonus; + } + else + { + mod += sideBonus; + } + + // If (mod / 100) is less than 0, return 0, because we can't lower more than 100%. + return Math.max(mod / 100, 0); + } + + /** + * Gets the single instance of HitConditionBonus. + * @return single instance of HitConditionBonus + */ + public static HitConditionBonusData getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final HitConditionBonusData INSTANCE = new HitConditionBonusData(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/InitialEquipmentData.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/InitialEquipmentData.java new file mode 100644 index 0000000..465cdef --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/InitialEquipmentData.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.data.xml; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collection; +import java.util.EnumMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Logger; + +import org.w3c.dom.Document; + +import org.l2jmobius.commons.util.IXmlReader; +import org.l2jmobius.gameserver.entity.actor.enums.player.PlayerClass; +import org.l2jmobius.gameserver.entity.item.holders.InitialEquipment; +import org.l2jmobius.gameserver.util.StatSet; + +/** + * @author Mobius + */ +public class InitialEquipmentData implements IXmlReader +{ + private static final Logger LOGGER = Logger.getLogger(InitialEquipmentData.class.getName()); + + private final Map> _classEquipment = new EnumMap<>(PlayerClass.class); + + protected InitialEquipmentData() + { + load(); + } + + @Override + public void load() + { + _classEquipment.clear(); + parseDatapackFile("data/stats/players/initialEquipment.xml"); + LOGGER.info(getClass().getSimpleName() + ": Loaded " + _classEquipment.size() + " initial equipment data."); + } + + @Override + public void parseDocument(Document document, File file) + { + forEach(document, "list", listNode -> forEach(listNode, "equipment", equipmentNode -> + { + final Map attributes = parseAttributes(equipmentNode); + final List equipment = new ArrayList<>(); + forEach(equipmentNode, "item", itemNode -> + { + final StatSet set = new StatSet(); + parseAttributes(itemNode).forEach(set::set); + equipment.add(new InitialEquipment(set)); + }); + + final PlayerClass playerClass = PlayerClass.getPlayerClass(Integer.parseInt((String) attributes.get("classId"))); + _classEquipment.put(playerClass, equipment); + })); + } + + /** + * Retrieves the initial equipment items associated with a specified class. + * @param playerClass the {@link PlayerClass} representing the class for which the initial equipment is required + * @return a {@link Collection} of {@link InitialEquipment} objects representing the initial equipment for the specified class, or {@code null} if no equipment is found for the given class + */ + public Collection getClassEquipment(PlayerClass playerClass) + { + return _classEquipment.get(playerClass); + } + + public static InitialEquipmentData getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final InitialEquipmentData INSTANCE = new InitialEquipmentData(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/InitialShortcutData.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/InitialShortcutData.java new file mode 100644 index 0000000..446926e --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/InitialShortcutData.java @@ -0,0 +1,328 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.data.xml; + +import java.io.File; +import java.util.ArrayList; +import java.util.EnumMap; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Logger; + +import org.w3c.dom.Document; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; + +import org.l2jmobius.commons.util.IXmlReader; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.enums.player.MacroType; +import org.l2jmobius.gameserver.entity.actor.enums.player.PlayerClass; +import org.l2jmobius.gameserver.entity.actor.enums.player.ShortcutType; +import org.l2jmobius.gameserver.entity.actor.holders.player.Macro; +import org.l2jmobius.gameserver.entity.actor.holders.player.MacroCmd; +import org.l2jmobius.gameserver.entity.actor.holders.player.Shortcut; +import org.l2jmobius.gameserver.entity.item.instance.Item; +import org.l2jmobius.gameserver.network.serverpackets.ShortcutRegister; + +/** + * Manages initial shortcut and macro configurations for new player characters.
    + * Loads configuration data from XML files and provides shortcuts based on player class. + *
      + *
    • Class-specific shortcut configurations for different player classes.
    • + *
    • Global shortcut settings available to all players.
    • + *
    • Macro preset definitions with commands and parameters.
    • + *
    • Automatic shortcut registration for skills, items, and macros.
    • + *
    + * @author Zoey76, Mobius + */ +public class InitialShortcutData implements IXmlReader +{ + private static final Logger LOGGER = Logger.getLogger(InitialShortcutData.class.getName()); + + // Shortcut Data Storage. + private final Map> _initialShortcutData = new EnumMap<>(PlayerClass.class); + private final List _initialGlobalShortcutList = new ArrayList<>(); + + // Macro Configuration. + private final Map _macroPresets = new HashMap<>(); + + protected InitialShortcutData() + { + load(); + } + + @Override + public void load() + { + _initialShortcutData.clear(); + _initialGlobalShortcutList.clear(); + + parseDatapackFile("data/stats/players/initialShortcuts.xml"); + + LOGGER.info(getClass().getSimpleName() + ": Loaded " + _initialGlobalShortcutList.size() + " initial global shortcut data."); + LOGGER.info(getClass().getSimpleName() + ": Loaded " + _initialShortcutData.size() + " initial shortcut data."); + LOGGER.info(getClass().getSimpleName() + ": Loaded " + _macroPresets.size() + " macro presets."); + } + + @Override + public void parseDocument(Document document, File file) + { + for (Node node = document.getFirstChild(); node != null; node = node.getNextSibling()) + { + if ("list".equals(node.getNodeName())) + { + for (Node dataNode = node.getFirstChild(); dataNode != null; dataNode = dataNode.getNextSibling()) + { + switch (dataNode.getNodeName()) + { + case "shortcuts": + { + NamedNodeMap attributes = dataNode.getAttributes(); + final Node classIdNode = attributes.getNamedItem("classId"); + final List shortcutList = new ArrayList<>(); + + for (Node childNode = dataNode.getFirstChild(); childNode != null; childNode = childNode.getNextSibling()) + { + if ("page".equals(childNode.getNodeName())) + { + attributes = childNode.getAttributes(); + final int pageId = parseInteger(attributes, "pageId"); + for (Node slotNode = childNode.getFirstChild(); slotNode != null; slotNode = slotNode.getNextSibling()) + { + if ("slot".equals(slotNode.getNodeName())) + { + final NamedNodeMap slotAttributes = slotNode.getAttributes(); + final int slotId = parseInteger(slotAttributes, "slotId"); + final ShortcutType shortcutType = parseEnum(slotAttributes, ShortcutType.class, "shortcutType"); + final int shortcutId = parseInteger(slotAttributes, "shortcutId"); + final int shortcutLevel = parseInteger(slotAttributes, "shortcutLevel", 0); + shortcutList.add(new Shortcut(slotId, pageId, shortcutType, shortcutId, shortcutLevel)); + } + } + } + } + + if (classIdNode != null) + { + _initialShortcutData.put(PlayerClass.getPlayerClass(Integer.parseInt(classIdNode.getNodeValue())), shortcutList); + } + else + { + _initialGlobalShortcutList.addAll(shortcutList); + } + break; + } + case "macros": + { + for (Node childNode = dataNode.getFirstChild(); childNode != null; childNode = childNode.getNextSibling()) + { + if ("macro".equals(childNode.getNodeName())) + { + NamedNodeMap attributes = childNode.getAttributes(); + if (!parseBoolean(attributes, "enabled", true)) + { + continue; + } + + final int macroId = parseInteger(attributes, "macroId"); + final int icon = parseInteger(attributes, "icon"); + final String name = parseString(attributes, "name"); + final String description = parseString(attributes, "description"); + final String acronym = parseString(attributes, "acronym"); + final List commands = new ArrayList<>(1); + int entryIndex = 0; + + for (Node commandNode = childNode.getFirstChild(); commandNode != null; commandNode = commandNode.getNextSibling()) + { + if ("command".equals(commandNode.getNodeName())) + { + attributes = commandNode.getAttributes(); + final MacroType type = parseEnum(attributes, MacroType.class, "type"); + int parameterOne = 0; + int parameterTwo = 0; + final String commandText = commandNode.getTextContent(); + + switch (type) + { + case SKILL: + { + parameterOne = parseInteger(attributes, "skillId"); // Skill ID. + parameterTwo = parseInteger(attributes, "skillLevel", 0); // Skill level. + break; + } + case ACTION: + { + parameterOne = parseInteger(attributes, "actionId"); // Not handled by client. + break; + } + case TEXT: + { + // Text commands have no numeric parameters. + break; + } + case SHORTCUT: + { + parameterOne = parseInteger(attributes, "page"); // Page. + parameterTwo = parseInteger(attributes, "slot", 0); // Slot. + break; + } + case ITEM: + { + parameterOne = parseInteger(attributes, "itemId"); // Not handled by client. + break; + } + case DELAY: + { + parameterOne = parseInteger(attributes, "delay"); // Delay in seconds. + break; + } + } + + commands.add(new MacroCmd(entryIndex++, type, parameterOne, parameterTwo, commandText)); + } + } + + _macroPresets.put(macroId, new Macro(macroId, icon, name, description, acronym, commands)); + } + } + break; + } + } + } + } + } + } + + /** + * Registers all available shortcuts for the specified player including global and class-specific shortcuts.
    + * Validates item availability, skill knowledge, and macro definitions before registration. + * @param player the {@link Player} for whom to register the shortcuts. + */ + public void registerAllShortcuts(Player player) + { + if (player == null) + { + return; + } + + // Register global shortcuts. + for (Shortcut shortcut : _initialGlobalShortcutList) + { + int shortcutId = shortcut.getId(); + switch (shortcut.getType()) + { + case ITEM: + { + final Item item = player.getInventory().getItemByItemId(shortcutId); + if (item == null) + { + continue; + } + + shortcutId = item.getObjectId(); + break; + } + case SKILL: + { + if (!player.getSkills().containsKey(shortcutId)) + { + continue; + } + break; + } + case MACRO: + { + final Macro macro = _macroPresets.get(shortcutId); + if (macro == null) + { + continue; + } + + player.registerMacro(macro); + break; + } + } + + // Register shortcut. + final Shortcut newShortcut = new Shortcut(shortcut.getSlot(), shortcut.getPage(), shortcut.getType(), shortcutId, shortcut.getLevel()); + player.sendPacket(new ShortcutRegister(newShortcut)); + player.registerShortcut(newShortcut); + } + + // Register class specific shortcuts. + if (_initialShortcutData.containsKey(player.getPlayerClass())) + { + for (Shortcut shortcut : _initialShortcutData.get(player.getPlayerClass())) + { + int shortcutId = shortcut.getId(); + switch (shortcut.getType()) + { + case ITEM: + { + final Item item = player.getInventory().getItemByItemId(shortcutId); + if (item == null) + { + continue; + } + + shortcutId = item.getObjectId(); + break; + } + case SKILL: + { + if (!player.getSkills().containsKey(shortcut.getId())) + { + continue; + } + break; + } + case MACRO: + { + final Macro macro = _macroPresets.get(shortcutId); + if (macro == null) + { + continue; + } + + player.registerMacro(macro); + break; + } + } + + // Register shortcut. + final Shortcut newShortcut = new Shortcut(shortcut.getSlot(), shortcut.getPage(), shortcut.getType(), shortcutId, shortcut.getLevel()); + player.sendPacket(new ShortcutRegister(newShortcut)); + player.registerShortcut(newShortcut); + } + } + } + + public static InitialShortcutData getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final InitialShortcutData INSTANCE = new InitialShortcutData(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/ItemData.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/ItemData.java new file mode 100644 index 0000000..53e5783 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/ItemData.java @@ -0,0 +1,284 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.data.xml; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ScheduledFuture; +import java.util.logging.Logger; + +import org.l2jmobius.commons.config.ThreadConfig; +import org.l2jmobius.commons.threads.ThreadPool; +import org.l2jmobius.gameserver.config.GeneralConfig; +import org.l2jmobius.gameserver.config.ServerConfig; +import org.l2jmobius.gameserver.entity.item.Armor; +import org.l2jmobius.gameserver.entity.item.EtcItem; +import org.l2jmobius.gameserver.entity.item.ItemTemplate; +import org.l2jmobius.gameserver.entity.item.Weapon; +import org.l2jmobius.gameserver.util.DocumentItem; + +/** + * This class serves as a container for all item templates in the game. + */ +public class ItemData +{ + private static final Logger LOGGER = Logger.getLogger(ItemData.class.getName()); + + private ItemTemplate[] _allTemplates; + private final Map _etcItems = new HashMap<>(); + private final Map _armors = new HashMap<>(); + private final Map _weapons = new HashMap<>(); + private final List _itemFiles = new ArrayList<>(); + + protected ItemData() + { + processDirectory("data/stats/items", _itemFiles); + if (GeneralConfig.CUSTOM_ITEMS_LOAD) + { + processDirectory("data/stats/items/custom", _itemFiles); + } + + load(); + } + + private void processDirectory(String dirName, List list) + { + final File dir = new File(ServerConfig.DATAPACK_ROOT, dirName); + if (!dir.exists()) + { + LOGGER.warning("Directory " + dir.getAbsolutePath() + " does not exist."); + return; + } + + final File[] files = dir.listFiles(); + if (files != null) + { + for (File file : files) + { + if (file.isFile() && file.getName().toLowerCase().endsWith(".xml")) + { + list.add(file); + } + } + } + } + + private void load() + { + final Collection items = ConcurrentHashMap.newKeySet(); + int highestId = 0; + _armors.clear(); + _etcItems.clear(); + _weapons.clear(); + + // If multithreading is enabled, use a thread pool to parse files. + if (ThreadConfig.THREADS_FOR_LOADING) + { + final Collection> tasks = ConcurrentHashMap.newKeySet(); + for (File file : _itemFiles) + { + tasks.add(ThreadPool.schedule(() -> + { + final DocumentItem document = new DocumentItem(file); + document.parse(); + items.addAll(document.getItemList()); + }, 0)); + } + + // Wait for all scheduled tasks to complete. + while (!tasks.isEmpty()) + { + for (ScheduledFuture task : tasks) + { + if ((task == null) || task.isDone() || task.isCancelled()) + { + tasks.remove(task); + } + } + } + } + else // Parse files sequentially if multithreading is not enabled. + { + for (File file : _itemFiles) + { + final DocumentItem document = new DocumentItem(file); + document.parse(); + items.addAll(document.getItemList()); + } + } + + // Process each loaded item and organize them into their respective collections. + for (ItemTemplate item : items) + { + if (highestId < item.getId()) + { + highestId = item.getId(); + } + + if (item instanceof EtcItem) + { + _etcItems.put(item.getId(), (EtcItem) item); + } + else if (item instanceof Armor) + { + _armors.put(item.getId(), (Armor) item); + } + else + { + _weapons.put(item.getId(), (Weapon) item); + } + } + + // Log the highest item ID and create a fast lookup array based on this value. + LOGGER.info(getClass().getSimpleName() + ": Highest item id used: " + highestId); + _allTemplates = new ItemTemplate[highestId + 1]; + + // Populate the fast lookup table with items from each category. + for (Armor item : _armors.values()) + { + _allTemplates[item.getId()] = item; + } + + for (Weapon item : _weapons.values()) + { + _allTemplates[item.getId()] = item; + } + + for (EtcItem item : _etcItems.values()) + { + _allTemplates[item.getId()] = item; + } + + LOGGER.info(getClass().getSimpleName() + ": Loaded " + _etcItems.size() + " etc items."); + LOGGER.info(getClass().getSimpleName() + ": Loaded " + _armors.size() + " armor items."); + LOGGER.info(getClass().getSimpleName() + ": Loaded " + _weapons.size() + " weapon items."); + LOGGER.info(getClass().getSimpleName() + ": Loaded " + (_etcItems.size() + _armors.size() + _weapons.size()) + " items in total."); + } + + public void reload() + { + load(); + EnchantItemHPBonusData.getInstance().load(); + } + + /** + * Returns the item template corresponding to the specified item ID. + * @param id the ID of the item to retrieve + * @return the {@link ItemTemplate} associated with the specified ID, or {@code null} if the ID is out of bounds + */ + public ItemTemplate getTemplate(int id) + { + if ((id >= _allTemplates.length) || (id < 0)) + { + return null; + } + + return _allTemplates[id]; + } + + /** + * Returns a set of all armor item IDs available in the collection. + * @return a {@link Set} of integers representing the IDs of all armors + */ + public Set getAllArmorsId() + { + return _armors.keySet(); + } + + /** + * Returns a collection of all armor items. + * @return a {@link Collection} of {@link Armor} objects representing all loaded armor items + */ + public Collection getAllArmors() + { + return _armors.values(); + } + + /** + * Returns a set of all weapon item IDs available in the collection. + * @return a {@link Set} of integers representing the IDs of all weapons + */ + public Set getAllWeaponsId() + { + return _weapons.keySet(); + } + + /** + * Returns a collection of all weapon items. + * @return a {@link Collection} of {@link Weapon} objects representing all loaded weapon items + */ + public Collection getAllWeapons() + { + return _weapons.values(); + } + + /** + * Returns a set of all miscellaneous item IDs (non-armor, non-weapon items) available in the collection. + * @return a {@link Set} of integers representing the IDs of all miscellaneous items + */ + public Set getAllEtcItemsId() + { + return _etcItems.keySet(); + } + + /** + * Returns a collection of all miscellaneous items (non-armor, non-weapon items). + * @return a {@link Collection} of {@link EtcItem} objects representing all loaded miscellaneous items + */ + public Collection getAllEtcItems() + { + return _etcItems.values(); + } + + /** + * Returns an array of all item templates, indexed by item ID for fast lookup. + * @return an array of {@link ItemTemplate} objects, where the index corresponds to the item ID + */ + public ItemTemplate[] getAllItems() + { + return _allTemplates; + } + + /** + * Returns the size of the item template array. + * @return the length of the {@link ItemTemplate} array, representing the maximum item ID + 1 + */ + public int getArraySize() + { + return _allTemplates.length; + } + + public static ItemData getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final ItemData INSTANCE = new ItemData(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/KarmaLossData.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/KarmaLossData.java new file mode 100644 index 0000000..cd79382 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/KarmaLossData.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.data.xml; + +import java.io.File; +import java.util.Arrays; +import java.util.logging.Logger; + +import org.w3c.dom.Document; +import org.w3c.dom.NamedNodeMap; + +import org.l2jmobius.commons.util.IXmlReader; +import org.l2jmobius.gameserver.config.PlayerConfig; + +/** + * @author Mobius + */ +public class KarmaLossData implements IXmlReader +{ + private static final Logger LOGGER = Logger.getLogger(KarmaLossData.class.getName()); + + private final double[] _levelModifiers = new double[PlayerConfig.PLAYER_MAXIMUM_LEVEL]; + + protected KarmaLossData() + { + load(); + } + + @Override + public void load() + { + Arrays.fill(_levelModifiers, 1d); + parseDatapackFile("data/stats/players/karmaLoss.xml"); + LOGGER.info(getClass().getSimpleName() + ": Loaded " + (_levelModifiers.length - 1) + " modifiers."); + } + + @Override + public void parseDocument(Document document, File file) + { + forEach(document, "list", listNode -> forEach(listNode, "modifier", modifierNode -> + { + final NamedNodeMap attrs = modifierNode.getAttributes(); + final int level = parseInteger(attrs, "level"); + if (level >= PlayerConfig.PLAYER_MAXIMUM_LEVEL) + { + return; + } + + _levelModifiers[level] = parseDouble(attrs, "val"); + })); + } + + /** + * Retrieves the karma modifier for a given level.
    + * This modifier is used to calculate the amount of karma lost upon death for players at the specified level. + * @param level the level for which the karma modifier is requested + * @return the {@code double} modifier associated with the specified level, used to calculate karma loss + */ + public double getModifier(int level) + { + if (level >= PlayerConfig.PLAYER_MAXIMUM_LEVEL) + { + LOGGER.warning(getClass().getSimpleName() + ": Requested too high level (" + level + ")."); + return _levelModifiers[_levelModifiers.length - 1]; + } + + return _levelModifiers[level]; + } + + public static KarmaLossData getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final KarmaLossData INSTANCE = new KarmaLossData(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/LevelUpCrystalData.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/LevelUpCrystalData.java new file mode 100644 index 0000000..b639569 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/LevelUpCrystalData.java @@ -0,0 +1,293 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.data.xml; + +import java.io.File; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.StringTokenizer; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.w3c.dom.Document; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; + +import org.l2jmobius.commons.util.IXmlReader; +import org.l2jmobius.gameserver.config.PlayerConfig; +import org.l2jmobius.gameserver.data.enums.AbsorbCrystalType; +import org.l2jmobius.gameserver.data.holders.LevelingSoulCrystalInfo; +import org.l2jmobius.gameserver.data.holders.SoulCrystal; +import org.l2jmobius.gameserver.data.holders.SoulCrystalDisplay; +import org.l2jmobius.gameserver.entity.actor.templates.NpcTemplate; + +/** + * Loads item enchant data. + * @author UnAfraid + */ +public class LevelUpCrystalData implements IXmlReader +{ + private static final Logger LOGGER = Logger.getLogger(LevelUpCrystalData.class.getName()); + + private static final Map _soulCrystalsData = new HashMap<>(); + private static final Map> _npcLevelingInfo = new HashMap<>(); + private static List _crystalDisplayInfo = new ArrayList<>(); + + protected LevelUpCrystalData() + { + load(); + } + + @Override + public synchronized void load() + { + _soulCrystalsData.clear(); + _npcLevelingInfo.clear(); + parseDatapackFile("data/LevelUpCrystalData.xml"); + LOGGER.info("Soul crystal data : Loaded " + _soulCrystalsData.size()); + LOGGER.info("Npc leveling crystal data : Loaded " + _npcLevelingInfo.size()); + } + + /** + * Gets the enchant scroll. + * @return Leveling SoulCrystal Information Map based NPC + */ + public Map> getNpcsSoulInfo() + { + return _npcLevelingInfo; + } + + /** + * Gets the enchant scroll. + * @param npcid + * @return Leveling SoulCrystal Information map by items + */ + public Map getNpcSoulInfo(int npcid) + { + return _npcLevelingInfo.get(npcid); + } + + /** + * Gets the enchant scroll. + * @return Map soul cristal based key itemid + */ + public Map getSoulCrystals() + { + return _soulCrystalsData; + } + + /** + * Gets the enchant scroll. + * @param itemid + * @return Leveling SoulCrystal Information map by items + */ + public SoulCrystal getSoulCrystal(int itemid) + { + return _soulCrystalsData.get(itemid); + } + + /** + * Gets the enchant scroll. + * @return SoulCrystalDisplay list + */ + public List getSoulCrystalInfo() + { + return _crystalDisplayInfo; + } + + @Override + public void parseDocument(Document document, File file) + { + if (!file.exists()) + { + LOGGER.log(Level.WARNING, "Missing LevelUpCrystalData.xml. The quest wont work without it!"); + return; + } + + Node first = document.getFirstChild(); + if ((first != null) && "list".equalsIgnoreCase(first.getNodeName())) + { + for (Node n = first.getFirstChild(); n != null; n = n.getNextSibling()) + { + if ("crystal".equalsIgnoreCase(n.getNodeName())) + { + for (Node d = n.getFirstChild(); d != null; d = d.getNextSibling()) + { + if ("item".equalsIgnoreCase(d.getNodeName())) + { + NamedNodeMap attrs = d.getAttributes(); + Node att = attrs.getNamedItem("itemId"); + if (att == null) + { + LOGGER.info("Missing itemId in Crystal List, skipping!"); + continue; + } + + int itemId = Integer.parseInt(attrs.getNamedItem("itemId").getNodeValue()); + + att = attrs.getNamedItem("level"); + if (att == null) + { + LOGGER.info("Missing level in Crystal List item Id " + itemId + " skipping!"); + continue; + } + + att = attrs.getNamedItem("leveledItemId"); + if (att == null) + { + LOGGER.info("Missing leveledItemId in Crystal List item Id " + itemId + " skipping!"); + continue; + } + + int level = Integer.parseInt(attrs.getNamedItem("level").getNodeValue()); + int leveledItemId = Integer.parseInt(attrs.getNamedItem("leveledItemId").getNodeValue()); + _soulCrystalsData.put(itemId, new SoulCrystal(level, itemId, leveledItemId)); + } + } + } + else if ("npc".equalsIgnoreCase(n.getNodeName())) + { + for (Node d = n.getFirstChild(); d != null; d = d.getNextSibling()) + { + if ("item".equalsIgnoreCase(d.getNodeName())) + { + NamedNodeMap attrs = d.getAttributes(); + Node att = attrs.getNamedItem("npcId"); + if (att == null) + { + LOGGER.info("Missing npc Id in NPC List, skipping!"); + continue; + } + + int npcId = Integer.parseInt(att.getNodeValue()); + Map temp = new HashMap<>(); + for (Node cd = d.getFirstChild(); cd != null; cd = cd.getNextSibling()) + { + boolean isSkillNeeded = false; + int chance = 5; + AbsorbCrystalType absorbType = AbsorbCrystalType.LAST_HIT; + + if ("detail".equalsIgnoreCase(cd.getNodeName())) + { + attrs = cd.getAttributes(); + + att = attrs.getNamedItem("absorbType"); + if (att != null) + { + absorbType = Enum.valueOf(AbsorbCrystalType.class, att.getNodeValue()); + } + + att = attrs.getNamedItem("chance"); + if (att != null) + { + chance = Integer.parseInt(att.getNodeValue()); + } + + att = attrs.getNamedItem("skill"); + if (att != null) + { + isSkillNeeded = Boolean.parseBoolean(att.getNodeValue()); + } + + Node att1 = attrs.getNamedItem("maxLevel"); + Node att2 = attrs.getNamedItem("levelList"); + if ((att1 == null) && (att2 == null)) + { + LOGGER.info("Missing maxlevel/levelList in NPC List npc Id " + npcId + " skipping!"); + continue; + } + + chance = (int) (chance * PlayerConfig.SOUL_CRYSTAL_CHANCE_MULTIPLIER); + chance = (chance > 100) ? 100 : chance; + LevelingSoulCrystalInfo info = new LevelingSoulCrystalInfo(absorbType, isSkillNeeded, chance); + if (att1 != null) + { + int maxLevel = Integer.parseInt(att1.getNodeValue()); + for (int i = 0; i <= maxLevel; i++) + { + temp.put(i, info); + } + } + else if (att2 != null) + { + StringTokenizer st = new StringTokenizer(att2.getNodeValue(), ","); + int tokenCount = st.countTokens(); + for (int i = 0; i < tokenCount; i++) + { + Integer value = Integer.decode(st.nextToken().trim()); + if (value == null) + { + LOGGER.info("Bad Level value!! npc Id " + npcId + ", token " + i + "!"); + value = 0; + } + + temp.put(value, info); + } + } + } + } + + if (temp.isEmpty()) + { + LOGGER.info("No leveling info for npc Id " + npcId + " skipping!"); + continue; + } + + _npcLevelingInfo.put(npcId, temp); + } + } + } + } + } + + final List _crystalDisplayAux = new ArrayList<>(); + for (Entry> entry : _npcLevelingInfo.entrySet()) + { + NpcTemplate npc = NpcData.getInstance().getTemplate(entry.getKey()); + int npcLvl = npc.getLevel(); + String name = npc.getName(); + for (Entry entry1 : entry.getValue().entrySet()) + { + int fromLevel = entry1.getKey(); + int chance = entry1.getValue().getChance(); + AbsorbCrystalType absorb = entry1.getValue().getAbsorbCrystalType(); + _crystalDisplayAux.add(new SoulCrystalDisplay(fromLevel, chance, absorb, npcLvl, name)); + } + } + + _crystalDisplayInfo = _crystalDisplayAux.stream().sorted(Comparator.comparingInt(SoulCrystalDisplay::getFromLevel).reversed().thenComparing(Comparator.comparingInt(SoulCrystalDisplay::getChance)).reversed()).toList(); + + for (Entry entry : _soulCrystalsData.entrySet()) + { + entry.getValue().getLeveledItemId(); + } + } + + public static LevelUpCrystalData getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final LevelUpCrystalData INSTANCE = new LevelUpCrystalData(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/MapRegionData.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/MapRegionData.java new file mode 100644 index 0000000..333fc5e --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/MapRegionData.java @@ -0,0 +1,818 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.data.xml; + +import java.io.File; +import java.util.ArrayList; +import java.util.EnumMap; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.logging.Logger; + +import org.w3c.dom.Document; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; + +import org.l2jmobius.commons.util.IXmlReader; +import org.l2jmobius.commons.util.Rnd; +import org.l2jmobius.gameserver.config.PlayerConfig; +import org.l2jmobius.gameserver.config.custom.FactionSystemConfig; +import org.l2jmobius.gameserver.data.sql.ClanHallTable; +import org.l2jmobius.gameserver.entity.Location; +import org.l2jmobius.gameserver.entity.WorldObject; +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.Npc; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.enums.creature.Race; +import org.l2jmobius.gameserver.entity.actor.enums.player.TeleportWhereType; +import org.l2jmobius.gameserver.entity.clan.Clan; +import org.l2jmobius.gameserver.entity.instancezone.Instance; +import org.l2jmobius.gameserver.entity.residences.ClanHall; +import org.l2jmobius.gameserver.entity.zone.type.ClanHallZone; +import org.l2jmobius.gameserver.entity.zone.type.RespawnZone; +import org.l2jmobius.gameserver.interfaces.ILocational; +import org.l2jmobius.gameserver.managers.CastleManager; +import org.l2jmobius.gameserver.managers.InstanceManager; +import org.l2jmobius.gameserver.managers.ZoneManager; +import org.l2jmobius.gameserver.mechanics.sevensigns.SevenSigns; +import org.l2jmobius.gameserver.mechanics.siege.Castle; + +/** + * Manages map regions, spawn points and teleportation locations across the game world.
    + * Handles player respawning based on location, karma status, clan ownership and race restrictions. + *
      + *
    • Region-based spawn point management for towns and special locations.
    • + *
    • Karma and chaotic player respawn handling.
    • + *
    • Castle and clan hall teleportation.
    • + *
    • Race-based region restrictions and alternate spawn points.
    • + *
    + * @author Mobius + */ +public class MapRegionData implements IXmlReader +{ + private static final Logger LOGGER = Logger.getLogger(MapRegionData.class.getName()); + + // Constants. + private static final String DEFAULT_RESPAWN = "talking_island_town"; + private static final String DEFAULT_TOWN = "Aden Castle Town"; + private static final int MAP_REGION_X_OFFSET = 20; // (9 + 11) center tile offset. + private static final int MAP_REGION_Y_OFFSET = 18; // (10 + 8) center tile offset. + private static final int COORDINATE_SHIFT = 15; // Bit shift for region calculation. + private static final int GRID_SIZE = 32; // Spatial grid dimensions for fast lookups. + + /** + * Compact region data holder to improve cache locality and reduce memory overhead.
    + * All region properties stored together in a single object for efficient access.
    + * Package-private to keep implementation details hidden. + */ + private static class RegionData + { + final String town; + final int locId; + final int castle; + final int bbs; + List maps; + List spawnLocs; + List otherSpawnLocs; + List chaoticSpawnLocs; + List banishSpawnLocs; + Map bannedRaces; + + RegionData(String town, int locId, int castle, int bbs) + { + this.town = town; + this.locId = locId; + this.castle = castle; + this.bbs = bbs; + } + } + + // Primary region storage - single HashMap for all regions. + private final Map _regions = new HashMap<>(); + + // Spatial index for O(1) coordinate-to-region lookups. + private final RegionData[][] _spatialGrid = new RegionData[GRID_SIZE][GRID_SIZE]; + + // Cached frequently accessed regions to avoid HashMap lookups. + private RegionData _defaultRespawnRegion; + + protected MapRegionData() + { + load(); + } + + @Override + public void load() + { + _regions.clear(); + + // Clear spatial grid. + for (int i = 0; i < GRID_SIZE; i++) + { + for (int j = 0; j < GRID_SIZE; j++) + { + _spatialGrid[i][j] = null; + } + } + + parseDatapackDirectory("data/mapregion", false); + + // Build spatial index after loading all regions. + buildSpatialIndex(); + + // Cache frequently accessed regions. + _defaultRespawnRegion = _regions.get(DEFAULT_RESPAWN); + + LOGGER.info(getClass().getSimpleName() + ": Loaded " + _regions.size() + " map regions."); + } + + /** + * Builds spatial grid index for O(1) coordinate lookups.
    + * Maps region coordinates directly to grid cells for instant access. + */ + private void buildSpatialIndex() + { + for (RegionData region : _regions.values()) + { + if (region.maps != null) + { + for (int[] map : region.maps) + { + final int x = map[0]; + final int y = map[1]; + if ((x >= 0) && (x < GRID_SIZE) && (y >= 0) && (y < GRID_SIZE)) + { + _spatialGrid[x][y] = region; + } + } + } + } + } + + @Override + public void parseDocument(Document document, File file) + { + NamedNodeMap attrs; + String name; + String town; + int locId; + int castle; + int bbs; + for (Node n = document.getFirstChild(); n != null; n = n.getNextSibling()) + { + if ("list".equalsIgnoreCase(n.getNodeName())) + { + for (Node d = n.getFirstChild(); d != null; d = d.getNextSibling()) + { + if ("region".equalsIgnoreCase(d.getNodeName())) + { + attrs = d.getAttributes(); + name = attrs.getNamedItem("name").getNodeValue(); + town = attrs.getNamedItem("town").getNodeValue(); + locId = parseInteger(attrs, "locId"); + castle = parseInteger(attrs, "castle"); + bbs = parseInteger(attrs, "bbs"); + + final RegionData region = new RegionData(town, locId, castle, bbs); + + for (Node c = d.getFirstChild(); c != null; c = c.getNextSibling()) + { + attrs = c.getAttributes(); + if ("respawnPoint".equalsIgnoreCase(c.getNodeName())) + { + final int spawnX = parseInteger(attrs, "X"); + final int spawnY = parseInteger(attrs, "Y"); + final int spawnZ = parseInteger(attrs, "Z"); + final boolean other = parseBoolean(attrs, "isOther", false); + final boolean chaotic = parseBoolean(attrs, "isChaotic", false); + final boolean banish = parseBoolean(attrs, "isBanish", false); + if (other) + { + if (region.otherSpawnLocs == null) + { + region.otherSpawnLocs = new ArrayList<>(); + } + + region.otherSpawnLocs.add(new Location(spawnX, spawnY, spawnZ)); + } + else if (chaotic) + { + if (region.chaoticSpawnLocs == null) + { + region.chaoticSpawnLocs = new ArrayList<>(); + } + + region.chaoticSpawnLocs.add(new Location(spawnX, spawnY, spawnZ)); + } + else if (banish) + { + if (region.banishSpawnLocs == null) + { + region.banishSpawnLocs = new ArrayList<>(); + } + + region.banishSpawnLocs.add(new Location(spawnX, spawnY, spawnZ)); + } + else + { + if (region.spawnLocs == null) + { + region.spawnLocs = new ArrayList<>(); + } + + region.spawnLocs.add(new Location(spawnX, spawnY, spawnZ)); + } + } + else if ("map".equalsIgnoreCase(c.getNodeName())) + { + if (region.maps == null) + { + region.maps = new ArrayList<>(); + } + + region.maps.add(new int[] + { + parseInteger(attrs, "X"), + parseInteger(attrs, "Y") + }); + } + else if ("banned".equalsIgnoreCase(c.getNodeName())) + { + if (region.bannedRaces == null) + { + region.bannedRaces = new EnumMap<>(Race.class); + } + + region.bannedRaces.put(Race.valueOf(attrs.getNamedItem("race").getNodeValue()), attrs.getNamedItem("point").getNodeValue()); + } + } + + _regions.put(name, region); + } + } + } + } + } + + /** + * Retrieves a normal spawn location for the specified region.
    + * Returns random spawn if configuration enabled, otherwise first spawn point. + * @param region + * @return the spawn location or {@code null} if region has no spawns. + */ + private Location getSpawnLoc(RegionData region) + { + if (region.spawnLocs == null) + { + return null; + } + + if (PlayerConfig.RANDOM_RESPAWN_IN_TOWN_ENABLED) + { + return region.spawnLocs.get(Rnd.get(region.spawnLocs.size())); + } + + return region.spawnLocs.get(0); + } + + /** + * Retrieves a chaotic spawn location for karma players in the specified region.
    + * Falls back to normal spawn if no chaotic spawn exists. + * @param region + * @return the spawn location. + */ + private Location getChaoticSpawnLoc(RegionData region) + { + if (region.chaoticSpawnLocs != null) + { + if (PlayerConfig.RANDOM_RESPAWN_IN_TOWN_ENABLED) + { + return region.chaoticSpawnLocs.get(Rnd.get(region.chaoticSpawnLocs.size())); + } + + return region.chaoticSpawnLocs.get(0); + } + + return getSpawnLoc(region); + } + + /** + * Finds the region containing the specified coordinates using spatial grid for O(1) lookup. + * @param locX + * @param locY + * @return the region or {@code null} if no region found. + */ + public RegionData getMapRegion(int locX, int locY) + { + final int regionX = getMapRegionX(locX); + final int regionY = getMapRegionY(locY); + + // O(1) lookup via spatial grid. + if ((regionX >= 0) && (regionX < GRID_SIZE) && (regionY >= 0) && (regionY < GRID_SIZE)) + { + return _spatialGrid[regionX][regionY]; + } + + return null; + } + + /** + * Retrieves the location ID for the region at the specified coordinates. + * @param locX + * @param locY + * @return the location ID or {@code 0} if no region found. + */ + public int getMapRegionLocId(int locX, int locY) + { + final RegionData region = getMapRegion(locX, locY); + return region != null ? region.locId : 0; + } + + /** + * Finds the region containing the specified world object. + * @param obj + * @return the region or {@code null} if no region found. + */ + public RegionData getMapRegion(WorldObject obj) + { + return getMapRegion(obj.getX(), obj.getY()); + } + + /** + * Retrieves the location ID for the region containing the specified world object. + * @param obj + * @return the location ID or {@code 0} if no region found. + */ + public int getMapRegionLocId(WorldObject obj) + { + return getMapRegionLocId(obj.getX(), obj.getY()); + } + + /** + * Calculates the map region X-coordinate index from world position. + * @param posX + * @return the region X index. + */ + public int getMapRegionX(int posX) + { + return (posX >> COORDINATE_SHIFT) + MAP_REGION_X_OFFSET; + } + + /** + * Calculates the map region Y-coordinate index from world position. + * @param posY + * @return the region Y index. + */ + public int getMapRegionY(int posY) + { + return (posY >> COORDINATE_SHIFT) + MAP_REGION_Y_OFFSET; + } + + /** + * Retrieves the closest town name based on creature position. + * @param creature + * @return the town name or default town if no region found. + */ + public String getClosestTownName(Creature creature) + { + final RegionData region = getMapRegion(creature); + return region == null ? DEFAULT_TOWN : region.town; + } + + /** + * Retrieves the castle ID associated with the area where the creature is located. + * @param creature + * @return the castle ID, or {@code 0} if no region found. + */ + public int getAreaCastle(Creature creature) + { + final RegionData region = getMapRegion(creature); + return region == null ? 0 : region.castle; + } + + /** + * Determines the teleportation location based on creature type, status and teleport destination.
    + * Handles castle, fortress, clan hall teleports, siege flags, karma spawns and faction bases. + * @param creature + * @param teleportWhere + * @return the teleport destination location. + */ + public Location getTeleToLocation(Creature creature, TeleportWhereType teleportWhere) + { + if (creature.isPlayer()) + { + final Player player = creature.asPlayer(); + + Castle castle = null; + ClanHall clanhall = null; + final Clan clan = player.getClan(); + + // Flying players in Gracia cannot use teleports to Aden continent. + if ((clan != null) && !player.isFlying()) + { + // Clan hall teleportation. + if (teleportWhere == TeleportWhereType.CLANHALL) + { + clanhall = ClanHallTable.getInstance().getAbstractHallByOwner(clan); + if (clanhall != null) + { + final ClanHallZone zone = clanhall.getZone(); + if (zone != null) + { + if (player.getKarma() > 0) + { + return zone.getChaoticSpawnLoc(); + } + + return zone.getSpawnLoc(); + } + } + } + + // Castle teleportation. + if (teleportWhere == TeleportWhereType.CASTLE) + { + // Check if player is on castle ground and clan is defender. + castle = CastleManager.getInstance().getCastleByOwner(clan); + if (castle == null) + { + castle = CastleManager.getInstance().getCastle(player); + if (!((castle != null) && castle.getSiege().isInProgress() && (castle.getSiege().getDefenderClan(clan) != null))) + { + castle = null; + } + } + + if ((castle != null) && (castle.getResidenceId() > 0)) + { + if (player.getKarma() > 0) + { + return castle.getResidenceZone().getChaoticSpawnLoc(); + } + + return castle.getResidenceZone().getSpawnLoc(); + } + } + + // Siege flag teleportation. + if (teleportWhere == TeleportWhereType.SIEGEFLAG) + { + castle = CastleManager.getInstance().getCastle(player); + if (castle != null) + { + if (castle.getSiege().isInProgress()) + { + final Set flags = castle.getSiege().getFlag(clan); + if ((flags != null) && !flags.isEmpty()) + { + return flags.stream().findAny().get().getLocation(); + } + } + } + } + } + + // Karma player respawn outside city. + if (player.getKarma() > 0) + { + return getNearestKarmaRespawn(player); + } + + // Respawn far from castle during siege if clan is participating. + castle = CastleManager.getInstance().getCastle(player); + if ((castle != null) && castle.getSiege().isInProgress() && (castle.getSiege().checkIsDefender(clan) || castle.getSiege().checkIsAttacker(clan)) && (SevenSigns.getInstance().getSealOwner(SevenSigns.SEAL_STRIFE) == SevenSigns.CABAL_DAWN)) + { + return castle.getResidenceZone().getOtherSpawnLoc(); + } + + // Instance exit location. + if (player.isInInstance()) + { + final Instance inst = InstanceManager.getInstance().getInstance(player.getInstanceId()); + if (inst != null) + { + final Location loc = inst.getExitLoc(); + if (loc != null) + { + return loc; + } + } + } + + // Faction system base respawn. + if (FactionSystemConfig.FACTION_SYSTEM_ENABLED && FactionSystemConfig.FACTION_RESPAWN_AT_BASE) + { + if (player.isGood()) + { + return FactionSystemConfig.FACTION_GOOD_BASE_LOCATION; + } + + if (player.isEvil()) + { + return FactionSystemConfig.FACTION_EVIL_BASE_LOCATION; + } + } + } + + return getNearestTownRespawn(creature); + } + + /** + * Retrieves the nearest karma respawn location for a player with negative reputation.
    + * Checks respawn zones first, then falls back to region-based spawns. + * @param player + * @return the karma respawn location. + */ + public Location getNearestKarmaRespawn(Player player) + { + try + { + final RespawnZone zone = ZoneManager.getInstance().getZone(player, RespawnZone.class); + if (zone != null) + { + return getRestartRegionChaoticSpawnLoc(player, zone.getRespawnPoint(player)); + } + + final RegionData region = getMapRegion(player); + if (region != null) + { + // Check for banned races and redirect to alternate region. + if ((region.bannedRaces != null) && region.bannedRaces.containsKey(player.getRace())) + { + final String alternateRegionName = region.bannedRaces.get(player.getRace()); + final RegionData alternateRegion = _regions.get(alternateRegionName); + if (alternateRegion != null) + { + return getChaoticSpawnLoc(alternateRegion); + } + } + + return getChaoticSpawnLoc(region); + } + } + catch (Exception e) + { + // Fall through to default handling. + } + + // Use cached default region. + if (_defaultRespawnRegion != null) + { + return getChaoticSpawnLoc(_defaultRespawnRegion); + } + + return null; + } + + /** + * Retrieves the nearest town respawn location for the specified creature.
    + * Checks respawn zones first, then falls back to region-based spawns. + * @param creature + * @return the town respawn location. + */ + public Location getNearestTownRespawn(Creature creature) + { + try + { + final RespawnZone zone = ZoneManager.getInstance().getZone(creature, RespawnZone.class); + if (zone != null) + { + return getRestartRegionSpawnLoc(creature, zone.getRespawnPoint(creature.asPlayer())); + } + + final RegionData region = getMapRegion(creature); + if (region != null) + { + // Check for banned races and redirect to alternate region. + if (creature.isPlayer()) + { + final Player player = creature.asPlayer(); + if ((region.bannedRaces != null) && region.bannedRaces.containsKey(player.getRace())) + { + final String alternateRegionName = region.bannedRaces.get(player.getRace()); + final RegionData alternateRegion = _regions.get(alternateRegionName); + if (alternateRegion != null) + { + return getSpawnLoc(alternateRegion); + } + } + } + + return getSpawnLoc(region); + } + } + catch (Exception e) + { + // Fall through to default respawn. + } + + // Use cached default region. + if (_defaultRespawnRegion != null) + { + return getSpawnLoc(_defaultRespawnRegion); + } + + return null; + } + + /** + * Retrieves the restart region spawn location accounting for race restrictions.
    + * Redirects to alternate region if creature's race is banned. + * @param creature + * @param point + * @return the restart spawn location. + */ + public Location getRestartRegionSpawnLoc(Creature creature, String point) + { + try + { + final Player player = creature.asPlayer(); + RegionData region = _regions.get(point); + if (region == null) + { + region = _defaultRespawnRegion; + } + + if ((region.bannedRaces != null) && region.bannedRaces.containsKey(player.getRace())) + { + return getRestartRegionSpawnLoc(player, region.bannedRaces.get(player.getRace())); + } + + return getSpawnLoc(region); + } + catch (Exception e) + { + if (_defaultRespawnRegion != null) + { + return getSpawnLoc(_defaultRespawnRegion); + } + + return null; + } + } + + /** + * Retrieves the restart region chaotic spawn location accounting for race restrictions.
    + * Redirects to alternate region if creature's race is banned. + * @param creature + * @param point + * @return the restart chaotic spawn location. + */ + public Location getRestartRegionChaoticSpawnLoc(Creature creature, String point) + { + try + { + RegionData region = _regions.get(point); + if (region == null) + { + region = _defaultRespawnRegion; + } + + final Player player = creature.asPlayer(); + if ((region.bannedRaces != null) && region.bannedRaces.containsKey(player.getRace())) + { + return getRestartRegionChaoticSpawnLoc(player, region.bannedRaces.get(player.getRace())); + } + + return getChaoticSpawnLoc(region); + } + catch (Exception e) + { + if (_defaultRespawnRegion != null) + { + return getChaoticSpawnLoc(_defaultRespawnRegion); + } + + return null; + } + } + + /** + * Retrieves the location ID for the restart region based on creature's race and point.
    + * If the creature's race is banned in the specified region, an alternate region is provided. + * @param creature + * @param point + * @return the location ID of the restart region, or {@code 0} if not found. + */ + public int getRestartRegionLocId(Creature creature, String point) + { + try + { + RegionData region = _regions.get(point); + if (region == null) + { + return _defaultRespawnRegion != null ? _defaultRespawnRegion.locId : 0; + } + + final Player player = creature.asPlayer(); + if ((region.bannedRaces != null) && region.bannedRaces.containsKey(player.getRace())) + { + return getRestartRegionLocId(player, region.bannedRaces.get(player.getRace())); + } + + return region.locId; + } + catch (Exception e) + { + return _defaultRespawnRegion != null ? _defaultRespawnRegion.locId : 0; + } + } + + /** + * Retrieves the location ID for the restart region based on player's race and point.
    + * Convenience overload for Player parameter. + * @param player + * @param point + * @return the location ID of the restart region, or {@code 0} if not found. + */ + public int getRestartRegionLocId(Player player, String point) + { + return getRestartRegionLocId((Creature) player, point); + } + + /** + * Retrieves the spawn location for the specified region name.
    + * Returns random spawn if configuration enabled, otherwise first spawn point. + * @param regionName + * @return the spawn location or {@code null} if region not found or has no spawns. + */ + public Location getSpawnLocByRegionName(String regionName) + { + final RegionData region = _regions.get(regionName); + if (region == null) + { + return null; + } + + if (region.spawnLocs == null) + { + return null; + } + + if (PlayerConfig.RANDOM_RESPAWN_IN_TOWN_ENABLED) + { + return region.spawnLocs.get(Rnd.get(region.spawnLocs.size())); + } + + return region.spawnLocs.get(0); + } + + /** + * Retrieves the town name for the specified region. + * @param regionName + * @return the town name or {@code null} if region not found. + */ + public String getMapRegionTown(String regionName) + { + final RegionData region = _regions.get(regionName); + return region != null ? region.town : null; + } + + /** + * Retrieves the bulletin board system ID for the specified location.
    + * Falls back to default respawn region BBS if location has no region. + * @param loc + * @return the BBS ID. + */ + public int getBBs(ILocational loc) + { + final RegionData region = getMapRegion(loc.getX(), loc.getY()); + if (region != null) + { + return region.bbs; + } + + // Use cached default region. + return _defaultRespawnRegion != null ? _defaultRespawnRegion.bbs : 0; + } + + public static MapRegionData getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final MapRegionData INSTANCE = new MapRegionData(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/MultisellData.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/MultisellData.java new file mode 100644 index 0000000..0aca43a --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/MultisellData.java @@ -0,0 +1,476 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.data.xml; + +import java.io.File; +import java.util.Iterator; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.regex.Pattern; + +import org.w3c.dom.DOMException; +import org.w3c.dom.Document; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; + +import org.l2jmobius.commons.util.IXmlReader; +import org.l2jmobius.gameserver.config.ConfigLoader; +import org.l2jmobius.gameserver.config.GeneralConfig; +import org.l2jmobius.gameserver.entity.actor.Npc; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.clan.Clan; +import org.l2jmobius.gameserver.entity.item.ItemTemplate; +import org.l2jmobius.gameserver.mechanics.multisell.Entry; +import org.l2jmobius.gameserver.mechanics.multisell.Ingredient; +import org.l2jmobius.gameserver.mechanics.multisell.ListContainer; +import org.l2jmobius.gameserver.mechanics.multisell.PreparedListContainer; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.serverpackets.ExPCCafePointInfo; +import org.l2jmobius.gameserver.network.serverpackets.MultiSellList; +import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; +import org.l2jmobius.gameserver.util.StatSet; + +public class MultisellData implements IXmlReader +{ + private static final Pattern XML_FILE_PATTERN = Pattern.compile("\\d+\\.xml"); + + private final Map _entries = new ConcurrentHashMap<>(); + + public static final int PAGE_SIZE = 40; + + // Special IDs. + public static final int PC_CAFE_POINTS = -100; + public static final int CLAN_REPUTATION = -200; + public static final int FAME = -300; + + protected MultisellData() + { + load(); + } + + @Override + public void load() + { + _entries.clear(); + parseDatapackDirectory("data/multisell", false); + if (GeneralConfig.CUSTOM_MULTISELL_LOAD) + { + parseDatapackDirectory("data/multisell/custom", false); + } + + verify(); + LOGGER.log(Level.INFO, getClass().getSimpleName() + ": Loaded " + _entries.size() + " multisell lists."); + } + + @Override + public void parseDocument(Document document, File file) + { + try + { + final int id = Integer.parseInt(file.getName().replace(".xml", "")); + int entryId = 1; + Node att; + final ListContainer list = new ListContainer(id); + for (Node n = document.getFirstChild(); n != null; n = n.getNextSibling()) + { + if ("list".equalsIgnoreCase(n.getNodeName())) + { + att = n.getAttributes().getNamedItem("applyTaxes"); + list.setApplyTaxes((att != null) && Boolean.parseBoolean(att.getNodeValue())); + att = n.getAttributes().getNamedItem("useRate"); + if (att != null) + { + try + { + list.setUseRate(Double.parseDouble(att.getNodeValue())); + if (list.getUseRate() <= 1e-6) + { + throw new NumberFormatException("The value cannot be 0"); // threat 0 as invalid value + } + } + catch (NumberFormatException e) + { + try + { + list.setUseRate(ConfigLoader.class.getField(att.getNodeValue()).getDouble(ConfigLoader.class)); + } + catch (Exception e1) + { + LOGGER.warning(e1.getMessage() + document.getLocalName()); + list.setUseRate(1.0); + } + } + catch (DOMException e) + { + LOGGER.warning(e.getMessage() + document.getLocalName()); + } + } + + att = n.getAttributes().getNamedItem("maintainEnchantment"); + list.setMaintainEnchantment((att != null) && Boolean.parseBoolean(att.getNodeValue())); + for (Node d = n.getFirstChild(); d != null; d = d.getNextSibling()) + { + if ("item".equalsIgnoreCase(d.getNodeName())) + { + final Entry e = parseEntry(d, entryId++, list); + list.getEntries().add(e); + } + else if ("npcs".equalsIgnoreCase(d.getNodeName())) + { + for (Node b = d.getFirstChild(); b != null; b = b.getNextSibling()) + { + if ("npc".equalsIgnoreCase(b.getNodeName())) + { + list.allowNpc(Integer.parseInt(b.getTextContent())); + } + } + } + } + } + } + + _entries.put(id, list); + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, getClass().getSimpleName() + ": Error in file " + file, e); + } + } + + @Override + public boolean isValidXmlFile(File file) + { + return (file != null) && file.isFile() && XML_FILE_PATTERN.matcher(file.getName().toLowerCase()).matches(); + } + + private Entry parseEntry(Node node, int entryId, ListContainer list) + { + Node n = node; + final Node first = n.getFirstChild(); + final Entry entry = new Entry(entryId); + NamedNodeMap attrs; + Node att; + StatSet set; + for (n = first; n != null; n = n.getNextSibling()) + { + if ("ingredient".equalsIgnoreCase(n.getNodeName())) + { + attrs = n.getAttributes(); + set = new StatSet(); + for (int i = 0; i < attrs.getLength(); i++) + { + att = attrs.item(i); + set.set(att.getNodeName(), att.getNodeValue()); + } + + entry.addIngredient(new Ingredient(set)); + } + else if ("production".equalsIgnoreCase(n.getNodeName())) + { + attrs = n.getAttributes(); + set = new StatSet(); + for (int i = 0; i < attrs.getLength(); i++) + { + att = attrs.item(i); + set.set(att.getNodeName(), att.getNodeValue()); + } + + entry.addProduct(new Ingredient(set)); + } + } + + // Check if buy price is lower than sell price. + // Only applies when there is only one ingredient and it is adena. + if (GeneralConfig.CORRECT_PRICES && (entry.getIngredients().size() == 1) && (entry.getProducts().size() == 1)) + { + final Ingredient ingredient = entry.getIngredients().get(0); + if (ingredient.getItemId() == 57) + { + int totalPrice = 0; + for (Ingredient product : entry.getProducts()) + { + final ItemTemplate template = ItemData.getInstance().getTemplate(product.getItemId()); + if (template == null) + { + continue; + } + + totalPrice += (product.getItemCount() * (template.getReferencePrice() / 2)); + } + + final int adenaCount = ingredient.getItemCount(); + if (totalPrice > adenaCount) + { + ingredient.setItemCount(totalPrice); + LOGGER.warning("Buy price " + adenaCount + " is less than sell price " + totalPrice + " at entry " + entryId + " of multisell " + list.getListId() + "."); + } + } + } + + return entry; + } + + /** + * This will generate the multisell list for the items.
    + * There exist various parameters in multisells that affect the way they will appear: + *
      + *
    1. Inventory only: + *
        + *
      • If true, only show items of the multisell for which the "primary" ingredients are already in the player's inventory. By "primary" ingredients we mean weapon and armor.
      • + *
      • If false, show the entire list.
      • + *
      + *
    2. + *
    3. Maintain enchantment: presumably, only lists with "inventory only" set to true should sometimes have this as true. This makes no sense otherwise... + *
        + *
      • If true, then the product will match the enchantment level of the ingredient.
        + * If the player has multiple items that match the ingredient list but the enchantment levels differ, then the entries need to be duplicated to show the products and ingredients for each enchantment level.
        + * For example: If the player has a crystal staff +1 and a crystal staff +3 and goes to exchange it at the mammon, the list should have all exchange possibilities for the +1 staff, followed by all possibilities for the +3 staff.
      • + *
      • If false, then any level ingredient will be considered equal and product will always be at +0
      • + *
      + *
    4. + *
    5. Apply taxes: Uses the "taxIngredient" entry in order to add a certain amount of adena to the ingredients. + *
    6. + *
    7. Additional product and ingredient multipliers.
    8. + *
    + * @param listId + * @param player + * @param npc + * @param inventoryOnly + * @param productMultiplier + * @param ingredientMultiplier + */ + public void separateAndSend(int listId, Player player, Npc npc, boolean inventoryOnly, double productMultiplier, double ingredientMultiplier) + { + final ListContainer template = _entries.get(listId); + if (template == null) + { + LOGGER.warning(getClass().getSimpleName() + ": can't find list id: " + listId + " requested by player: " + player.getName() + ", npcId:" + (npc != null ? npc.getId() : 0)); + return; + } + + if (!template.isNpcAllowed(-1)) + { + if ((npc == null) || !template.isNpcAllowed(npc.getId())) + { + if (player.isGM()) + { + player.sendMessage("Multisell " + listId + " is restricted. Under current conditions cannot be used. Only GMs are allowed to use it."); + } + else + { + LOGGER.warning(getClass().getSimpleName() + ": " + player + " attempted to open multisell " + listId + " from npc " + npc + " which is not allowed!"); + return; + } + } + } + + final PreparedListContainer list = new PreparedListContainer(template, inventoryOnly, player, npc); + + // Pass through this only when multipliers are different from 1. + if ((productMultiplier != 1) || (ingredientMultiplier != 1)) + { + list.getEntries().forEach(entry -> + { + // Math.max used here to avoid dropping count to 0. + entry.getProducts().forEach(product -> product.setItemCount((int) Math.max(product.getItemCount() * productMultiplier, 1))); + + // Math.max used here to avoid dropping count to 0. + entry.getIngredients().forEach(ingredient -> ingredient.setItemCount((int) Math.max(ingredient.getItemCount() * ingredientMultiplier, 1))); + }); + } + + int index = 0; + do + { + // Send list at least once even if size = 0. + player.sendPacket(new MultiSellList(list, index)); + index += PAGE_SIZE; + } + while (index < list.getEntries().size()); + player.setMultiSell(list); + } + + public void separateAndSend(int listId, Player player, Npc npc, boolean inventoryOnly) + { + separateAndSend(listId, player, npc, inventoryOnly, 1, 1); + } + + public static boolean hasSpecialIngredient(int id, long amount, Player player) + { + switch (id) + { + case PC_CAFE_POINTS: + { + if (player.getPcCafePoints() < amount) + { + player.sendPacket(SystemMessageId.YOU_ARE_SHORT_OF_ACCUMULATED_POINTS); + break; + } + + return true; + } + case CLAN_REPUTATION: + { + final Clan clan = player.getClan(); + if (clan == null) + { + player.sendPacket(SystemMessageId.YOU_ARE_NOT_A_CLAN_MEMBER_AND_CANNOT_PERFORM_THIS_ACTION); + break; + } + + if (!player.isClanLeader()) + { + player.sendPacket(SystemMessageId.ONLY_THE_CLAN_LEADER_IS_ENABLED); + break; + } + + if (clan.getReputationScore() < amount) + { + player.sendPacket(SystemMessageId.THE_CLAN_REPUTATION_SCORE_IS_TOO_LOW); + break; + } + + return true; + } + case FAME: + { + if (player.getFame() < amount) + { + player.sendMessage("You don't have enough reputation to do that."); + break; + } + + return true; + } + } + + return false; + } + + public static boolean takeSpecialIngredient(int id, int amount, Player player) + { + switch (id) + { + case PC_CAFE_POINTS: + { + player.setPcCafePoints(player.getPcCafePoints() - amount); + player.sendPacket(new ExPCCafePointInfo(player.getPcCafePoints(), -amount, 0)); + return true; + } + case CLAN_REPUTATION: + { + player.getClan().takeReputationScore(amount); + final SystemMessage smsg = new SystemMessage(SystemMessageId.S1_POINTS_HAVE_BEEN_DEDUCTED_FROM_THE_CLAN_S_REPUTATION_SCORE); + smsg.addInt(amount); + player.sendPacket(smsg); + return true; + } + case FAME: + { + player.setFame(player.getFame() - amount); + player.updateUserInfo(); + return true; + } + } + + return false; + } + + public static void giveSpecialProduct(int id, long amount, Player player) + { + switch (id) + { + case PC_CAFE_POINTS: + { + player.setPcCafePoints((int) (player.getPcCafePoints() + amount)); + player.sendPacket(new ExPCCafePointInfo(player.getPcCafePoints(), (int) amount, 0)); + break; + } + case CLAN_REPUTATION: + { + player.getClan().addReputationScore((int) amount); + break; + } + case FAME: + { + player.setFame((int) (player.getFame() + amount)); + player.updateUserInfo(); + break; + } + } + } + + private void verify() + { + ListContainer list; + final Iterator iter = _entries.values().iterator(); + while (iter.hasNext()) + { + list = iter.next(); + for (Entry ent : list.getEntries()) + { + for (Ingredient ing : ent.getIngredients()) + { + if (!verifyIngredient(ing)) + { + LOGGER.warning(getClass().getSimpleName() + ": can't find ingredient with itemId: " + ing.getItemId() + " in list: " + list.getListId()); + } + } + + for (Ingredient ing : ent.getProducts()) + { + if (!verifyIngredient(ing)) + { + LOGGER.warning(getClass().getSimpleName() + ": can't find product with itemId: " + ing.getItemId() + " in list: " + list.getListId()); + } + } + } + } + } + + private boolean verifyIngredient(Ingredient ing) + { + switch (ing.getItemId()) + { + case PC_CAFE_POINTS: + case CLAN_REPUTATION: + case FAME: + { + return true; + } + default: + { + return ing.getTemplate() != null; + } + } + } + + public static MultisellData getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final MultisellData INSTANCE = new MultisellData(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/NpcData.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/NpcData.java new file mode 100644 index 0000000..7c587a9 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/NpcData.java @@ -0,0 +1,943 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.data.xml; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.EnumMap; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Predicate; + +import org.w3c.dom.Document; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; + +import org.l2jmobius.commons.util.IXmlReader; +import org.l2jmobius.gameserver.config.GeneralConfig; +import org.l2jmobius.gameserver.config.RatesConfig; +import org.l2jmobius.gameserver.config.custom.FakePlayersConfig; +import org.l2jmobius.gameserver.entity.actor.enums.npc.AISkillScope; +import org.l2jmobius.gameserver.entity.actor.enums.npc.DropType; +import org.l2jmobius.gameserver.entity.actor.enums.player.PlayerClass; +import org.l2jmobius.gameserver.entity.actor.holders.npc.DropGroupHolder; +import org.l2jmobius.gameserver.entity.actor.holders.npc.DropHolder; +import org.l2jmobius.gameserver.entity.actor.holders.npc.MinionHolder; +import org.l2jmobius.gameserver.entity.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.mechanics.effects.EffectType; +import org.l2jmobius.gameserver.mechanics.skill.Skill; +import org.l2jmobius.gameserver.mechanics.skill.holders.SkillHolder; +import org.l2jmobius.gameserver.util.ArrayUtil; +import org.l2jmobius.gameserver.util.StatSet; + +/** + * NPC data parser. + * @author NosBit, Mobius + */ +public class NpcData implements IXmlReader +{ + private final Map _npcs = new ConcurrentHashMap<>(); + private final Map _clans = new ConcurrentHashMap<>(); + private static final Collection _masterMonsterIDs = ConcurrentHashMap.newKeySet(); + private static Integer _genericClanId = null; + + protected NpcData() + { + load(); + } + + @Override + public synchronized void load() + { + _masterMonsterIDs.clear(); + + parseDatapackDirectory("data/stats/npcs", false); + LOGGER.info(getClass().getSimpleName() + ": Loaded " + _npcs.size() + " NPCs."); + if (GeneralConfig.CUSTOM_NPC_DATA) + { + final int npcCount = _npcs.size(); + parseDatapackDirectory("data/stats/npcs/custom", true); + LOGGER.info(getClass().getSimpleName() + ": Loaded " + (_npcs.size() - npcCount) + " custom NPCs."); + } + + loadNpcsSkillLearn(); + } + + @Override + public void parseDocument(Document document, File file) + { + for (Node node = document.getFirstChild(); node != null; node = node.getNextSibling()) + { + if ("list".equalsIgnoreCase(node.getNodeName())) + { + for (Node listNode = node.getFirstChild(); listNode != null; listNode = listNode.getNextSibling()) + { + if ("npc".equalsIgnoreCase(listNode.getNodeName())) + { + NamedNodeMap attrs = listNode.getAttributes(); + final StatSet set = new StatSet(new HashMap<>()); + final int npcId = parseInteger(attrs, "id"); + final int level = parseInteger(attrs, "level", 85); + final String type = parseString(attrs, "type", "Folk"); + Map parameters = null; + Map skills = null; + Set clans = null; + Set ignoreClanNpcIds = null; + List dropLists = null; + List dropGroups = null; + set.set("id", npcId); + set.set("displayId", parseInteger(attrs, "displayId")); + set.set("level", level); + set.set("type", type); + set.set("name", parseString(attrs, "name")); + set.set("usingServerSideName", parseBoolean(attrs, "usingServerSideName")); + set.set("title", parseString(attrs, "title")); + set.set("usingServerSideTitle", parseBoolean(attrs, "usingServerSideTitle")); + for (Node npcNode = listNode.getFirstChild(); npcNode != null; npcNode = npcNode.getNextSibling()) + { + attrs = npcNode.getAttributes(); + switch (npcNode.getNodeName().toLowerCase()) + { + case "parameters": + { + if (parameters == null) + { + parameters = new HashMap<>(); + } + + for (Node parametersNode = npcNode.getFirstChild(); parametersNode != null; parametersNode = parametersNode.getNextSibling()) + { + attrs = parametersNode.getAttributes(); + switch (parametersNode.getNodeName().toLowerCase()) + { + case "param": + { + parameters.put(parseString(attrs, "name"), parseString(attrs, "value")); + break; + } + case "skill": + { + parameters.put(parseString(attrs, "name"), new SkillHolder(parseInteger(attrs, "id"), parseInteger(attrs, "level"))); + break; + } + case "minions": + { + final List minions = new ArrayList<>(1); + for (Node minionsNode = parametersNode.getFirstChild(); minionsNode != null; minionsNode = minionsNode.getNextSibling()) + { + if (minionsNode.getNodeName().equalsIgnoreCase("npc")) + { + attrs = minionsNode.getAttributes(); + minions.add(new MinionHolder(parseInteger(attrs, "id"), parseInteger(attrs, "count"), parseInteger(attrs, "max", 0), parseInteger(attrs, "respawnTime"), parseInteger(attrs, "weightPoint", 0))); + } + } + + if (!minions.isEmpty()) + { + parameters.put(parseString(parametersNode.getAttributes(), "name"), minions); + } + + break; + } + } + } + break; + } + case "race": + case "sex": + { + set.set(npcNode.getNodeName(), npcNode.getTextContent().toUpperCase()); + break; + } + case "equipment": + { + set.set("chestId", parseInteger(attrs, "chest")); + set.set("rhandId", parseInteger(attrs, "rhand")); + set.set("lhandId", parseInteger(attrs, "lhand")); + set.set("weaponEnchant", parseInteger(attrs, "weaponEnchant")); + break; + } + case "acquire": + { + set.set("exp", parseDouble(attrs, "exp")); + set.set("sp", parseDouble(attrs, "sp")); + set.set("raidPoints", parseDouble(attrs, "raidPoints")); + break; + } + case "stats": + { + set.set("baseSTR", parseInteger(attrs, "str")); + set.set("baseINT", parseInteger(attrs, "int")); + set.set("baseDEX", parseInteger(attrs, "dex")); + set.set("baseWIT", parseInteger(attrs, "wit")); + set.set("baseCON", parseInteger(attrs, "con")); + set.set("baseMEN", parseInteger(attrs, "men")); + for (Node statsNode = npcNode.getFirstChild(); statsNode != null; statsNode = statsNode.getNextSibling()) + { + attrs = statsNode.getAttributes(); + switch (statsNode.getNodeName().toLowerCase()) + { + case "vitals": + { + set.set("baseHpMax", parseDouble(attrs, "hp")); + set.set("baseHpReg", parseDouble(attrs, "hpRegen")); + set.set("baseMpMax", parseDouble(attrs, "mp")); + set.set("baseMpReg", parseDouble(attrs, "mpRegen")); + break; + } + case "attack": + { + set.set("basePAtk", parseDouble(attrs, "physical")); + set.set("baseMAtk", parseDouble(attrs, "magical")); + set.set("baseRndDam", parseInteger(attrs, "random")); + set.set("baseCritRate", parseInteger(attrs, "critical")); + set.set("accuracy", parseDouble(attrs, "accuracy")); // TODO: Implement me + set.set("basePAtkSpd", parseInteger(attrs, "attackSpeed")); + set.set("reuseDelay", parseInteger(attrs, "reuseDelay")); // TODO: Implement me + set.set("baseAtkType", parseString(attrs, "type")); + set.set("baseAtkRange", parseInteger(attrs, "range")); + set.set("distance", parseInteger(attrs, "distance")); // TODO: Implement me + set.set("width", parseInteger(attrs, "width")); // TODO: Implement me + break; + } + case "defence": + { + set.set("basePDef", parseDouble(attrs, "physical")); + set.set("baseMDef", parseDouble(attrs, "magical")); + set.set("evasion", parseInteger(attrs, "evasion")); // TODO: Implement me + set.set("baseShldDef", parseInteger(attrs, "shield")); + set.set("baseShldRate", parseInteger(attrs, "shieldRate")); + break; + } + case "attribute": + { + for (Node attributeNode = statsNode.getFirstChild(); attributeNode != null; attributeNode = attributeNode.getNextSibling()) + { + attrs = attributeNode.getAttributes(); + switch (attributeNode.getNodeName().toLowerCase()) + { + case "attack": + { + final String attackAttributeType = parseString(attrs, "type"); + switch (attackAttributeType.toUpperCase()) + { + case "FIRE": + { + set.set("baseFire", parseInteger(attrs, "value")); + break; + } + case "WATER": + { + set.set("baseWater", parseInteger(attrs, "value")); + break; + } + case "WIND": + { + set.set("baseWind", parseInteger(attrs, "value")); + break; + } + case "EARTH": + { + set.set("baseEarth", parseInteger(attrs, "value")); + break; + } + case "DARK": + { + set.set("baseDark", parseInteger(attrs, "value")); + break; + } + case "HOLY": + { + set.set("baseHoly", parseInteger(attrs, "value")); + break; + } + } + break; + } + case "defence": + { + set.set("baseFireRes", parseInteger(attrs, "fire")); + set.set("baseWaterRes", parseInteger(attrs, "water")); + set.set("baseWindRes", parseInteger(attrs, "wind")); + set.set("baseEarthRes", parseInteger(attrs, "earth")); + set.set("baseHolyRes", parseInteger(attrs, "holy")); + set.set("baseDarkRes", parseInteger(attrs, "dark")); + set.set("baseElementRes", parseInteger(attrs, "default")); + break; + } + } + } + break; + } + case "speed": + { + for (Node speedNode = statsNode.getFirstChild(); speedNode != null; speedNode = speedNode.getNextSibling()) + { + attrs = speedNode.getAttributes(); + switch (speedNode.getNodeName().toLowerCase()) + { + case "walk": + { + final double groundWalk = parseDouble(attrs, "ground"); + set.set("baseWalkSpd", groundWalk <= 0d ? 0.1 : groundWalk); + set.set("baseSwimWalkSpd", parseDouble(attrs, "swim")); + set.set("baseFlyWalkSpd", parseDouble(attrs, "fly")); + break; + } + case "run": + { + final double runSpeed = parseDouble(attrs, "ground"); + set.set("baseRunSpd", runSpeed <= 0d ? 0.1 : runSpeed); + set.set("baseSwimRunSpd", parseDouble(attrs, "swim")); + set.set("baseFlyRunSpd", parseDouble(attrs, "fly")); + break; + } + } + } + break; + } + case "hittime": + { + set.set("hitTime", npcNode.getTextContent()); // TODO: Implement me default 600 (value in ms) + break; + } + } + } + break; + } + case "status": + { + set.set("unique", parseBoolean(attrs, "unique")); + set.set("attackable", parseBoolean(attrs, "attackable")); + set.set("targetable", parseBoolean(attrs, "targetable")); + set.set("talkable", parseBoolean(attrs, "talkable")); + set.set("undying", parseBoolean(attrs, "undying")); + set.set("showName", parseBoolean(attrs, "showName")); + set.set("randomWalk", parseBoolean(attrs, "randomWalk")); + set.set("randomAnimation", parseBoolean(attrs, "randomAnimation")); + set.set("flying", parseBoolean(attrs, "flying")); + set.set("canMove", parseBoolean(attrs, "canMove")); + set.set("noSleepMode", parseBoolean(attrs, "noSleepMode")); + set.set("passableDoor", parseBoolean(attrs, "passableDoor")); + set.set("hasSummoner", parseBoolean(attrs, "hasSummoner")); + set.set("canBeSown", parseBoolean(attrs, "canBeSown")); + break; + } + case "fakeplayer": + { + set.set("fakePlayer", true); + set.set("classId", parseInteger(attrs, "classId", 1)); + set.set("hair", parseInteger(attrs, "hair", 1)); + set.set("hairColor", parseInteger(attrs, "hairColor", 1)); + set.set("face", parseInteger(attrs, "face", 1)); + set.set("nameColor", parseInteger(attrs, "nameColor", 0xFFFFFF)); + set.set("titleColor", parseInteger(attrs, "titleColor", 0xECF9A2)); + set.set("equipHead", parseInteger(attrs, "equipHead", 0)); + set.set("equipRHand", parseInteger(attrs, "equipRHand", 0)); // Or dual hand. + set.set("equipLHand", parseInteger(attrs, "equipLHand", 0)); + set.set("equipGloves", parseInteger(attrs, "equipGloves", 0)); + set.set("equipChest", parseInteger(attrs, "equipChest", 0)); + set.set("equipLegs", parseInteger(attrs, "equipLegs", 0)); + set.set("equipFeet", parseInteger(attrs, "equipFeet", 0)); + set.set("equipCloak", parseInteger(attrs, "equipCloak", 0)); + set.set("equipHair", parseInteger(attrs, "equipHair", 0)); + set.set("equipHair2", parseInteger(attrs, "equipHair2", 0)); + set.set("agathionId", parseInteger(attrs, "agathionId", 0)); + set.set("weaponEnchantLevel", parseInteger(attrs, "weaponEnchantLevel", 0)); + set.set("armorEnchantLevel", parseInteger(attrs, "armorEnchantLevel", 0)); + set.set("fishing", parseBoolean(attrs, "fishing", false)); + set.set("baitLocationX", parseInteger(attrs, "baitLocationX", 0)); + set.set("baitLocationY", parseInteger(attrs, "baitLocationY", 0)); + set.set("baitLocationZ", parseInteger(attrs, "baitLocationZ", 0)); + set.set("recommends", parseInteger(attrs, "recommends", 0)); + set.set("nobleLevel", parseInteger(attrs, "nobleLevel", 0)); + set.set("hero", parseBoolean(attrs, "hero", false)); + set.set("clanId", parseInteger(attrs, "clanId", 0)); + set.set("pledgeStatus", parseInteger(attrs, "pledgeStatus", 0)); + set.set("sitting", parseBoolean(attrs, "sitting", false)); + set.set("privateStoreType", parseInteger(attrs, "privateStoreType", 0)); + set.set("privateStoreMessage", parseString(attrs, "privateStoreMessage", "")); + set.set("fakePlayerTalkable", parseBoolean(attrs, "fakePlayerTalkable", true)); + break; + } + case "skilllist": + { + skills = new HashMap<>(); + for (Node skillListNode = npcNode.getFirstChild(); skillListNode != null; skillListNode = skillListNode.getNextSibling()) + { + if ("skill".equalsIgnoreCase(skillListNode.getNodeName())) + { + attrs = skillListNode.getAttributes(); + final int skillId = parseInteger(attrs, "id"); + final int skillLevel = parseInteger(attrs, "level"); + final Skill skill = SkillData.getInstance().getSkill(skillId, skillLevel); + if (skill != null) + { + skills.put(skill.getId(), skill); + } + else + { + LOGGER.warning("[" + file.getName() + "] skill not found. NPC ID: " + npcId + " Skill ID:" + skillId + " Skill Level: " + skillLevel); + } + } + } + break; + } + case "shots": + { + set.set("soulShot", parseInteger(attrs, "soul")); + set.set("spiritShot", parseInteger(attrs, "spirit")); + set.set("soulShotChance", parseInteger(attrs, "shotChance")); + set.set("spiritShotChance", parseInteger(attrs, "spiritChance")); + break; + } + case "corpsetime": + { + set.set("corpseTime", npcNode.getTextContent()); + break; + } + case "excrteffect": + { + set.set("exCrtEffect", npcNode.getTextContent()); // TODO: Implement me default ? type boolean + break; + } + case "snpcprophprate": + { + set.set("sNpcPropHpRate", npcNode.getTextContent()); // TODO: Implement me default 1 type double + break; + } + case "ai": + { + set.set("aiType", parseString(attrs, "type")); + set.set("aggroRange", parseInteger(attrs, "aggroRange")); + set.set("clanHelpRange", parseInteger(attrs, "clanHelpRange")); + set.set("isChaos", parseBoolean(attrs, "isChaos")); + set.set("isAggressive", parseBoolean(attrs, "isAggressive")); + for (Node aiNode = npcNode.getFirstChild(); aiNode != null; aiNode = aiNode.getNextSibling()) + { + attrs = aiNode.getAttributes(); + switch (aiNode.getNodeName().toLowerCase()) + { + case "skill": + { + set.set("minSkillChance", parseInteger(attrs, "minChance")); + set.set("maxSkillChance", parseInteger(attrs, "maxChance")); + set.set("primarySkillId", parseInteger(attrs, "primaryId")); + set.set("shortRangeSkillId", parseInteger(attrs, "shortRangeId")); + set.set("shortRangeSkillChance", parseInteger(attrs, "shortRangeChance")); + set.set("longRangeSkillId", parseInteger(attrs, "longRangeId")); + set.set("longRangeSkillChance", parseInteger(attrs, "longRangeChance")); + break; + } + case "clanlist": + { + for (Node clanListNode = aiNode.getFirstChild(); clanListNode != null; clanListNode = clanListNode.getNextSibling()) + { + attrs = clanListNode.getAttributes(); + switch (clanListNode.getNodeName().toLowerCase()) + { + case "clan": + { + if (clans == null) + { + clans = new HashSet<>(1); + } + + clans.add(getOrCreateClanId(clanListNode.getTextContent())); + break; + } + case "ignorenpcid": + { + if (ignoreClanNpcIds == null) + { + ignoreClanNpcIds = new HashSet<>(1); + } + + ignoreClanNpcIds.add(Integer.parseInt(clanListNode.getTextContent())); + break; + } + } + } + break; + } + } + } + break; + } + case "droplists": + { + for (Node dropListsNode = npcNode.getFirstChild(); dropListsNode != null; dropListsNode = dropListsNode.getNextSibling()) + { + DropType dropType = null; + + try + { + dropType = Enum.valueOf(DropType.class, dropListsNode.getNodeName().toUpperCase()); + } + catch (Exception e) + { + // Handled bellow. + } + + if (dropType != null) + { + for (Node dropNode = dropListsNode.getFirstChild(); dropNode != null; dropNode = dropNode.getNextSibling()) + { + final String nodeName = dropNode.getNodeName(); + if (nodeName.equalsIgnoreCase("group")) + { + if (dropGroups == null) + { + dropGroups = new ArrayList<>(); + } + + final DropGroupHolder group = new DropGroupHolder(parseDouble(dropNode.getAttributes(), "chance")); + for (Node groupNode = dropNode.getFirstChild(); groupNode != null; groupNode = groupNode.getNextSibling()) + { + if (groupNode.getNodeName().equalsIgnoreCase("item")) + { + final NamedNodeMap groupAttrs = groupNode.getAttributes(); + final int itemId = parseInteger(groupAttrs, "id"); + + if (ItemData.getInstance().getTemplate(itemId) == null) + { + LOGGER.warning(getClass().getSimpleName() + ": Could not find drop with id " + itemId + "."); + } + else + { + group.addDrop(new DropHolder(dropType, itemId, parseInteger(groupAttrs, "min"), parseInteger(groupAttrs, "max"), parseDouble(groupAttrs, "chance"))); + } + } + } + + // Group drops are sorted by chance (low to high). + group.sortByChance(); + dropGroups.add(group); + } + else if (nodeName.equalsIgnoreCase("item")) + { + if (dropLists == null) + { + dropLists = new ArrayList<>(); + } + + final NamedNodeMap dropAttrs = dropNode.getAttributes(); + final int itemId = parseInteger(dropAttrs, "id"); + + if (ItemData.getInstance().getTemplate(itemId) == null) + { + LOGGER.warning(getClass().getSimpleName() + ": Could not find drop with id " + itemId + "."); + } + else + { + dropLists.add(new DropHolder(dropType, itemId, parseInteger(dropAttrs, "min"), parseInteger(dropAttrs, "max"), parseDouble(dropAttrs, "chance"))); + } + } + } + } + } + break; + } + case "collision": + { + for (Node collisionNode = npcNode.getFirstChild(); collisionNode != null; collisionNode = collisionNode.getNextSibling()) + { + attrs = collisionNode.getAttributes(); + switch (collisionNode.getNodeName().toLowerCase()) + { + case "radius": + { + set.set("collisionRadius", parseDouble(attrs, "normal")); + set.set("collisionRadiusGrown", parseDouble(attrs, "grown")); + break; + } + case "height": + { + set.set("collisionHeight", parseDouble(attrs, "normal")); + set.set("collisionHeightGrown", parseDouble(attrs, "grown")); + break; + } + } + } + break; + } + } + } + + if (!FakePlayersConfig.FAKE_PLAYERS_ENABLED && set.getBoolean("fakePlayer", false)) + { + continue; + } + + NpcTemplate template = _npcs.get(npcId); + if (template == null) + { + template = new NpcTemplate(set); + _npcs.put(template.getId(), template); + } + else + { + template.set(set); + } + + template.setParameters(parameters != null ? new StatSet(Collections.unmodifiableMap(parameters)) : StatSet.EMPTY_STATSET); + if (skills != null) + { + Map> aiSkillLists = null; + for (Skill skill : skills.values()) + { + if (skill.isPassive()) + { + continue; + } + + if (aiSkillLists == null) + { + aiSkillLists = new EnumMap<>(AISkillScope.class); + } + + final List aiSkillScopes = new ArrayList<>(); + final AISkillScope shortOrLongRangeScope = skill.getCastRange() <= 150 ? AISkillScope.SHORT_RANGE : AISkillScope.LONG_RANGE; + if (skill.isSuicideAttack()) + { + aiSkillScopes.add(AISkillScope.SUICIDE); + } + else + { + aiSkillScopes.add(AISkillScope.GENERAL); + + if (skill.isContinuous()) + { + if (!skill.isDebuff()) + { + aiSkillScopes.add(AISkillScope.BUFF); + } + else + { + aiSkillScopes.add(AISkillScope.DEBUFF); + aiSkillScopes.add(AISkillScope.COT); + aiSkillScopes.add(shortOrLongRangeScope); + } + } + else if (skill.hasEffectType(EffectType.DISPEL, EffectType.DISPEL_BY_SLOT)) + { + aiSkillScopes.add(AISkillScope.NEGATIVE); + aiSkillScopes.add(shortOrLongRangeScope); + } + else if (skill.hasEffectType(EffectType.HEAL)) + { + aiSkillScopes.add(AISkillScope.HEAL); + } + else if (skill.hasEffectType(EffectType.PHYSICAL_ATTACK, EffectType.PHYSICAL_ATTACK_HP_LINK, EffectType.MAGICAL_ATTACK, EffectType.DEATH_LINK, EffectType.HP_DRAIN)) + { + aiSkillScopes.add(AISkillScope.ATTACK); + aiSkillScopes.add(AISkillScope.UNIVERSAL); + aiSkillScopes.add(shortOrLongRangeScope); + } + else if (skill.hasEffectType(EffectType.SLEEP)) + { + aiSkillScopes.add(AISkillScope.IMMOBILIZE); + } + else if (skill.hasEffectType(EffectType.STUN, EffectType.ROOT)) + { + aiSkillScopes.add(AISkillScope.IMMOBILIZE); + aiSkillScopes.add(shortOrLongRangeScope); + } + else if (skill.hasEffectType(EffectType.MUTE, EffectType.FEAR)) + { + aiSkillScopes.add(AISkillScope.COT); + aiSkillScopes.add(shortOrLongRangeScope); + } + else if (skill.hasEffectType(EffectType.PARALYZE)) + { + aiSkillScopes.add(AISkillScope.IMMOBILIZE); + aiSkillScopes.add(shortOrLongRangeScope); + } + else if (skill.hasEffectType(EffectType.DMG_OVER_TIME, EffectType.DMG_OVER_TIME_PERCENT)) + { + aiSkillScopes.add(shortOrLongRangeScope); + } + else if (skill.hasEffectType(EffectType.RESURRECTION)) + { + aiSkillScopes.add(AISkillScope.RES); + } + else + { + aiSkillScopes.add(AISkillScope.UNIVERSAL); + } + } + + for (AISkillScope aiSkillScope : aiSkillScopes) + { + List aiSkills = aiSkillLists.get(aiSkillScope); + if (aiSkills == null) + { + aiSkills = new ArrayList<>(); + aiSkillLists.put(aiSkillScope, aiSkills); + } + + aiSkills.add(skill); + } + } + + template.setSkills(skills); + template.setAISkillLists(aiSkillLists); + } + else + { + template.setSkills(null); + template.setAISkillLists(null); + } + + template.setClans(clans); + template.setIgnoreClanNpcIds(ignoreClanNpcIds); + + // Clean old drop groups. + template.removeDropGroups(); + + // Set new drop groups. + if (dropGroups != null) + { + template.setDropGroups(dropGroups); + } + + // Clean old drop lists. + template.removeDrops(); + + // Add configurable item drop for bosses. + if ((RatesConfig.BOSS_DROP_ENABLED) && (type.contains("RaidBoss") && (level >= RatesConfig.BOSS_DROP_MIN_LEVEL) && (level <= RatesConfig.BOSS_DROP_MAX_LEVEL))) + { + if (dropLists == null) + { + dropLists = new ArrayList<>(); + } + + dropLists.addAll(RatesConfig.BOSS_DROP_LIST); + } + + // Set new drop lists. + if (dropLists != null) + { + // Drops are sorted by chance (high to low). + dropLists.sort((d1, d2) -> Double.compare(d2.getChance(), d1.getChance())); + for (DropHolder dropHolder : dropLists) + { + switch (dropHolder.getDropType()) + { + case DROP: + { + template.addDrop(dropHolder); + break; + } + case SPOIL: + { + template.addSpoil(dropHolder); + break; + } + } + } + } + + if (!template.getParameters().getMinionList("Privates").isEmpty() && (template.getParameters().getSet().get("SummonPrivateRate") == null)) + { + _masterMonsterIDs.add(template.getId()); + } + } + } + } + } + } + + /** + * Gets or creates a clan id if it does not exist. + * @param clanName the clan name to get or create its id + * @return the clan id for the given clan name + */ + private int getOrCreateClanId(String clanName) + { + Integer id = _clans.get(clanName); + if (id == null) + { + id = _clans.size(); + _clans.put(clanName, id); + } + + return id; + } + + /** + * Gets the clan id + * @param clanName the clan name to get its id + * @return the clan id for the given clan name if it exists, -1 otherwise + */ + public int getClanId(String clanName) + { + final Integer id = _clans.get(clanName); + return id != null ? id : -1; + } + + public int getGenericClanId() + { + if (_genericClanId != null) + { + return _genericClanId; + } + + synchronized (this) + { + _genericClanId = _clans.get("ALL"); + + if (_genericClanId == null) + { + _genericClanId = -1; + } + } + + return _genericClanId; + } + + /** + * Gets the template. + * @param id the template Id to get. + * @return the template for the given id. + */ + public NpcTemplate getTemplate(int id) + { + return _npcs.get(id); + } + + /** + * Gets the template by name. + * @param name of the template to get. + * @return the template for the given name. + */ + public NpcTemplate getTemplateByName(String name) + { + for (NpcTemplate npcTemplate : _npcs.values()) + { + if (npcTemplate.getName().equalsIgnoreCase(name)) + { + return npcTemplate; + } + } + + return null; + } + + /** + * Gets all templates matching the filter. + * @param filter + * @return the template list for the given filter + */ + public List getTemplates(Predicate filter) + { + final List result = new ArrayList<>(); + for (NpcTemplate npcTemplate : _npcs.values()) + { + if (filter.test(npcTemplate)) + { + result.add(npcTemplate); + } + } + + return result; + } + + /** + * Gets the all of level. + * @param levels of all the templates to get. + * @return the template list for the given level. + */ + public List getAllOfLevel(int... levels) + { + return getTemplates(template -> ArrayUtil.contains(levels, template.getLevel())); + } + + /** + * Gets the all monsters of level. + * @param levels of all the monster templates to get. + * @return the template list for the given level. + */ + public List getAllMonstersOfLevel(int... levels) + { + return getTemplates(template -> ArrayUtil.contains(levels, template.getLevel()) && template.isType("Monster")); + } + + /** + * Gets the all npc starting with. + * @param text of all the NPC templates which its name start with. + * @return the template list for the given letter. + */ + public List getAllNpcStartingWith(String text) + { + return getTemplates(template -> template.isType("Folk") && template.getName().startsWith(text)); + } + + /** + * Gets the all npc of class type. + * @param classTypes of all the templates to get. + * @return the template list for the given class type. + */ + public List getAllNpcOfClassType(String... classTypes) + { + return getTemplates(template -> ArrayUtil.contains(classTypes, template.getType(), true)); + } + + public void loadNpcsSkillLearn() + { + _npcs.values().forEach(template -> + { + final List teachInfo = SkillLearnData.getInstance().getSkillLearnData(template.getId()); + if (teachInfo != null) + { + template.addTeachInfo(teachInfo); + } + }); + } + + /** + * @return the IDs of monsters that have minions. + */ + public static Collection getMasterMonsterIDs() + { + return _masterMonsterIDs; + } + + /** + * Gets the single instance of NpcData. + * @return single instance of NpcData + */ + public static NpcData getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final NpcData INSTANCE = new NpcData(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/NpcNameLocalisationData.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/NpcNameLocalisationData.java new file mode 100644 index 0000000..a27e09d --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/NpcNameLocalisationData.java @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.data.xml; + +import java.io.File; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.w3c.dom.Document; + +import org.l2jmobius.commons.util.IXmlReader; +import org.l2jmobius.gameserver.config.custom.MultilingualSupportConfig; +import org.l2jmobius.gameserver.util.StatSet; + +/** + * @author Mobius + */ +public class NpcNameLocalisationData implements IXmlReader +{ + private static final Logger LOGGER = Logger.getLogger(NpcNameLocalisationData.class.getName()); + + private static final Map> NPC_NAME_LOCALISATIONS = new ConcurrentHashMap<>(); + private static String _lang; + + protected NpcNameLocalisationData() + { + load(); + } + + @Override + public void load() + { + NPC_NAME_LOCALISATIONS.clear(); + + if (MultilingualSupportConfig.MULTILANG_ENABLE) + { + for (String lang : MultilingualSupportConfig.MULTILANG_ALLOWED) + { + final File file = new File("data/lang/" + lang + "/NpcNameLocalisation.xml"); + if (!file.isFile()) + { + continue; + } + + NPC_NAME_LOCALISATIONS.put(lang, new ConcurrentHashMap<>()); + _lang = lang; + parseDatapackFile("data/lang/" + lang + "/NpcNameLocalisation.xml"); + final int count = NPC_NAME_LOCALISATIONS.get(lang).values().size(); + if (count == 0) + { + NPC_NAME_LOCALISATIONS.remove(lang); + } + else + { + LOGGER.log(Level.INFO, getClass().getSimpleName() + ": Loaded localisations for [" + lang + "]."); + } + } + } + } + + @Override + public void parseDocument(Document document, File file) + { + forEach(document, "list", listNode -> forEach(listNode, "localisation", localisationNode -> + { + final StatSet set = new StatSet(parseAttributes(localisationNode)); + NPC_NAME_LOCALISATIONS.get(_lang).put(set.getInt("id"), new String[] + { + set.getString("name"), + set.getString("title") + }); + })); + } + + /** + * Retrieves the localization data for a specified NPC in a given language. + * @param lang the language code for localization (e.g., "en", "fr"). + * @param id the unique identifier of the NPC. + * @return a {@code String[]} array containing the NPC's name and title, or {@code null} if the localization data does not exist for the given language and ID. + */ + public String[] getLocalisation(String lang, int id) + { + final Map localisations = NPC_NAME_LOCALISATIONS.get(lang); + if (localisations != null) + { + return localisations.get(id); + } + + return null; + } + + /** + * Checks if localization data exists for a specified NPC ID across all available languages. + * @param id the unique identifier of the NPC. + * @return {@code true} if localization data exists for the specified NPC ID in any language, {@code false} otherwise. + */ + public boolean hasLocalisation(int id) + { + for (Map data : NPC_NAME_LOCALISATIONS.values()) + { + if (data.containsKey(id)) + { + return true; + } + } + + return false; + } + + public static NpcNameLocalisationData getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final NpcNameLocalisationData INSTANCE = new NpcNameLocalisationData(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/NpcPersonalAIData.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/NpcPersonalAIData.java new file mode 100644 index 0000000..34f1798 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/NpcPersonalAIData.java @@ -0,0 +1,145 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.data.xml; + +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + +import org.l2jmobius.commons.util.Rnd; +import org.l2jmobius.gameserver.entity.actor.Npc; +import org.l2jmobius.gameserver.entity.spawns.Spawn; + +/** + * This class holds parameter, specific to certain NPCs.
    + * It can be either general parameters overridden for certain NPC instance instead of template parameters(aggro range, for example), or some optional parameters, handled by datapack scripts.
    + * @author GKR + */ +public class NpcPersonalAIData +{ + private final Map> _AIData = new HashMap<>(); + + /** + * Instantiates a new table. + */ + protected NpcPersonalAIData() + { + } + + /** + * Stores data for given spawn. + * @param spawnDat spawn to process + * @param data Map of AI values + */ + public void storeData(Spawn spawnDat, Map data) + { + if ((data != null) && !data.isEmpty()) + { + // Check for spawn name. Since spawn name is key for AI Data, generate random name, if spawn name isn't specified. + if (spawnDat.getName() == null) + { + spawnDat.setName(Long.toString(Rnd.nextLong())); + } + + _AIData.put(spawnDat.getName(), data); + } + } + + /** + * Gets AI value with given spawnName and paramName + * @param spawnName spawn name to check + * @param paramName parameter to check + * @return value of given parameter for given spawn name + */ + public int getAIValue(String spawnName, String paramName) + { + return hasAIValue(spawnName, paramName) ? _AIData.get(spawnName).get(paramName) : -1; + } + + /** + * Verifies if there is AI value with given spawnName and paramName + * @param spawnName spawn name to check + * @param paramName parameter name to check + * @return {@code true} if parameter paramName is set for spawn spawnName, {@code false} otherwise + */ + public boolean hasAIValue(String spawnName, String paramName) + { + if (spawnName == null) + { + return false; + } + + final Map params = _AIData.get(spawnName); + return (params != null) && params.containsKey(paramName); + } + + /** + * Initializes npc parameters by specified values. + * @param npc NPC to process + * @param spawn link to NPC's spawn + * @param spawnName name of spawn + */ + public void initializeNpcParameters(Npc npc, Spawn spawn, String spawnName) + { + final Map map = _AIData.get(spawnName); + if (map != null) + { + + try + { + // for (String key : map.keySet()) + for (Entry entry : map.entrySet()) + { + final String key = entry.getKey(); + switch (key) + { + case "disableRandomAnimation": + { + npc.setRandomAnimationEnabled((entry.getValue() == 0)); + break; + } + case "disableRandomWalk": + { + final boolean enable = entry.getValue() == 0; + npc.setRandomWalking(enable); + spawn.setRandomWalking(enable); + break; + } + } + } + } + catch (Exception e) + { + // Do nothing. + } + } + } + + /** + * Gets the single instance of NpcTable. + * @return single instance of NpcTable + */ + public static NpcPersonalAIData getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final NpcPersonalAIData INSTANCE = new NpcPersonalAIData(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/OptionData.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/OptionData.java new file mode 100644 index 0000000..62b735d --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/OptionData.java @@ -0,0 +1,229 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.data.xml; + +import java.io.File; +import java.util.Collections; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Logger; + +import org.w3c.dom.Document; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; + +import org.l2jmobius.commons.util.IXmlReader; +import org.l2jmobius.gameserver.mechanics.options.OptionSkillHolder; +import org.l2jmobius.gameserver.mechanics.options.OptionSkillType; +import org.l2jmobius.gameserver.mechanics.options.Options; +import org.l2jmobius.gameserver.mechanics.skill.Skill; +import org.l2jmobius.gameserver.mechanics.stats.Stat; +import org.l2jmobius.gameserver.mechanics.stats.functions.FuncTemplate; + +/** + * Item Option data. + * @author UnAfraid + */ +public class OptionData implements IXmlReader +{ + private static final Logger LOGGER = Logger.getLogger(OptionData.class.getName()); + + private static Options[] _options; + private static Map _optionMap = new ConcurrentHashMap<>(); + + protected OptionData() + { + load(); + } + + @Override + public synchronized void load() + { + parseDatapackDirectory("data/stats/augmentation/options", false); + + _options = new Options[Collections.max(_optionMap.keySet()) + 1]; + for (Entry option : _optionMap.entrySet()) + { + _options[option.getKey()] = option.getValue(); + } + + LOGGER.info(getClass().getSimpleName() + ": Loaded " + _optionMap.size() + " options."); + _optionMap.clear(); + } + + @Override + public void parseDocument(Document document, File file) + { + for (Node n = document.getFirstChild(); n != null; n = n.getNextSibling()) + { + if ("list".equalsIgnoreCase(n.getNodeName())) + { + for (Node d = n.getFirstChild(); d != null; d = d.getNextSibling()) + { + if ("option".equalsIgnoreCase(d.getNodeName())) + { + final int id = parseInteger(d.getAttributes(), "id"); + final Options option = new Options(id); + for (Node cd = d.getFirstChild(); cd != null; cd = cd.getNextSibling()) + { + switch (cd.getNodeName()) + { + case "stats": + { + for (Node fd = cd.getFirstChild(); fd != null; fd = fd.getNextSibling()) + { + switch (fd.getNodeName()) + { + case "add": + case "sub": + case "mul": + case "div": + case "set": + case "share": + case "enchant": + case "enchanthp": + { + parseFuncs(fd.getAttributes(), fd.getNodeName(), option); + } + } + } + break; + } + case "active_skill": + { + final int skillId = parseInteger(cd.getAttributes(), "id"); + final int skillLevel = parseInteger(cd.getAttributes(), "level"); + final Skill skill = SkillData.getInstance().getSkill(skillId, skillLevel); + if (skill != null) + { + option.setActiveSkill(skill); + } + else + { + LOGGER.info(getClass().getSimpleName() + ": Could not find skill " + skillId + "(" + skillLevel + ") used by option " + id + "."); + } + break; + } + case "passive_skill": + { + final int skillId = parseInteger(cd.getAttributes(), "id"); + final int skillLevel = parseInteger(cd.getAttributes(), "level"); + final Skill skill = SkillData.getInstance().getSkill(skillId, skillLevel); + if (skill != null) + { + option.setPassiveSkill(skill); + } + else + { + LOGGER.info(getClass().getSimpleName() + ": Could not find skill " + skillId + "(" + skillLevel + ") used by option " + id + "."); + } + break; + } + case "attack_skill": + { + final int skillId = parseInteger(cd.getAttributes(), "id"); + final int skillLevel = parseInteger(cd.getAttributes(), "level"); + final Skill skill = SkillData.getInstance().getSkill(skillId, skillLevel); + if (skill != null) + { + option.addActivationSkill(new OptionSkillHolder(skill, parseDouble(cd.getAttributes(), "chance"), OptionSkillType.ATTACK)); + } + else + { + LOGGER.info(getClass().getSimpleName() + ": Could not find skill " + skillId + "(" + skillLevel + ") used by option " + id + "."); + } + break; + } + case "magic_skill": + { + final int skillId = parseInteger(cd.getAttributes(), "id"); + final int skillLevel = parseInteger(cd.getAttributes(), "level"); + final Skill skill = SkillData.getInstance().getSkill(skillId, skillLevel); + if (skill != null) + { + option.addActivationSkill(new OptionSkillHolder(skill, parseDouble(cd.getAttributes(), "chance"), OptionSkillType.MAGIC)); + } + else + { + LOGGER.info(getClass().getSimpleName() + ": Could not find skill " + skillId + "(" + skillLevel + ") used by option " + id + "."); + } + break; + } + case "critical_skill": + { + final int skillId = parseInteger(cd.getAttributes(), "id"); + final int skillLevel = parseInteger(cd.getAttributes(), "level"); + final Skill skill = SkillData.getInstance().getSkill(skillId, skillLevel); + if (skill != null) + { + option.addActivationSkill(new OptionSkillHolder(skill, parseDouble(cd.getAttributes(), "chance"), OptionSkillType.CRITICAL)); + } + else + { + LOGGER.info(getClass().getSimpleName() + ": Could not find skill " + skillId + "(" + skillLevel + ") used by option " + id + "."); + } + break; + } + } + } + + _optionMap.put(option.getId(), option); + } + } + } + } + } + + private void parseFuncs(NamedNodeMap attrs, String functionName, Options op) + { + final Stat stat = Stat.valueOfXml(parseString(attrs, "stat")); + final double val = parseDouble(attrs, "val"); + int order = -1; + final Node orderNode = attrs.getNamedItem("order"); + if (orderNode != null) + { + order = Integer.parseInt(orderNode.getNodeValue()); + } + + op.addFunc(new FuncTemplate(null, null, functionName, order, stat, val)); + } + + public Options getOptions(int id) + { + if (_options.length > id) + { + return _options[id]; + } + + return null; + } + + /** + * Gets the single instance of OptionsData. + * @return single instance of OptionsData + */ + public static OptionData getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final OptionData INSTANCE = new OptionData(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/PetDataTable.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/PetDataTable.java new file mode 100644 index 0000000..14465e5 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/PetDataTable.java @@ -0,0 +1,246 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.data.xml; + +import java.io.File; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.w3c.dom.Document; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; + +import org.l2jmobius.commons.util.IXmlReader; +import org.l2jmobius.gameserver.data.holders.PetData; +import org.l2jmobius.gameserver.data.holders.PetLevelData; +import org.l2jmobius.gameserver.entity.actor.enums.player.MountType; +import org.l2jmobius.gameserver.util.StatSet; + +/** + * @author Zoey76 + */ +public class PetDataTable implements IXmlReader +{ + private final Map _pets = new ConcurrentHashMap<>(); + + protected PetDataTable() + { + load(); + } + + @Override + public void load() + { + _pets.clear(); + parseDatapackDirectory("data/stats/pets", false); + LOGGER.info(getClass().getSimpleName() + ": Loaded " + _pets.size() + " pets."); + } + + @Override + public void parseDocument(Document document, File file) + { + NamedNodeMap attrs; + for (Node d = document.getFirstChild().getFirstChild(); d != null; d = d.getNextSibling()) + { + if (d.getNodeName().equals("pet")) + { + final int npcId = parseInteger(d.getAttributes(), "id"); + final int itemId = parseInteger(d.getAttributes(), "itemId"); + + // Index ignored for now. + final PetData data = new PetData(npcId, itemId); + for (Node p = d.getFirstChild(); p != null; p = p.getNextSibling()) + { + if (p.getNodeName().equals("set")) + { + attrs = p.getAttributes(); + final String type = attrs.getNamedItem("name").getNodeValue(); + if ("food".equals(type)) + { + for (String foodId : attrs.getNamedItem("val").getNodeValue().split(";")) + { + data.addFood(Integer.parseInt(foodId)); + } + } + else if ("load".equals(type)) + { + data.setLoad(parseInteger(attrs, "val")); + } + else if ("hungry_limit".equals(type)) + { + data.setHungryLimit(parseInteger(attrs, "val")); + } + else if ("sync_level".equals(type)) + { + data.setSyncLevel(parseInteger(attrs, "val") == 1); + } + + // evolve ignored + } + else if (p.getNodeName().equals("skills")) + { + for (Node s = p.getFirstChild(); s != null; s = s.getNextSibling()) + { + if (s.getNodeName().equals("skill")) + { + attrs = s.getAttributes(); + data.addNewSkill(parseInteger(attrs, "skillId"), parseInteger(attrs, "skillLevel"), parseInteger(attrs, "minLevel")); + } + } + } + else if (p.getNodeName().equals("stats")) + { + for (Node s = p.getFirstChild(); s != null; s = s.getNextSibling()) + { + if (s.getNodeName().equals("stat")) + { + final int level = Integer.parseInt(s.getAttributes().getNamedItem("level").getNodeValue()); + final StatSet set = new StatSet(); + for (Node bean = s.getFirstChild(); bean != null; bean = bean.getNextSibling()) + { + if (bean.getNodeName().equals("set")) + { + attrs = bean.getAttributes(); + if (attrs.getNamedItem("name").getNodeValue().equals("speed_on_ride")) + { + set.set("walkSpeedOnRide", attrs.getNamedItem("walk").getNodeValue()); + set.set("runSpeedOnRide", attrs.getNamedItem("run").getNodeValue()); + set.set("slowSwimSpeedOnRide", attrs.getNamedItem("slowSwim").getNodeValue()); + set.set("fastSwimSpeedOnRide", attrs.getNamedItem("fastSwim").getNodeValue()); + if (attrs.getNamedItem("slowFly") != null) + { + set.set("slowFlySpeedOnRide", attrs.getNamedItem("slowFly").getNodeValue()); + } + + if (attrs.getNamedItem("fastFly") != null) + { + set.set("fastFlySpeedOnRide", attrs.getNamedItem("fastFly").getNodeValue()); + } + } + else + { + set.set(attrs.getNamedItem("name").getNodeValue(), attrs.getNamedItem("val").getNodeValue()); + } + } + } + + data.addNewStat(level, new PetLevelData(set)); + } + } + } + } + + _pets.put(npcId, data); + } + } + } + + /** + * Retrieves pet data based on the item ID used to summon it. + * @param itemId the item ID associated with the pet. + * @return the {@link PetData} associated with the given item ID, or {@code null} if no matching data is found. + */ + public PetData getPetDataByItemId(int itemId) + { + for (PetData data : _pets.values()) + { + if (data.getItemId() == itemId) + { + return data; + } + } + + return null; + } + + /** + * Retrieves pet level data for a specified pet ID and level. + * @param petId the unique identifier of the pet. + * @param petLevel the level of the pet. + * @return the {@link PetLevelData} containing the pet's parameters for the given ID and level, or the maximum level data if the requested level exceeds it. + */ + public PetLevelData getPetLevelData(int petId, int petLevel) + { + final PetData pd = getPetData(petId); + if (pd != null) + { + if (petLevel > pd.getMaxLevel()) + { + return pd.getPetLevelData(pd.getMaxLevel()); + } + + return pd.getPetLevelData(petLevel); + } + + return null; + } + + /** + * Retrieves the pet data for a specified pet ID. + * @param petId the unique identifier of the pet. + * @return the {@link PetData} associated with the given ID, or {@code null} if no data is available. + */ + public PetData getPetData(int petId) + { + if (!_pets.containsKey(petId)) + { + LOGGER.info(getClass().getSimpleName() + ": Missing pet data for npcid: " + petId); + } + + return _pets.get(petId); + } + + /** + * Retrieves the minimum level for a specified pet ID. + * @param petId the unique identifier of the pet. + * @return the minimum level of the pet with the given ID. + */ + public int getPetMinLevel(int petId) + { + return _pets.get(petId).getMinLevel(); + } + + /** + * Retrieves the summoning item ID for a pet based on its NPC ID. + * @param npcId the NPC ID associated with the pet. + * @return the item ID used to summon the pet associated with the given NPC ID. + */ + public int getPetItemsByNpc(int npcId) + { + return _pets.get(npcId).getItemId(); + } + + /** + * Checks if a pet with the given NPC ID is mountable. + * @param npcId the NPC ID to verify. + * @return {@code true} if the pet with the specified NPC ID is mountable, {@code false} otherwise. + */ + public static boolean isMountable(int npcId) + { + return MountType.findByNpcId(npcId) != MountType.NONE; + } + + public static PetDataTable getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final PetDataTable INSTANCE = new PetDataTable(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/PetSkillData.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/PetSkillData.java new file mode 100644 index 0000000..35eb833 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/PetSkillData.java @@ -0,0 +1,237 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.data.xml; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Logger; + +import org.w3c.dom.Document; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; + +import org.l2jmobius.commons.util.IXmlReader; +import org.l2jmobius.gameserver.entity.actor.Summon; +import org.l2jmobius.gameserver.mechanics.skill.Skill; +import org.l2jmobius.gameserver.mechanics.skill.holders.SkillHolder; + +/** + * @author Mobius + */ +public class PetSkillData implements IXmlReader +{ + private static final Logger LOGGER = Logger.getLogger(PetSkillData.class.getName()); + private final Map> _skillTrees = new HashMap<>(); + + protected PetSkillData() + { + load(); + } + + @Override + public void load() + { + _skillTrees.clear(); + parseDatapackFile("data/PetSkillData.xml"); + LOGGER.info(getClass().getSimpleName() + ": Loaded " + _skillTrees.size() + " skills."); + } + + @Override + public void parseDocument(Document document, File file) + { + for (Node n = document.getFirstChild(); n != null; n = n.getNextSibling()) + { + if ("list".equalsIgnoreCase(n.getNodeName())) + { + for (Node d = n.getFirstChild(); d != null; d = d.getNextSibling()) + { + if ("skill".equalsIgnoreCase(d.getNodeName())) + { + final NamedNodeMap attrs = d.getAttributes(); + final int npcId = parseInteger(attrs, "npcId"); + final int skillId = parseInteger(attrs, "skillId"); + final int skillLevel = parseInteger(attrs, "skillLevel"); + Map skillTree = _skillTrees.get(npcId); + if (skillTree == null) + { + skillTree = new HashMap<>(); + _skillTrees.put(npcId, skillTree); + } + + if (SkillData.getInstance().getSkill(skillId, skillLevel == 0 ? 1 : skillLevel) != null) + { + skillTree.put((long) SkillData.getSkillHashCode(skillId, skillLevel + 1), new SkillHolder(skillId, skillLevel)); + } + else + { + LOGGER.info(getClass().getSimpleName() + ": Could not find skill with id " + skillId + ", level " + skillLevel + " for NPC " + npcId + "."); + } + } + } + } + } + } + + /** + * Determines the highest available skill level for a specified skill ID based on the pet's level. + * @param pet the {@link Summon} pet whose available skill level is being checked. + * @param skillId the ID of the skill to retrieve the available level for. + * @return the highest available level for the specified skill ID, or 0 if the pet has no skills assigned. + */ + public int getAvailableLevel(Summon pet, int skillId) + { + int level = 0; + if (!_skillTrees.containsKey(pet.getId())) + { + // LOGGER.warning(getClass().getSimpleName() + ": Pet id " + pet.getId() + " does not have any skills assigned."); + return level; + } + + for (SkillHolder skillHolder : _skillTrees.get(pet.getId()).values()) + { + if (skillHolder.getSkillId() != skillId) + { + continue; + } + + if (skillHolder.getSkillLevel() == 0) + { + if (pet.getLevel() < 70) + { + level = pet.getLevel() / 10; + if (level <= 0) + { + level = 1; + } + } + else + { + level = 7 + ((pet.getLevel() - 70) / 5); + } + + // formula usable for skill that have 10 or more skill levels + final int maxLevel = SkillData.getInstance().getMaxLevel(skillHolder.getSkillId()); + if (level > maxLevel) + { + level = maxLevel; + } + break; + } + else if ((1 <= pet.getLevel()) && (skillHolder.getSkillLevel() > level)) + { + level = skillHolder.getSkillLevel(); + } + } + + return level; + } + + /** + * Retrieves a list of available skill IDs for a specified pet. + * @param pet the {@link Summon} pet whose available skill IDs are being retrieved. + * @return a {@link List} of skill IDs available for the given pet, or an empty list if no skills are assigned. + */ + public List getAvailableSkills(Summon pet) + { + final List skillIds = new ArrayList<>(); + if (!_skillTrees.containsKey(pet.getId())) + { + // LOGGER.warning(getClass().getSimpleName() + ": Pet id " + pet.getId() + " does not have any skills assigned."); + return skillIds; + } + + for (SkillHolder skillHolder : _skillTrees.get(pet.getId()).values()) + { + if (skillIds.contains(skillHolder.getSkillId())) + { + continue; + } + + skillIds.add(skillHolder.getSkillId()); + } + + return skillIds; + } + + /** + * Retrieves a list of known skills for a specified pet. + * @param pet the {@link Summon} pet whose known skills are being retrieved. + * @return a {@link List} of {@link Skill} objects known by the given pet, or an empty list if the pet has no skills. + */ + public List getKnownSkills(Summon pet) + { + final List skills = new ArrayList<>(); + if (!_skillTrees.containsKey(pet.getId())) + { + return skills; + } + + for (SkillHolder skillHolder : _skillTrees.get(pet.getId()).values()) + { + final Skill skill = skillHolder.getSkill(); + if (skills.contains(skill)) + { + continue; + } + + skills.add(skill); + } + + return skills; + } + + /** + * Retrieves a specific known skill for a pet by skill ID. + * @param pet the {@link Summon} pet whose known skill is being retrieved. + * @param skillId the ID of the skill to retrieve. + * @return the {@link Skill} object corresponding to the specified skill ID, or {@code null} if the skill is not known by the pet. + */ + public Skill getKnownSkill(Summon pet, int skillId) + { + if (!_skillTrees.containsKey(pet.getId())) + { + return null; + } + + for (SkillHolder skillHolder : _skillTrees.get(pet.getId()).values()) + { + if (skillHolder.getSkillId() == skillId) + { + return skillHolder.getSkill(); + } + } + + return null; + } + + public static PetSkillData getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final PetSkillData INSTANCE = new PetSkillData(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/PlayerTemplateData.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/PlayerTemplateData.java new file mode 100644 index 0000000..c901921 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/PlayerTemplateData.java @@ -0,0 +1,213 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.data.xml; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.logging.Logger; + +import org.w3c.dom.Document; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; + +import org.l2jmobius.commons.util.IXmlReader; +import org.l2jmobius.gameserver.entity.Location; +import org.l2jmobius.gameserver.entity.actor.enums.player.PlayerClass; +import org.l2jmobius.gameserver.entity.actor.templates.PlayerTemplate; +import org.l2jmobius.gameserver.util.StatSet; + +/** + * @author Forsaiken, Mobius + */ +public class PlayerTemplateData implements IXmlReader +{ + private static final Logger LOGGER = Logger.getLogger(PlayerTemplateData.class.getName()); + + private final Map _playerTemplates = new ConcurrentHashMap<>(); + final AtomicInteger _levelUpGainCount = new AtomicInteger(); + + protected PlayerTemplateData() + { + load(); + } + + @Override + public void load() + { + _playerTemplates.clear(); + _levelUpGainCount.set(0); + parseDatapackDirectory("data/stats/players/templates", true); + + LOGGER.info(getClass().getSimpleName() + ": Loaded " + _playerTemplates.size() + " player templates."); + LOGGER.info(getClass().getSimpleName() + ": Loaded " + _levelUpGainCount.get() + " level up gain records."); + } + + @Override + public void parseDocument(Document document, File file) + { + final int maxLevel = ExperienceData.getInstance().getMaxLevel(); + + NamedNodeMap attributes; + int classId = 0; + for (Node n = document.getFirstChild(); n != null; n = n.getNextSibling()) + { + if ("list".equalsIgnoreCase(n.getNodeName())) + { + for (Node d = n.getFirstChild(); d != null; d = d.getNextSibling()) + { + if ("classId".equalsIgnoreCase(d.getNodeName())) + { + classId = Integer.parseInt(d.getTextContent()); + } + else if ("staticData".equalsIgnoreCase(d.getNodeName())) + { + final StatSet set = new StatSet(); + set.set("classId", classId); + final List creationPoints = new ArrayList<>(); + for (Node nd = d.getFirstChild(); nd != null; nd = nd.getNextSibling()) + { + // Skip odd nodes. + if (nd.getNodeName().equals("#text")) + { + continue; + } + + if (nd.getChildNodes().getLength() > 1) + { + for (Node cnd = nd.getFirstChild(); cnd != null; cnd = cnd.getNextSibling()) + { + // Use CreatureTemplate(superclass) fields for male collision height and collision radius. + if (nd.getNodeName().equalsIgnoreCase("collisionMale")) + { + if (cnd.getNodeName().equalsIgnoreCase("radius")) + { + set.set("collisionRadius", cnd.getTextContent()); + } + else if (cnd.getNodeName().equalsIgnoreCase("height")) + { + set.set("collisionHeight", cnd.getTextContent()); + } + } + + if ("node".equalsIgnoreCase(cnd.getNodeName())) + { + attributes = cnd.getAttributes(); + creationPoints.add(new Location(parseInteger(attributes, "x"), parseInteger(attributes, "y"), parseInteger(attributes, "z"))); + } + else if ("walk".equalsIgnoreCase(cnd.getNodeName())) + { + set.set("baseWalkSpd", cnd.getTextContent()); + } + else if ("run".equalsIgnoreCase(cnd.getNodeName())) + { + set.set("baseRunSpd", cnd.getTextContent()); + } + else if ("slowSwim".equals(cnd.getNodeName())) + { + set.set("baseSwimWalkSpd", cnd.getTextContent()); + } + else if ("fastSwim".equals(cnd.getNodeName())) + { + set.set("baseSwimRunSpd", cnd.getTextContent()); + } + else if (!cnd.getNodeName().equals("#text")) + { + set.set(nd.getNodeName() + cnd.getNodeName(), cnd.getTextContent()); + } + } + } + else + { + set.set(nd.getNodeName(), nd.getTextContent()); + } + } + + // Calculate total pdef and mdef from parts. + set.set("basePDef", set.getInt("basePDefchest", 0) + set.getInt("basePDeflegs", 0) + set.getInt("basePDefhead", 0) + set.getInt("basePDeffeet", 0) + set.getInt("basePDefgloves", 0) + set.getInt("basePDefunderwear", 0) + set.getInt("basePDefcloak", 0)); + set.set("baseMDef", set.getInt("baseMDefrear", 0) + set.getInt("baseMDeflear", 0) + set.getInt("baseMDefrfinger", 0) + set.getInt("baseMDefrfinger", 0) + set.getInt("baseMDefneck", 0)); + _playerTemplates.put(PlayerClass.getPlayerClass(classId), new PlayerTemplate(set, creationPoints)); + } + else if ("lvlUpgainData".equalsIgnoreCase(d.getNodeName())) + { + int level = 0; + final PlayerTemplate template = _playerTemplates.get(PlayerClass.getPlayerClass(classId)); + for (Node lvlNode = d.getFirstChild(); lvlNode != null; lvlNode = lvlNode.getNextSibling()) + { + if ("level".equalsIgnoreCase(lvlNode.getNodeName())) + { + attributes = lvlNode.getAttributes(); + level = parseInteger(attributes, "val"); + if (level > (maxLevel - 1)) + { + return; + } + + for (Node valNode = lvlNode.getFirstChild(); valNode != null; valNode = valNode.getNextSibling()) + { + final String nodeName = valNode.getNodeName(); + if (nodeName.startsWith("hp") || nodeName.startsWith("mp") || nodeName.startsWith("cp")) + { + template.setUpgainValue(nodeName, level, Double.parseDouble(valNode.getTextContent())); + _levelUpGainCount.incrementAndGet(); + } + } + } + } + } + } + } + } + } + + /** + * Retrieves the {@link PlayerTemplate} associated with the specified {@link PlayerClass}. + * @param playerClass the {@link PlayerClass} for which to retrieve the template. + * @return the {@link PlayerTemplate} associated with the given {@link PlayerClass}, or {@code null} if no template is found. + */ + public PlayerTemplate getTemplate(PlayerClass playerClass) + { + return _playerTemplates.get(playerClass); + } + + /** + * Retrieves the {@link PlayerTemplate} associated with the specified class ID. + * @param classId the integer ID of the class for which to retrieve the template. + * @return the {@link PlayerTemplate} associated with the given class ID, or {@code null} if no template is found. + */ + public PlayerTemplate getTemplate(int classId) + { + return _playerTemplates.get(PlayerClass.getPlayerClass(classId)); + } + + public static PlayerTemplateData getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final PlayerTemplateData INSTANCE = new PlayerTemplateData(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/RecipeData.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/RecipeData.java new file mode 100644 index 0000000..fa86980 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/RecipeData.java @@ -0,0 +1,275 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.data.xml; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.w3c.dom.Document; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; + +import org.l2jmobius.commons.util.IXmlReader; +import org.l2jmobius.gameserver.data.holders.RecipeHolder; +import org.l2jmobius.gameserver.data.holders.RecipeStatHolder; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.item.recipe.RecipeList; +import org.l2jmobius.gameserver.util.StatSet; + +/** + * The Class RecipeData. + * @author Zoey76 + */ +public class RecipeData implements IXmlReader +{ + private final Map _recipes = new HashMap<>(); + + /** + * Instantiates a new recipe data. + */ + protected RecipeData() + { + load(); + } + + @Override + public void load() + { + _recipes.clear(); + parseDatapackFile("data/Recipes.xml"); + LOGGER.info(getClass().getSimpleName() + ": Loaded " + _recipes.size() + " recipes."); + } + + @Override + public void parseDocument(Document document, File file) + { + // TODO: Cleanup checks enforced by XSD. + final List recipePartList = new ArrayList<>(); + final List recipeStatUseList = new ArrayList<>(); + final List recipeAltStatChangeList = new ArrayList<>(); + for (Node n = document.getFirstChild(); n != null; n = n.getNextSibling()) + { + if ("list".equalsIgnoreCase(n.getNodeName())) + { + RECIPES_FILE: for (Node d = n.getFirstChild(); d != null; d = d.getNextSibling()) + { + if ("item".equalsIgnoreCase(d.getNodeName())) + { + recipePartList.clear(); + recipeStatUseList.clear(); + recipeAltStatChangeList.clear(); + final NamedNodeMap attrs = d.getAttributes(); + Node att; + int id = -1; + att = attrs.getNamedItem("id"); + if (att == null) + { + LOGGER.severe(getClass().getSimpleName() + ": Missing id for recipe item, skipping"); + continue; + } + + final StatSet set = new StatSet(); + id = Integer.parseInt(att.getNodeValue()); + set.set("id", id); + + att = attrs.getNamedItem("recipeId"); + if (att == null) + { + LOGGER.severe(getClass().getSimpleName() + ": Missing recipeId for recipe item id: " + id + ", skipping"); + continue; + } + + set.set("recipeId", Integer.parseInt(att.getNodeValue())); + + att = attrs.getNamedItem("name"); + if (att == null) + { + LOGGER.severe(getClass().getSimpleName() + ": Missing name for recipe item id: " + id + ", skipping"); + continue; + } + + set.set("recipeName", att.getNodeValue()); + + att = attrs.getNamedItem("craftLevel"); + if (att == null) + { + LOGGER.severe(getClass().getSimpleName() + ": Missing level for recipe item id: " + id + ", skipping"); + continue; + } + + set.set("craftLevel", Integer.parseInt(att.getNodeValue())); + + att = attrs.getNamedItem("type"); + if (att == null) + { + LOGGER.severe(getClass().getSimpleName() + ": Missing type for recipe item id: " + id + ", skipping"); + continue; + } + + set.set("isDwarvenRecipe", att.getNodeValue().equalsIgnoreCase("dwarven")); + + att = attrs.getNamedItem("successRate"); + if (att == null) + { + LOGGER.severe(getClass().getSimpleName() + ": Missing successRate for recipe item id: " + id + ", skipping"); + continue; + } + + set.set("successRate", Integer.parseInt(att.getNodeValue())); + + for (Node c = d.getFirstChild(); c != null; c = c.getNextSibling()) + { + if ("statUse".equalsIgnoreCase(c.getNodeName())) + { + final String statName = c.getAttributes().getNamedItem("name").getNodeValue(); + final int value = Integer.parseInt(c.getAttributes().getNamedItem("value").getNodeValue()); + try + { + recipeStatUseList.add(new RecipeStatHolder(statName, value)); + } + catch (Exception e) + { + LOGGER.severe(getClass().getSimpleName() + ": Error in StatUse parameter for recipe item id: " + id + ", skipping"); + continue RECIPES_FILE; + } + } + else if ("altStatChange".equalsIgnoreCase(c.getNodeName())) + { + final String statName = c.getAttributes().getNamedItem("name").getNodeValue(); + final int value = Integer.parseInt(c.getAttributes().getNamedItem("value").getNodeValue()); + try + { + recipeAltStatChangeList.add(new RecipeStatHolder(statName, value)); + } + catch (Exception e) + { + LOGGER.severe(getClass().getSimpleName() + ": Error in AltStatChange parameter for recipe item id: " + id + ", skipping"); + continue RECIPES_FILE; + } + } + else if ("ingredient".equalsIgnoreCase(c.getNodeName())) + { + final int ingId = Integer.parseInt(c.getAttributes().getNamedItem("id").getNodeValue()); + final int ingCount = Integer.parseInt(c.getAttributes().getNamedItem("count").getNodeValue()); + recipePartList.add(new RecipeHolder(ingId, ingCount)); + } + else if ("production".equalsIgnoreCase(c.getNodeName())) + { + set.set("itemId", Integer.parseInt(c.getAttributes().getNamedItem("id").getNodeValue())); + set.set("count", Integer.parseInt(c.getAttributes().getNamedItem("count").getNodeValue())); + } + } + + final RecipeList recipeList = new RecipeList(set); + for (RecipeHolder recipePart : recipePartList) + { + recipeList.addRecipe(recipePart); + } + + for (RecipeStatHolder recipeStatUse : recipeStatUseList) + { + recipeList.addStatUse(recipeStatUse); + } + + for (RecipeStatHolder recipeAltStatChange : recipeAltStatChangeList) + { + recipeList.addAltStatChange(recipeAltStatChange); + } + + _recipes.put(id, recipeList); + } + } + } + } + } + + /** + * Gets the recipe list. + * @param listId the list id + * @return the recipe list + */ + public RecipeList getRecipeList(int listId) + { + return _recipes.get(listId); + } + + /** + * Gets the recipe by item id. + * @param itemId the item id + * @return the recipe by item id + */ + public RecipeList getRecipeByItemId(int itemId) + { + for (RecipeList find : _recipes.values()) + { + if (find.getRecipeId() == itemId) + { + return find; + } + } + + return null; + } + + /** + * Gets the all item ids. + * @return the all item ids + */ + public int[] getAllItemIds() + { + final int[] idList = new int[_recipes.size()]; + int i = 0; + for (RecipeList rec : _recipes.values()) + { + idList[i++] = rec.getRecipeId(); + } + + return idList; + } + + /** + * Gets the valid recipe list. + * @param player the player + * @param id the recipe list id + * @return the valid recipe list + */ + public RecipeList getValidRecipeList(Player player, int id) + { + final RecipeList recipeList = _recipes.get(id); + if ((recipeList == null) || (recipeList.getRecipes().length == 0)) + { + player.sendMessage(getClass().getSimpleName() + ": No recipe for: " + id); + player.setCrafting(false); + return null; + } + + return recipeList; + } + + public static RecipeData getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final RecipeData INSTANCE = new RecipeData(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/SendMessageLocalisationData.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/SendMessageLocalisationData.java new file mode 100644 index 0000000..842394c --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/SendMessageLocalisationData.java @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.data.xml; + +import java.io.File; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.w3c.dom.Document; + +import org.l2jmobius.commons.util.IXmlReader; +import org.l2jmobius.gameserver.config.custom.MultilingualSupportConfig; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.util.StatSet; + +/** + * @author Mobius + */ +public class SendMessageLocalisationData implements IXmlReader +{ + private static final Logger LOGGER = Logger.getLogger(SendMessageLocalisationData.class.getName()); + + private static final String SPLIT_STRING = "XXX"; + private static final Map> SEND_MESSAGE_LOCALISATIONS = new ConcurrentHashMap<>(); + private static String _lang; + + protected SendMessageLocalisationData() + { + load(); + } + + @Override + public void load() + { + SEND_MESSAGE_LOCALISATIONS.clear(); + + if (MultilingualSupportConfig.MULTILANG_ENABLE) + { + for (String lang : MultilingualSupportConfig.MULTILANG_ALLOWED) + { + final File file = new File("data/lang/" + lang + "/SendMessageLocalisation.xml"); + if (!file.isFile()) + { + continue; + } + + SEND_MESSAGE_LOCALISATIONS.put(lang, new ConcurrentHashMap<>()); + _lang = lang; + parseDatapackFile("data/lang/" + lang + "/SendMessageLocalisation.xml"); + final int count = SEND_MESSAGE_LOCALISATIONS.get(lang).values().size(); + if (count == 0) + { + SEND_MESSAGE_LOCALISATIONS.remove(lang); + } + else + { + LOGGER.log(Level.INFO, getClass().getSimpleName() + ": Loaded localisations for [" + lang + "]."); + } + } + } + } + + @Override + public void parseDocument(Document document, File file) + { + forEach(document, "list", listNode -> forEach(listNode, "localisation", localisationNode -> + { + final StatSet set = new StatSet(parseAttributes(localisationNode)); + SEND_MESSAGE_LOCALISATIONS.get(_lang).put(set.getString("message").split(SPLIT_STRING), set.getString("translation").split(SPLIT_STRING)); + })); + } + + public static String getLocalisation(Player player, String message) + { + if (MultilingualSupportConfig.MULTILANG_ENABLE && (player != null)) + { + final Map localisations = SEND_MESSAGE_LOCALISATIONS.get(player.getLang()); + if (localisations != null) + { + // No pretty way of doing something like this. + // Consider using proper SystemMessages where possible. + String[] searchMessage; + String[] replacementMessage; + String localisation = message; + boolean found; + for (Entry entry : localisations.entrySet()) + { + searchMessage = entry.getKey(); + replacementMessage = entry.getValue(); + + // Exact match. + if (searchMessage.length == 1) + { + if (searchMessage[0].equals(localisation)) + { + return replacementMessage[0]; + } + } + else // Split match. + { + found = true; + for (String part : searchMessage) + { + if (!localisation.contains(part)) + { + found = false; + break; + } + } + + if (found) + { + for (int i = 0; i < searchMessage.length; i++) + { + localisation = localisation.replace(searchMessage[i], replacementMessage[i]); + } + break; + } + } + } + + return localisation; + } + } + + return message; + } + + public static SendMessageLocalisationData getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final SendMessageLocalisationData INSTANCE = new SendMessageLocalisationData(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/SiegeScheduleData.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/SiegeScheduleData.java new file mode 100644 index 0000000..541bc04 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/SiegeScheduleData.java @@ -0,0 +1,129 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.data.xml; + +import java.io.File; +import java.util.Calendar; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Logger; + +import org.w3c.dom.Document; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; + +import org.l2jmobius.commons.util.IXmlReader; +import org.l2jmobius.commons.util.StringUtil; +import org.l2jmobius.gameserver.mechanics.siege.SiegeScheduleDate; +import org.l2jmobius.gameserver.util.StatSet; + +/** + * @author UnAfraid + */ +public class SiegeScheduleData implements IXmlReader +{ + private static final Logger LOGGER = Logger.getLogger(SiegeScheduleData.class.getName()); + + private final Map _scheduleData = new HashMap<>(); + + protected SiegeScheduleData() + { + load(); + } + + @Override + public synchronized void load() + { + parseDatapackFile("config/SiegeSchedule.xml"); + LOGGER.info(getClass().getSimpleName() + ": Loaded " + _scheduleData.size() + " siege schedulers."); + } + + @Override + public void parseDocument(Document document, File file) + { + for (Node n = document.getFirstChild(); n != null; n = n.getNextSibling()) + { + if ("list".equalsIgnoreCase(n.getNodeName())) + { + for (Node cd = n.getFirstChild(); cd != null; cd = cd.getNextSibling()) + { + switch (cd.getNodeName()) + { + case "schedule": + { + final StatSet set = new StatSet(); + final NamedNodeMap attrs = cd.getAttributes(); + for (int i = 0; i < attrs.getLength(); i++) + { + final Node node = attrs.item(i); + final String key = node.getNodeName(); + String val = node.getNodeValue(); + if ("day".equals(key) && !StringUtil.isNumeric(val)) + { + val = Integer.toString(getValueForField(val)); + } + + set.set(key, val); + } + + _scheduleData.put(set.getInt("castleId"), new SiegeScheduleDate(set)); + break; + } + } + } + } + } + } + + /** + * Retrieves the integer value associated with a specific field name in the {@code Calendar} class. This method uses reflection to access the value of the specified field from {@code Calendar}. + * @param field the name of the field in the {@code Calendar} class to retrieve + * @return the integer value of the specified {@code Calendar} field, or {@code -1} if the field cannot be accessed or does not exist + */ + private int getValueForField(String field) + { + try + { + return Calendar.class.getField(field).getInt(Calendar.class.getName()); + } + catch (Exception e) + { + LOGGER.warning(getClass().getSimpleName() + ": Could not get value for field " + field + ". " + e.getMessage()); + return -1; + } + } + + /** + * Retrieves the scheduled siege date associated with a specific castle ID. + * @param castleId the ID of the castle for which to retrieve the siege schedule date + * @return the {@code SiegeScheduleDate} for the specified castle ID, or {@code null} if no schedule data is available for this castle ID + */ + public SiegeScheduleDate getScheduleDateForCastleId(int castleId) + { + return _scheduleData.get(castleId); + } + + public static SiegeScheduleData getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final SiegeScheduleData INSTANCE = new SiegeScheduleData(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/SkillData.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/SkillData.java new file mode 100644 index 0000000..76b297e --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/SkillData.java @@ -0,0 +1,304 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.data.xml; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ScheduledFuture; +import java.util.logging.Logger; + +import org.l2jmobius.commons.config.ThreadConfig; +import org.l2jmobius.commons.threads.ThreadPool; +import org.l2jmobius.commons.util.StringUtil; +import org.l2jmobius.commons.util.TraceUtil; +import org.l2jmobius.gameserver.config.GeneralConfig; +import org.l2jmobius.gameserver.config.ServerConfig; +import org.l2jmobius.gameserver.mechanics.skill.Skill; +import org.l2jmobius.gameserver.util.DocumentSkill; + +/** + * The {@code SkillData} class is responsible for parsing, loading, and managing skill data within the game server. + */ +public class SkillData +{ + private static final Logger LOGGER = Logger.getLogger(SkillData.class.getName()); + + private final Map _skillsByHash = new ConcurrentHashMap<>(); + private final Map _maxSkillLevels = new ConcurrentHashMap<>(); + private final Set _enchantable = ConcurrentHashMap.newKeySet(); + private final List _skillFiles = new ArrayList<>(); + private static int count = 0; + + protected SkillData() + { + processDirectory("data/stats/skills", _skillFiles); + if (GeneralConfig.CUSTOM_SKILLS_LOAD) + { + processDirectory("data/stats/skills/custom", _skillFiles); + } + + load(); + } + + private void processDirectory(String dirName, List list) + { + final File dir = new File(ServerConfig.DATAPACK_ROOT, dirName); + if (!dir.exists()) + { + LOGGER.warning("Directory " + dir.getAbsolutePath() + " does not exist."); + return; + } + + final File[] files = dir.listFiles(); + if (files != null) + { + for (File file : files) + { + if (file.isFile() && file.getName().toLowerCase().endsWith(".xml")) + { + list.add(file); + } + } + } + } + + private void load() + { + final Map temp = new ConcurrentHashMap<>(); + loadAllSkills(temp); + + _skillsByHash.clear(); + _skillsByHash.putAll(temp); + + _maxSkillLevels.clear(); + _enchantable.clear(); + for (Skill skill : _skillsByHash.values()) + { + final int skillId = skill.getId(); + final int skillLevel = skill.getLevel(); + if (skillLevel > 99) + { + if (!_enchantable.contains(skillId)) + { + _enchantable.add(skillId); + } + continue; + } + + // Only non-enchanted skills. + final int maxLevel = getMaxLevel(skillId); + if (skillLevel > maxLevel) + { + _maxSkillLevels.put(skillId, skillLevel); + } + } + } + + public List loadSkills(File file) + { + if (file == null) + { + LOGGER.warning("Skill file not found."); + return null; + } + + final DocumentSkill doc = new DocumentSkill(file); + doc.parse(); + return doc.getSkills(); + } + + public void loadAllSkills(Map allSkills) + { + if (ThreadConfig.THREADS_FOR_LOADING) + { + final Collection> jobs = ConcurrentHashMap.newKeySet(); + for (File file : _skillFiles) + { + jobs.add(ThreadPool.schedule(() -> + { + final List skills = loadSkills(file); + if (skills == null) + { + return; + } + + for (Skill skill : skills) + { + allSkills.put(SkillData.getSkillHashCode(skill), skill); + count++; + } + }, 0)); + } + + while (!jobs.isEmpty()) + { + for (ScheduledFuture job : jobs) + { + if ((job == null) || job.isDone() || job.isCancelled()) + { + jobs.remove(job); + } + } + } + } + else + { + for (File file : _skillFiles) + { + final List skills = loadSkills(file); + if (skills == null) + { + return; + } + + for (Skill skill : skills) + { + allSkills.put(SkillData.getSkillHashCode(skill), skill); + count++; + } + } + } + + LOGGER.info(getClass().getSimpleName() + ": Loaded " + count + " Skill templates from XML files."); + } + + public void reload() + { + load(); + + // Reload Skill Tree as well. + SkillTreeData.getInstance().load(); + } + + /** + * Generates a unique hash code for a skill based on its ID and level. + * @param skill The {@link Skill} instance to be hashed. + * @return A unique hash code combining skill ID and level. + */ + public static int getSkillHashCode(Skill skill) + { + return getSkillHashCode(skill.getId(), skill.getLevel()); + } + + /** + * Generates a unique hash code for a skill based on its ID and level. + * @param skillId The ID of the skill. + * @param skillLevel The level of the skill. + * @return A unique hash code combining skill ID and level. + */ + public static int getSkillHashCode(int skillId, int skillLevel) + { + return (skillId * 1021) + skillLevel; + } + + /** + * Retrieves a skill based on its ID and level. + * @param skillId The ID of the skill. + * @param level The level of the skill. + * @return The {@link Skill} object if found, or null if not found. + */ + public Skill getSkill(int skillId, int level) + { + final Skill result = _skillsByHash.get(getSkillHashCode(skillId, level)); + if (result != null) + { + return result; + } + + // Skill/level not found, fix for transformation scripts. + final int maxLevel = getMaxLevel(skillId); + + // Requested level too high. + if ((maxLevel > 0) && (level > maxLevel)) + { + LOGGER.warning(StringUtil.concat(getClass().getSimpleName(), ": Call to unexisting skill level id: ", String.valueOf(skillId), " requested level: ", String.valueOf(level), " max level: ", String.valueOf(maxLevel), ".", System.lineSeparator(), TraceUtil.getStackTrace(new Exception()))); + return _skillsByHash.get(getSkillHashCode(skillId, maxLevel)); + } + + LOGGER.warning(StringUtil.concat(getClass().getSimpleName(), ": No skill info found for skill id ", String.valueOf(skillId), " and skill level ", String.valueOf(level), ".", System.lineSeparator(), TraceUtil.getStackTrace(new Exception()))); + return null; + } + + /** + * Retrieves the maximum level available for a skill based on its ID. + * @param skillId The ID of the skill. + * @return The maximum level for the specified skill ID, or 0 if the skill ID is not found. + */ + public int getMaxLevel(int skillId) + { + final Integer maxLevel = _maxSkillLevels.get(skillId); + return maxLevel != null ? maxLevel : 0; + } + + /** + * Verifies if the given skill ID correspond to an enchantable skill. + * @param skillId the skill ID + * @return {@code true} if the skill is enchantable, {@code false} otherwise + */ + public boolean isEnchantable(int skillId) + { + return _enchantable.contains(skillId); + } + + /** + * Retrieves an array of siege-related skills based on certain conditions. + *

    + * If {@code addNoble} is true, the list will include the Advanced Headquarters skill. If {@code hasCastle} is true, additional castle-specific skills such as Outpost Construction and Outpost Demolition are added. + *

    + * @param addNoble Whether to include Advanced Headquarters skill. + * @param hasCastle Whether to include castle-related skills. + * @return A {@link List} of siege-related {@link Skill} objects. + */ + public Skill[] getSiegeSkills(boolean addNoble, boolean hasCastle) + { + final Skill[] result = new Skill[2 + (addNoble ? 1 : 0) + (hasCastle ? 2 : 0)]; + int i = 0; + result[i++] = _skillsByHash.get(getSkillHashCode(246, 1)); + result[i++] = _skillsByHash.get(getSkillHashCode(247, 1)); + if (addNoble) + { + result[i++] = _skillsByHash.get(getSkillHashCode(326, 1)); + } + + if (hasCastle) + { + result[i++] = _skillsByHash.get(getSkillHashCode(844, 1)); + result[i++] = _skillsByHash.get(getSkillHashCode(845, 1)); + } + + return result; + } + + public static SkillData getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final SkillData INSTANCE = new SkillData(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/SkillLearnData.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/SkillLearnData.java new file mode 100644 index 0000000..f03d9c4 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/SkillLearnData.java @@ -0,0 +1,101 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.data.xml; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.w3c.dom.Document; +import org.w3c.dom.Node; + +import org.l2jmobius.commons.util.IXmlReader; +import org.l2jmobius.gameserver.entity.actor.enums.player.PlayerClass; + +/** + * Holds all skill learn data for all NPCs. + * @author xban1x + */ +public class SkillLearnData implements IXmlReader +{ + private final Map> _skillLearn = new HashMap<>(); + + protected SkillLearnData() + { + load(); + } + + @Override + public synchronized void load() + { + _skillLearn.clear(); + parseDatapackFile("data/SkillLearn.xml"); + LOGGER.info(getClass().getSimpleName() + ": Loaded " + _skillLearn.size() + " skill learn data."); + } + + @Override + public void parseDocument(Document document, File file) + { + for (Node node = document.getFirstChild(); node != null; node = node.getNextSibling()) + { + if ("list".equalsIgnoreCase(node.getNodeName())) + { + for (Node list_node = node.getFirstChild(); list_node != null; list_node = list_node.getNextSibling()) + { + if ("npc".equalsIgnoreCase(list_node.getNodeName())) + { + final List classIds = new ArrayList<>(); + for (Node c = list_node.getFirstChild(); c != null; c = c.getNextSibling()) + { + if ("classId".equalsIgnoreCase(c.getNodeName())) + { + classIds.add(PlayerClass.getPlayerClass(Integer.parseInt(c.getTextContent()))); + } + } + + _skillLearn.put(parseInteger(list_node.getAttributes(), "id"), classIds); + } + } + } + } + } + + /** + * @param npcId + * @return {@link List} of {@link PlayerClass}'s that this npcId can teach. + */ + public List getSkillLearnData(int npcId) + { + return _skillLearn.get(npcId); + } + + /** + * Gets the single instance of SkillLearnData. + * @return single instance of SkillLearnData + */ + public static SkillLearnData getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final SkillLearnData INSTANCE = new SkillLearnData(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/SkillTreeData.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/SkillTreeData.java new file mode 100644 index 0000000..58ecb75 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/SkillTreeData.java @@ -0,0 +1,1067 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.data.xml; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import org.w3c.dom.Document; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; + +import org.l2jmobius.commons.util.IXmlReader; +import org.l2jmobius.gameserver.config.PlayerConfig; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.enums.creature.Race; +import org.l2jmobius.gameserver.entity.actor.enums.player.PlayerClass; +import org.l2jmobius.gameserver.entity.actor.enums.player.SocialClass; +import org.l2jmobius.gameserver.entity.clan.Clan; +import org.l2jmobius.gameserver.entity.item.holders.ItemHolder; +import org.l2jmobius.gameserver.mechanics.skill.CommonSkill; +import org.l2jmobius.gameserver.mechanics.skill.Skill; +import org.l2jmobius.gameserver.mechanics.skill.enums.AcquireSkillType; +import org.l2jmobius.gameserver.mechanics.skill.holders.SkillHolder; +import org.l2jmobius.gameserver.mechanics.skill.holders.SkillLearn; +import org.l2jmobius.gameserver.util.StatSet; + +/** + * This class loads and manage the characters and pledges skills trees.
    + * Here can be found the following skill trees:
    + *
      + *
    • Class skill trees: player skill trees for each class.
    • + *
    • Transfer skill trees: player skill trees for each healer class.
    • + *
    • Collect skill tree: player skill tree for Gracia related skills.
    • + *
    • Fishing skill tree: player skill tree for fishing related skills.
    • + *
    • Transform skill tree: player skill tree for transformation related skills.
    • + *
    • Sub-Class skill tree: player skill tree for sub-class related skills.
    • + *
    • Noble skill tree: player skill tree for noblesse related skills.
    • + *
    • Hero skill tree: player skill tree for heroes related skills.
    • + *
    • GM skill tree: player skill tree for Game Master related skills.
    • + *
    • Common skill tree: custom skill tree for players, skills in this skill tree will be available for all players.
    • + *
    • Pledge skill tree: clan skill tree for main clan.
    • + *
    • Sub-Pledge skill tree: clan skill tree for sub-clans.
    • + *
    + * For easy customization of player class skill trees, the parent Id of each class is taken from the XML data, this means you can use a different class parent Id than in the normal game play, for example all 3rd class dagger users will have Treasure Hunter skills as 1st and 2nd class skills.
    + * For XML schema please refer to skillTrees.xsd in datapack in xsd folder and for parameters documentation refer to documentation.txt in skillTrees folder.
    + * @author Zoey76, Mobius + */ +public class SkillTreeData implements IXmlReader +{ + // ClassId, Map of Skill Hash Code, SkillLearn + private final Map> _classSkillTrees = new ConcurrentHashMap<>(); + private final Map> _completeClassSkillTree = new HashMap<>(); + + // Skill Hash Code, SkillLearn + private final Map _fishingSkillTree = new ConcurrentHashMap<>(); + private final Map _pledgeSkillTree = new ConcurrentHashMap<>(); + private final Map _commonSkillTree = new ConcurrentHashMap<>(); + + // Other skill trees + private final Map _nobleSkillTree = new ConcurrentHashMap<>(); + private final Map _heroSkillTree = new ConcurrentHashMap<>(); + private final Map _gameMasterSkillTree = new ConcurrentHashMap<>(); + private final Map _gameMasterAuraSkillTree = new ConcurrentHashMap<>(); + + // Checker, sorted arrays of hash codes + private Map _skillsByClassIdHashCodes; // Occupation skills + private Map _skillsByRaceHashCodes; // Race-specific Transformations + private int[] _allSkillsHashCodes; // Fishing, Collection, Transformations, Common Skills. + + /** Parent class Ids are read from XML and stored in this map, to allow easy customization. */ + private final Map _parentClassMap = new LinkedHashMap<>(); + + private boolean _loading = true; + + /** + * Instantiates a new skill trees data. + */ + protected SkillTreeData() + { + load(); + } + + @Override + public void load() + { + _loading = true; + _parentClassMap.clear(); + _classSkillTrees.clear(); + _fishingSkillTree.clear(); + _pledgeSkillTree.clear(); + _nobleSkillTree.clear(); + _heroSkillTree.clear(); + _gameMasterSkillTree.clear(); + _gameMasterAuraSkillTree.clear(); + + // Load files. + parseDatapackDirectory("data/stats/players/skillTrees/", true); + + // Cache the complete class skill trees. + _completeClassSkillTree.clear(); + for (Entry> entry : _classSkillTrees.entrySet()) + { + final Map skillTree = new LinkedHashMap<>(); + + // Add all skills that belong to all classes. + skillTree.putAll(_commonSkillTree); + + final PlayerClass entryPlayerClass = entry.getKey(); + PlayerClass currentPlayerClass = entryPlayerClass; + + final List classSequence = new ArrayList<>(); + while (currentPlayerClass != null) + { + classSequence.addFirst(currentPlayerClass); + currentPlayerClass = _parentClassMap.get(currentPlayerClass); + } + + for (PlayerClass cid : classSequence) + { + final Map classSkillTree = _classSkillTrees.get(cid); + if (classSkillTree != null) + { + skillTree.putAll(classSkillTree); + } + } + + _completeClassSkillTree.put(entryPlayerClass, skillTree); + } + + // Generate check arrays. + generateCheckArrays(); + + // Logs a report with skill trees info. + report(); + + _loading = false; + } + + /** + * Parse a skill tree file and store it into the correct skill tree. + */ + @Override + public void parseDocument(Document document, File file) + { + int cId = -1; + PlayerClass playerClass = null; + for (Node n = document.getFirstChild(); n != null; n = n.getNextSibling()) + { + if ("list".equalsIgnoreCase(n.getNodeName())) + { + for (Node d = n.getFirstChild(); d != null; d = d.getNextSibling()) + { + if ("skillTree".equalsIgnoreCase(d.getNodeName())) + { + final Map classSkillTree = new HashMap<>(); + final Map trasferSkillTree = new HashMap<>(); + final String type = d.getAttributes().getNamedItem("type").getNodeValue(); + Node attr = d.getAttributes().getNamedItem("classId"); + if (attr != null) + { + cId = Integer.parseInt(attr.getNodeValue()); + playerClass = PlayerClass.getPlayerClass(cId); + } + else + { + cId = -1; + } + + attr = d.getAttributes().getNamedItem("parentClassId"); + if (attr != null) + { + final int parentClassId = Integer.parseInt(attr.getNodeValue()); + if ((cId > -1) && (cId != parentClassId) && (parentClassId > -1) && !_parentClassMap.containsKey(playerClass)) + { + _parentClassMap.put(playerClass, PlayerClass.getPlayerClass(parentClassId)); + } + } + + for (Node c = d.getFirstChild(); c != null; c = c.getNextSibling()) + { + if ("skill".equalsIgnoreCase(c.getNodeName())) + { + final StatSet learnSkillSet = new StatSet(); + NamedNodeMap attrs = c.getAttributes(); + for (int i = 0; i < attrs.getLength(); i++) + { + attr = attrs.item(i); + learnSkillSet.set(attr.getNodeName(), attr.getNodeValue()); + } + + final SkillLearn skillLearn = new SkillLearn(learnSkillSet); + for (Node b = c.getFirstChild(); b != null; b = b.getNextSibling()) + { + attrs = b.getAttributes(); + switch (b.getNodeName()) + { + case "item": + { + skillLearn.addRequiredItem(new ItemHolder(parseInteger(attrs, "id"), parseInteger(attrs, "count"))); + break; + } + case "preRequisiteSkill": + { + skillLearn.addPreReqSkill(new SkillHolder(parseInteger(attrs, "id"), parseInteger(attrs, "lvl"))); + break; + } + case "race": + { + skillLearn.addRace(Race.valueOf(b.getTextContent())); + break; + } + case "residenceId": + { + skillLearn.addResidenceId(Integer.parseInt(b.getTextContent())); + break; + } + case "socialClass": + { + skillLearn.setSocialClass(Enum.valueOf(SocialClass.class, b.getTextContent())); + break; + } + case "subClassConditions": + { + skillLearn.addSubclassConditions(parseInteger(attrs, "slot"), parseInteger(attrs, "lvl")); + break; + } + } + } + + final int skillHashCode = SkillData.getSkillHashCode(skillLearn.getSkillId(), skillLearn.getSkillLevel()); + switch (type) + { + case "classSkillTree": + { + if (cId != -1) + { + classSkillTree.put(skillHashCode, skillLearn); + } + else + { + _commonSkillTree.put(skillHashCode, skillLearn); + } + break; + } + case "transferSkillTree": + { + trasferSkillTree.put(skillHashCode, skillLearn); + break; + } + case "fishingSkillTree": + { + _fishingSkillTree.put(skillHashCode, skillLearn); + break; + } + case "pledgeSkillTree": + { + _pledgeSkillTree.put(skillHashCode, skillLearn); + break; + } + case "nobleSkillTree": + { + _nobleSkillTree.put(skillHashCode, skillLearn); + break; + } + case "heroSkillTree": + { + _heroSkillTree.put(skillHashCode, skillLearn); + break; + } + case "gameMasterSkillTree": + { + _gameMasterSkillTree.put(skillHashCode, skillLearn); + break; + } + case "gameMasterAuraSkillTree": + { + _gameMasterAuraSkillTree.put(skillHashCode, skillLearn); + break; + } + default: + { + LOGGER.warning(getClass().getSimpleName() + ": Unknown Skill Tree type: " + type + "!"); + } + } + } + } + + if (type.equals("classSkillTree") && (cId > -1)) + { + if (!_classSkillTrees.containsKey(playerClass)) + { + _classSkillTrees.put(playerClass, classSkillTree); + } + else + { + _classSkillTrees.get(playerClass).putAll(classSkillTree); + } + } + } + } + } + } + } + + /** + * Method to get the complete skill tree for a given class id.
    + * Include all skills common to all classes.
    + * Includes all parent skill trees. + * @param playerClass the class skill tree Id + * @return the complete Class Skill Tree including skill trees from parent class for a given {@code playerClass} + */ + public Map getCompleteClassSkillTree(PlayerClass playerClass) + { + return _completeClassSkillTree.getOrDefault(playerClass, Collections.emptyMap()); + } + + /** + * Gets the common skill tree. + * @return the complete Common Skill Tree + */ + public Map getCommonSkillTree() + { + return _commonSkillTree; + } + + /** + * Gets the fishing skill tree. + * @return the complete Fishing Skill Tree + */ + public Map getFishingSkillTree() + { + return _fishingSkillTree; + } + + /** + * Gets the pledge skill tree. + * @return the complete Clan Skill Tree + */ + public Map getPledgeSkillTree() + { + return _pledgeSkillTree; + } + + /** + * Gets the noble skill tree. + * @return the complete Noble Skill Tree + */ + public Map getNobleSkillTree() + { + final Map tree = new HashMap<>(); + final SkillData st = SkillData.getInstance(); + for (Entry e : _nobleSkillTree.entrySet()) + { + tree.put(e.getKey(), st.getSkill(e.getValue().getSkillId(), e.getValue().getSkillLevel())); + } + + return tree; + } + + /** + * Gets the hero skill tree. + * @return the complete Hero Skill Tree + */ + public Map getHeroSkillTree() + { + final Map tree = new HashMap<>(); + final SkillData st = SkillData.getInstance(); + for (Entry e : _heroSkillTree.entrySet()) + { + tree.put(e.getKey(), st.getSkill(e.getValue().getSkillId(), e.getValue().getSkillLevel())); + } + + return tree; + } + + /** + * Gets the Game Master skill tree. + * @return the complete Game Master Skill Tree + */ + public Map getGMSkillTree() + { + final Map tree = new HashMap<>(); + final SkillData st = SkillData.getInstance(); + for (Entry e : _gameMasterSkillTree.entrySet()) + { + tree.put(e.getKey(), st.getSkill(e.getValue().getSkillId(), e.getValue().getSkillLevel())); + } + + return tree; + } + + /** + * Gets the Game Master Aura skill tree. + * @return the complete Game Master Aura Skill Tree + */ + public Map getGMAuraSkillTree() + { + final Map tree = new HashMap<>(); + final SkillData st = SkillData.getInstance(); + for (Entry e : _gameMasterAuraSkillTree.entrySet()) + { + tree.put(e.getKey(), st.getSkill(e.getValue().getSkillId(), e.getValue().getSkillLevel())); + } + + return tree; + } + + /** + * Gets the available skills. + * @param player the learning skill player + * @param playerClass the learning skill class Id + * @param includeByFs if {@code true} skills from Forgotten Scroll will be included + * @param includeAutoGet if {@code true} Auto-Get skills will be included + * @return all available skills for a given {@code player}, {@code playerClass}, {@code includeByFs} and {@code includeAutoGet} + */ + public Collection getAvailableSkills(Player player, PlayerClass playerClass, boolean includeByFs, boolean includeAutoGet) + { + return getAvailableSkills(player, playerClass, includeByFs, includeAutoGet, true, player.getSkills()); + } + + /** + * Gets the available skills. + * @param player the learning skill player + * @param playerClass the learning skill class Id + * @param includeByFs if {@code true} skills from Forgotten Scroll will be included + * @param includeAutoGet if {@code true} Auto-Get skills will be included + * @param includeRequiredItems if {@code true} skills that have required items will be added + * @param existingSkills the complete Map of currently known skills. + * @return all available skills for a given {@code player}, {@code playerClass}, {@code includeByFs} and {@code includeAutoGet} + */ + private Collection getAvailableSkills(Player player, PlayerClass playerClass, boolean includeByFs, boolean includeAutoGet, boolean includeRequiredItems, Map existingSkills) + { + final List result = new ArrayList<>(); + final Map skills = getCompleteClassSkillTree(playerClass); + if (skills.isEmpty()) + { + // The Skill Tree for this class is undefined. + LOGGER.warning(getClass().getSimpleName() + ": Skilltree for class " + playerClass + " is not defined!"); + return result; + } + + for (SkillLearn skill : skills.values()) + { + if (((skill.getSkillId() == CommonSkill.DIVINE_INSPIRATION.getId()) && (!PlayerConfig.AUTO_LEARN_DIVINE_INSPIRATION && includeAutoGet) && !player.isGM())) + { + continue; + } + + if (((!includeAutoGet || !skill.isAutoGet()) && !skill.isLearnedByNpc() && (!includeByFs || !skill.isLearnedByFS())) || (player.getLevel() < skill.getGetLevel())) + { + continue; + } + + // Forgotten Scroll requirements checked above. + if (!includeRequiredItems && !skill.getRequiredItems().isEmpty() && !skill.isLearnedByFS()) + { + continue; + } + + final Skill oldSkill = existingSkills.get(skill.getSkillId()); + if (oldSkill != null) + { + if (oldSkill.getLevel() == (skill.getSkillLevel() - 1)) + { + result.add(skill); + } + } + else if (skill.getSkillLevel() == 1) + { + result.add(skill); + } + } + + return result; + } + + /** + * Used by auto learn configuration. + * @param player + * @param playerClass + * @param includeByFs if {@code true} forgotten scroll skills present in the skill tree will be added + * @param includeAutoGet if {@code true} auto-get skills present in the skill tree will be added + * @param includeRequiredItems if {@code true} skills that have required items will be added + * @return a list of auto learnable skills for the player. + */ + public Collection getAllAvailableSkills(Player player, PlayerClass playerClass, boolean includeByFs, boolean includeAutoGet, boolean includeRequiredItems) + { + final Map result = new HashMap<>(); + for (Skill skill : player.getSkills().values()) + { + // Adding only skills that can be learned by the player. + if (isSkillAllowed(player, skill)) + { + result.put(skill.getId(), skill); + } + } + + // Keep learning skills until no more are available. + Collection learnableSkills; + Collection previousLearnableSkills = null; + while (true) + { + learnableSkills = getAvailableSkills(player, playerClass, includeByFs, includeAutoGet, includeRequiredItems, result); + if (learnableSkills.isEmpty()) + { + break; + } + + // Break if no new skills became learnable since last iteration. + if (learnableSkills.equals(previousLearnableSkills)) + { + break; + } + + for (SkillLearn skillLearn : learnableSkills) + { + final Skill skill = SkillData.getInstance().getSkill(skillLearn.getSkillId(), skillLearn.getSkillLevel()); + result.put(skill.getId(), skill); + } + + // Store reference for convergence check. + previousLearnableSkills = learnableSkills; + } + + return result.values(); + } + + /** + * Gets the available auto get skills. + * @param player the player requesting the Auto-Get skills + * @return all the available Auto-Get skills for a given {@code player} + */ + public List getAvailableAutoGetSkills(Player player) + { + final List result = new ArrayList<>(); + final Map skills = getCompleteClassSkillTree(player.getPlayerClass()); + if (skills.isEmpty()) + { + // The Skill Tree for this class is undefined, so we return an empty list. + LOGGER.warning(getClass().getSimpleName() + ": Skill Tree for this class Id(" + player.getPlayerClass() + ") is not defined!"); + return result; + } + + final Race race = player.getRace(); + for (SkillLearn skill : skills.values()) + { + if (!skill.getRaces().isEmpty() && !skill.getRaces().contains(race)) + { + continue; + } + + if (skill.isAutoGet() && (player.getLevel() >= skill.getGetLevel())) + { + final Skill oldSkill = player.getSkills().get(skill.getSkillId()); + if (oldSkill != null) + { + if (oldSkill.getLevel() < skill.getSkillLevel()) + { + result.add(skill); + } + } + else + { + result.add(skill); + } + } + } + + return result; + } + + /** + * Dwarvens will get additional dwarven only fishing skills. + * @param player the player + * @return all the available Fishing skills for a given {@code player} + */ + public List getAvailableFishingSkills(Player player) + { + final List result = new ArrayList<>(); + final Race playerRace = player.getRace(); + for (SkillLearn skill : _fishingSkillTree.values()) + { + // If skill is Race specific and the player's race isn't allowed, skip it. + if (!skill.getRaces().isEmpty() && !skill.getRaces().contains(playerRace)) + { + continue; + } + + if (skill.isLearnedByNpc() && (player.getLevel() >= skill.getGetLevel())) + { + final Skill oldSkill = player.getSkills().get(skill.getSkillId()); + if (oldSkill != null) + { + if (oldSkill.getLevel() == (skill.getSkillLevel() - 1)) + { + result.add(skill); + } + } + else if (skill.getSkillLevel() == 1) + { + result.add(skill); + } + } + } + + return result; + } + + /** + * Gets the available pledge skills. + * @param clan the pledge skill learning clan + * @return all the available Clan skills for a given {@code clan} + */ + public List getAvailablePledgeSkills(Clan clan) + { + final List result = new ArrayList<>(); + for (SkillLearn skill : _pledgeSkillTree.values()) + { + if (!skill.isResidencialSkill() && (clan.getLevel() >= skill.getGetLevel())) + { + final Skill oldSkill = clan.getSkills().get(skill.getSkillId()); + if (oldSkill != null) + { + if ((oldSkill.getLevel() + 1) == skill.getSkillLevel()) + { + result.add(skill); + } + } + else if (skill.getSkillLevel() == 1) + { + result.add(skill); + } + } + } + + return result; + } + + /** + * Gets the available pledge skills. + * @param clan the pledge skill learning clan + * @return all the available pledge skills for a given {@code clan} + */ + public Map getMaxPledgeSkills(Clan clan) + { + final Map result = new HashMap<>(); + for (SkillLearn skill : _pledgeSkillTree.values()) + { + if (!skill.isResidencialSkill() && (clan.getLevel() >= skill.getGetLevel())) + { + final Skill oldSkill = clan.getSkills().get(skill.getSkillId()); + if ((oldSkill == null) || (oldSkill.getLevel() < skill.getSkillLevel())) + { + result.put(skill.getSkillId(), skill); + } + } + } + + return result; + } + + /** + * Gets the available residential skills. + * @param residenceId the id of the Castle, Fort, Territory + * @return all the available Residential skills for a given {@code residenceId} + */ + public List getAvailableResidentialSkills(int residenceId) + { + final List result = new ArrayList<>(); + for (SkillLearn skill : _pledgeSkillTree.values()) + { + if (skill.isResidencialSkill() && skill.getResidenceIds().contains(residenceId)) + { + result.add(skill); + } + } + + return result; + } + + /** + * Just a wrapper for all skill trees. + * @param skillType the skill type + * @param id the skill Id + * @param lvl the skill level + * @param player the player learning the skill + * @return the skill learn for the specified parameters + */ + public SkillLearn getSkillLearn(AcquireSkillType skillType, int id, int lvl, Player player) + { + SkillLearn sl = null; + switch (skillType) + { + case CLASS: + { + sl = getClassSkill(id, lvl, player.getLearningClass()); + break; + } + case FISHING: + { + sl = getFishingSkill(id, lvl); + break; + } + case PLEDGE: + { + sl = getPledgeSkill(id, lvl); + break; + } + } + + return sl; + } + + /** + * Gets the class skill. + * @param id the class skill Id + * @param lvl the class skill level. + * @param playerClass the class skill tree Id + * @return the class skill from the Class Skill Trees for a given {@code playerClass}, {@code id} and {@code lvl} + */ + public SkillLearn getClassSkill(int id, int lvl, PlayerClass playerClass) + { + return getCompleteClassSkillTree(playerClass).get(SkillData.getSkillHashCode(id, lvl)); + } + + /** + * Gets the fishing skill. + * @param id the fishing skill Id + * @param lvl the fishing skill level + * @return Fishing skill from the Fishing Skill Tree for a given {@code id} and {@code lvl} + */ + public SkillLearn getFishingSkill(int id, int lvl) + { + return _fishingSkillTree.get(SkillData.getSkillHashCode(id, lvl)); + } + + /** + * Gets the pledge skill. + * @param id the pledge skill Id + * @param lvl the pledge skill level + * @return the pledge skill from the Clan Skill Tree for a given {@code id} and {@code lvl} + */ + public SkillLearn getPledgeSkill(int id, int lvl) + { + return _pledgeSkillTree.get(SkillData.getSkillHashCode(id, lvl)); + } + + /** + * Gets the common skill. + * @param id the common skill Id. + * @param lvl the common skill level + * @return the common skill from the Common Skill Tree for a given {@code id} and {@code lvl} + */ + public SkillLearn getCommonSkill(int id, int lvl) + { + return _commonSkillTree.get(SkillData.getSkillHashCode(id, lvl)); + } + + /** + * Gets the minimum level for new skill. + * @param player the player that requires the minimum level + * @param skillTree the skill tree to search the minimum get level + * @return the minimum level for a new skill for a given {@code player} and {@code skillTree} + */ + public int getMinLevelForNewSkill(Player player, Map skillTree) + { + int minLevel = 0; + if (skillTree.isEmpty()) + { + LOGGER.warning(getClass().getSimpleName() + ": SkillTree is not defined for getMinLevelForNewSkill!"); + } + else + { + for (SkillLearn s : skillTree.values()) + { + if (s.isLearnedByNpc() && (player.getLevel() < s.getGetLevel()) && ((minLevel == 0) || (minLevel > s.getGetLevel()))) + { + minLevel = s.getGetLevel(); + } + } + } + + return minLevel; + } + + /** + * Checks if is hero skill. + * @param skillId the Id of the skill to check + * @param skillLevel the level of the skill to check, if it's -1 only Id will be checked + * @return {@code true} if the skill is present in the Hero Skill Tree, {@code false} otherwise + */ + public boolean isHeroSkill(int skillId, int skillLevel) + { + if (_heroSkillTree.containsKey(SkillData.getSkillHashCode(skillId, skillLevel))) + { + return true; + } + + for (SkillLearn skill : _heroSkillTree.values()) + { + if ((skill.getSkillId() == skillId) && (skillLevel == -1)) + { + return true; + } + } + + return false; + } + + /** + * Checks if is GM skill. + * @param skillId the Id of the skill to check + * @param skillLevel the level of the skill to check, if it's -1 only Id will be checked + * @return {@code true} if the skill is present in the Game Master Skill Trees, {@code false} otherwise + */ + public boolean isGMSkill(int skillId, int skillLevel) + { + if (skillLevel <= 0) + { + return _gameMasterSkillTree.values().stream().anyMatch(s -> s.getSkillId() == skillId) // + || _gameMasterAuraSkillTree.values().stream().anyMatch(s -> s.getSkillId() == skillId); + } + + final int hashCode = SkillData.getSkillHashCode(skillId, skillLevel); + return _gameMasterSkillTree.containsKey(hashCode) || _gameMasterAuraSkillTree.containsKey(hashCode); + } + + /** + * Checks if a skill is a Clan skill. + * @param skillId the Id of the skill to check + * @param skillLevel the level of the skill to check + * @return {@code true} if the skill is present in the Clan or Subpledge Skill Trees, {@code false} otherwise + */ + public boolean isClanSkill(int skillId, int skillLevel) + { + final int hashCode = SkillData.getSkillHashCode(skillId, skillLevel); + return _pledgeSkillTree.containsKey(hashCode); + } + + /** + * Adds the skills. + * @param gmchar the player to add the Game Master skills + * @param auraSkills if {@code true} it will add "GM Aura" skills, else will add the "GM regular" skills + */ + public void addSkills(Player gmchar, boolean auraSkills) + { + final Collection skills = auraSkills ? _gameMasterAuraSkillTree.values() : _gameMasterSkillTree.values(); + final SkillData st = SkillData.getInstance(); + for (SkillLearn sl : skills) + { + gmchar.addSkill(st.getSkill(sl.getSkillId(), sl.getSkillLevel()), false); // Don't Save GM skills to database. + } + } + + /** + * Create and store hash values for skills for easy and fast checks. + */ + private void generateCheckArrays() + { + int index; + int[] skillHashes; + + // Class-specific skills. + Map skillLearnMap; + final Set playerClassSet = _classSkillTrees.keySet(); + _skillsByClassIdHashCodes = new HashMap<>(playerClassSet.size()); + for (PlayerClass playerClass : playerClassSet) + { + index = 0; + skillLearnMap = new HashMap<>(getCompleteClassSkillTree(playerClass)); + skillHashes = new int[skillLearnMap.size()]; + for (int skillHash : skillLearnMap.keySet()) + { + skillHashes[index++] = skillHash; + } + + skillLearnMap.clear(); + Arrays.sort(skillHashes); + _skillsByClassIdHashCodes.put(playerClass.getId(), skillHashes); + } + + // Race-specific skills from Fishing and Transformation skill trees. + final List skillHashList = new ArrayList<>(); + _skillsByRaceHashCodes = new HashMap<>(Race.values().length); + for (Race race : Race.values()) + { + for (SkillLearn skillLearn : _fishingSkillTree.values()) + { + if (skillLearn.getRaces().contains(race)) + { + skillHashList.add(SkillData.getSkillHashCode(skillLearn.getSkillId(), skillLearn.getSkillLevel())); + } + } + + index = 0; + skillHashes = new int[skillHashList.size()]; + for (int skillHash : skillHashList) + { + skillHashes[index++] = skillHash; + } + + Arrays.sort(skillHashes); + _skillsByRaceHashCodes.put(race.ordinal(), skillHashes); + skillHashList.clear(); + } + + // Skills available for all classes and races. + for (SkillLearn skillLearn : _commonSkillTree.values()) + { + if (skillLearn.getRaces().isEmpty()) + { + skillHashList.add(SkillData.getSkillHashCode(skillLearn.getSkillId(), skillLearn.getSkillLevel())); + } + } + + for (SkillLearn skillLearn : _fishingSkillTree.values()) + { + if (skillLearn.getRaces().isEmpty()) + { + skillHashList.add(SkillData.getSkillHashCode(skillLearn.getSkillId(), skillLearn.getSkillLevel())); + } + } + + _allSkillsHashCodes = new int[skillHashList.size()]; + int hashIndex = 0; + for (int skillHash : skillHashList) + { + _allSkillsHashCodes[hashIndex++] = skillHash; + } + + Arrays.sort(_allSkillsHashCodes); + } + + /** + * Verify if the give skill is valid for the given player.
    + * GM's skills are excluded for GM players + * @param player the player to verify the skill + * @param skill the skill to be verified + * @return {@code true} if the skill is allowed to the given player + */ + public boolean isSkillAllowed(Player player, Skill skill) + { + if (skill.isExcludedFromCheck()) + { + return true; + } + + if (player.isGM() && skill.isGMSkill()) + { + return true; + } + + // Prevent accidental skill remove during reload. + if (_loading) + { + return true; + } + + final int maxLevel = SkillData.getInstance().getMaxLevel(skill.getId()); + final int hashCode = SkillData.getSkillHashCode(skill.getId(), Math.min(skill.getLevel(), maxLevel)); + if (Arrays.binarySearch(_skillsByClassIdHashCodes.get(player.getPlayerClass().getId()), hashCode) >= 0) + { + return true; + } + + if (Arrays.binarySearch(_skillsByRaceHashCodes.get(player.getRace().ordinal()), hashCode) >= 0) + { + return true; + } + + if (Arrays.binarySearch(_allSkillsHashCodes, hashCode) >= 0) + { + return true; + } + + return false; + } + + /** + * Logs current Skill Trees skills count. + */ + private void report() + { + int classSkillTreeCount = 0; + for (Map classSkillTree : _classSkillTrees.values()) + { + classSkillTreeCount += classSkillTree.size(); + } + + int dwarvenOnlyFishingSkillCount = 0; + for (SkillLearn fishSkill : _fishingSkillTree.values()) + { + if (fishSkill.getRaces().contains(Race.DWARF)) + { + dwarvenOnlyFishingSkillCount++; + } + } + + int resSkillCount = 0; + for (SkillLearn pledgeSkill : _pledgeSkillTree.values()) + { + if (pledgeSkill.isResidencialSkill()) + { + resSkillCount++; + } + } + + final String className = getClass().getSimpleName(); + LOGGER.info(className + ": Loaded " + classSkillTreeCount + " Class Skills for " + _classSkillTrees.size() + " class skill trees."); + LOGGER.info(className + ": Loaded " + _fishingSkillTree.size() + " fishing skills, " + dwarvenOnlyFishingSkillCount + " Dwarven only fishing skills."); + LOGGER.info(className + ": Loaded " + _pledgeSkillTree.size() + " clan skills, " + (_pledgeSkillTree.size() - resSkillCount) + " for clan and " + resSkillCount + " residential."); + LOGGER.info(className + ": Loaded " + _nobleSkillTree.size() + " noble skills."); + LOGGER.info(className + ": Loaded " + _heroSkillTree.size() + " hero skills."); + LOGGER.info(className + ": Loaded " + _gameMasterSkillTree.size() + " game master skills."); + LOGGER.info(className + ": Loaded " + _gameMasterAuraSkillTree.size() + " game master aura skills."); + final int commonSkills = _commonSkillTree.size(); + if (commonSkills > 0) + { + LOGGER.info(className + ": Loaded " + commonSkills + " common skills."); + } + } + + /** + * Gets the single instance of SkillTreesData. + * @return the only instance of this class + */ + public static SkillTreeData getInstance() + { + return SingletonHolder.INSTANCE; + } + + /** + * Singleton holder for the SkillTreesData class. + */ + private static class SingletonHolder + { + protected static final SkillTreeData INSTANCE = new SkillTreeData(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/SpawnData.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/SpawnData.java new file mode 100644 index 0000000..05902b8 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/SpawnData.java @@ -0,0 +1,1040 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.data.xml; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.w3c.dom.Document; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; + +import org.l2jmobius.commons.util.IXmlReader; +import org.l2jmobius.gameserver.config.DevelopmentConfig; +import org.l2jmobius.gameserver.config.ServerConfig; +import org.l2jmobius.gameserver.config.custom.FakePlayersConfig; +import org.l2jmobius.gameserver.data.SpawnGroupTable; +import org.l2jmobius.gameserver.data.SpawnTable; +import org.l2jmobius.gameserver.entity.Location; +import org.l2jmobius.gameserver.entity.World; +import org.l2jmobius.gameserver.entity.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.entity.spawns.Spawn; +import org.l2jmobius.gameserver.entity.spawns.SpawnGroup; +import org.l2jmobius.gameserver.entity.spawns.SpawnGroupEntry; +import org.l2jmobius.gameserver.entity.spawns.SpawnSelection; +import org.l2jmobius.gameserver.entity.zone.ZoneForm; +import org.l2jmobius.gameserver.entity.zone.form.ZoneCuboid; +import org.l2jmobius.gameserver.entity.zone.form.ZoneCylinder; +import org.l2jmobius.gameserver.entity.zone.form.ZoneNPoly; +import org.l2jmobius.gameserver.entity.zone.type.NpcSpawnTerritory; +import org.l2jmobius.gameserver.managers.DayNightSpawnManager; +import org.l2jmobius.gameserver.managers.ZoneManager; +import org.l2jmobius.gameserver.taskmanagers.SpawnGroupTaskManager; +import org.l2jmobius.gameserver.util.StatSet; + +/** + * @author Mobius + */ +public class SpawnData implements IXmlReader +{ + private static final Logger LOGGER = Logger.getLogger(SpawnData.class.getName()); + + private static final String OTHER_XML_FOLDER = "data/spawns/Others"; + + private final Map _spawnTemplates = new ConcurrentHashMap<>(); + private final Set _parsedFiles = ConcurrentHashMap.newKeySet(); + private int _spawnCount = 0; + + protected SpawnData() + { + load(); + } + + @Override + public void load() + { + _spawnTemplates.put(0, "None"); + if (!DevelopmentConfig.NO_SPAWNS) + { + // Reloading must not accumulate: groups respawn from scratch and pending replacements die with them. + // Group slots are swept by iterator, their mutated coordinates make hash based removal unreliable. + _spawnCount = 0; + _parsedFiles.clear(); + for (Set spawns : SpawnTable.getInstance().getSpawnTable().values()) + { + final Iterator iterator = spawns.iterator(); + while (iterator.hasNext()) + { + if (iterator.next().getGroup() != null) + { + iterator.remove(); + } + } + } + + SpawnGroupTable.getInstance().clear(); + SpawnGroupTaskManager.getInstance().clear(); + + LOGGER.info(getClass().getSimpleName() + ": Initializing spawns..."); + parseDatapackDirectory("data/spawns", true); + checkMissingFiles(new File(".", "data/spawns")); + for (SpawnGroup group : SpawnGroupTable.getInstance().getGroups()) + { + _spawnCount += group.initialSpawn(); + } + + if (!SpawnGroupTable.getInstance().getGroups().isEmpty()) + { + LOGGER.info(getClass().getSimpleName() + ": " + SpawnGroupTable.getInstance().getGroups().size() + " spawn groups initialized."); + } + + LOGGER.info(getClass().getSimpleName() + ": " + _parsedFiles.size() + " spawn files parsed."); + LOGGER.info(getClass().getSimpleName() + ": " + _spawnCount + " spawns have been initialized!"); + } + } + + /** + * Reports datapack files that were silently omitted by XML validation errors. + * @param directory the directory to verify recursively + */ + private void checkMissingFiles(File directory) + { + final File[] files = directory.listFiles(); + if (files == null) + { + return; + } + + for (File file : files) + { + if (file.isDirectory()) + { + checkMissingFiles(file); + } + else if (file.getName().toLowerCase().endsWith(".xml") && !_parsedFiles.contains(file.getAbsolutePath())) + { + LOGGER.severe(getClass().getSimpleName() + ": " + file.getPath() + " was not parsed!"); + } + } + } + + /** + * Verifies if the template exists and it's spawnable. + * @param npcId the NPC ID + * @return {@code true} if the NPC ID belongs to an spawnable tempalte, {@code false} otherwise + */ + private boolean checkTemplate(int npcId) + { + final NpcTemplate npcTemplate = NpcData.getInstance().getTemplate(npcId); + if (npcTemplate == null) + { + // Log a warning for invalid NPC IDs outside the fake player range [80000, 89999]. + // This hardcoded check provides a resource-constrained solution to prevent unnecessary logging. + if (FakePlayersConfig.FAKE_PLAYERS_ENABLED || ((npcId < 80000) && (npcId > 89999))) + { + LOGGER.warning(getClass().getSimpleName() + ": Requested spawn for non-existing NPC: " + npcId + "."); + } + + return false; + } + + if (npcTemplate.isType("SiegeGuard") || npcTemplate.isType("RaidBoss")) + { + // Do not spawn. + return false; + } + + if (!FakePlayersConfig.FAKE_PLAYERS_ENABLED && npcTemplate.isFakePlayer()) + { + return false; + } + + return true; + } + + @Override + public void parseDocument(Document document, File file) + { + _parsedFiles.add(file.getAbsolutePath()); + + NamedNodeMap attrs; + for (Node list = document.getFirstChild(); list != null; list = list.getNextSibling()) + { + if (list.getNodeName().equalsIgnoreCase("list")) + { + attrs = list.getAttributes(); + + // Skip disabled spawnlists. + if (!Boolean.parseBoolean(attrs.getNamedItem("enabled").getNodeValue())) + { + continue; + } + + for (Node param = list.getFirstChild(); param != null; param = param.getNextSibling()) + { + attrs = param.getAttributes(); + if (param.getNodeName().equalsIgnoreCase("spawn")) + { + String territoryName = null; + String spawnName = null; + Map map = null; + + // Check, if spawn name specified. + if (attrs.getNamedItem("name") != null) + { + spawnName = parseString(attrs, "name"); + } + + // Check, if spawn territory specified. + if (attrs.getNamedItem("zone") != null) + { + territoryName = parseString(attrs, "zone"); + } + + // Check, if a territory list is specified. Only spawn groups use it, one slot per territory. + String[] territoryNames = null; + if (attrs.getNamedItem("zones") != null) + { + if (territoryName != null) + { + LOGGER.warning(getClass().getSimpleName() + ": zone and zones are exclusive, ignoring zones in " + file.getName()); + } + else + { + territoryNames = parseString(attrs, "zones").split(";"); + territoryName = territoryNames[0]; + } + } + + // Check, if entry selection specified. Blocks without it keep the default spawning path. + SpawnGroup group = null; + if (attrs.getNamedItem("selection") != null) + { + final String selectionValue = parseString(attrs, "selection"); + final SpawnSelection selection; + if ("random".equalsIgnoreCase(selectionValue)) + { + selection = SpawnSelection.RANDOM; + } + else if ("randomFill".equalsIgnoreCase(selectionValue)) + { + selection = SpawnSelection.RANDOM_FILL; + } + else + { + if (!"all".equalsIgnoreCase(selectionValue)) + { + LOGGER.warning(getClass().getSimpleName() + ": Unknown selection \"" + selectionValue + "\" in " + file.getName() + ", using all."); + } + + selection = SpawnSelection.ALL; + } + + int maximumNpc = attrs.getNamedItem("maximumNpc") != null ? parseInteger(attrs, "maximumNpc") : 0; + if ((selection == SpawnSelection.RANDOM) && (maximumNpc > 0)) + { + // Retail random_spawn never reads the cap, accepting it silently would suggest otherwise. + LOGGER.warning(getClass().getSimpleName() + ": maximumNpc is not read by random selection, ignoring it in " + file.getName()); + maximumNpc = 0; + } + + if ((selection == SpawnSelection.RANDOM_FILL) && (maximumNpc <= 0)) + { + LOGGER.warning(getClass().getSimpleName() + ": randomFill requires maximumNpc, the group in " + file.getName() + " will spawn nothing."); + } + + final String groupName = spawnName != null ? spawnName : (territoryName != null ? territoryName : file.getName()); + group = new SpawnGroup(groupName, selection, maximumNpc); + SpawnGroupTable.getInstance().addGroup(group); + } + else if (territoryNames != null) + { + LOGGER.warning(getClass().getSimpleName() + ": zones requires an entry selection, using only the first territory in " + file.getName()); + territoryNames = null; + } + + for (Node npctag = param.getFirstChild(); npctag != null; npctag = npctag.getNextSibling()) + { + attrs = npctag.getAttributes(); + + // Check if there are any AI parameters. + if (npctag.getNodeName().equalsIgnoreCase("AIData")) + { + attrs = npctag.getAttributes(); + if (map == null) + { + map = new HashMap<>(); + } + + for (Node c = npctag.getFirstChild(); c != null; c = c.getNextSibling()) + { + // Skip odd nodes. + if (c.getNodeName().equals("#text")) + { + continue; + } + + int val; + switch (c.getNodeName()) + { + case "disableRandomAnimation": + case "disableRandomWalk": + { + val = Boolean.parseBoolean(c.getTextContent()) ? 1 : 0; + break; + } + default: + { + val = Integer.parseInt(c.getTextContent()); + } + } + + map.put(c.getNodeName(), val); + } + } + // Check for NPC spawn territories. + else if (npctag.getNodeName().equalsIgnoreCase("territory")) + { + // Territories can declare their own name, defaulting to the block zone. + final String declaredName = attrs.getNamedItem("name") != null ? parseString(attrs, "name") : territoryName; + if (ZoneManager.getInstance().spawnTerritoryExists(declaredName)) + { + continue; + } + + final int minZ = parseInteger(attrs, "minZ"); + final int maxZ = parseInteger(attrs, "maxZ"); + final String zoneShape = parseString(attrs, "shape", "NPoly"); + + final List rs = new ArrayList<>(); + int[][] coords; + ZoneForm zoneForm = null; + try + { + for (Node c = npctag.getFirstChild(); c != null; c = c.getNextSibling()) + { + if ("node".equalsIgnoreCase(c.getNodeName())) + { + attrs = c.getAttributes(); + final int[] point = new int[2]; + point[0] = parseInteger(attrs, "x"); + point[1] = parseInteger(attrs, "y"); + rs.add(point); + } + } + + coords = rs.toArray(new int[0][2]); + rs.clear(); + + if ((coords == null) || (coords.length == 0)) + { + LOGGER.warning(getClass().getSimpleName() + ": SpawnTable: missing data for spawn territory: " + declaredName + " XML file: " + file.getName()); + continue; + } + + // Create this zone. Parsing for cuboids is a bit different than for other polygons cuboids need exactly 2 points to be defined. + // Other polygons need at least 3 (one per vertex). + if (zoneShape.equalsIgnoreCase("Cuboid")) + { + if (coords.length == 2) + { + zoneForm = new ZoneCuboid(coords[0][0], coords[1][0], coords[0][1], coords[1][1], minZ, maxZ); + } + else + { + LOGGER.warning(getClass().getSimpleName() + ": SpawnTable: Missing cuboid vertex data for territory: " + declaredName + " in file: " + file.getName()); + continue; + } + } + else if (zoneShape.equalsIgnoreCase("NPoly")) + { + // nPoly needs to have at least 3 vertices. + if (coords.length > 2) + { + final int[] aX = new int[coords.length]; + final int[] aY = new int[coords.length]; + for (int i = 0; i < coords.length; i++) + { + aX[i] = coords[i][0]; + aY[i] = coords[i][1]; + } + + zoneForm = new ZoneNPoly(aX, aY, minZ, maxZ); + } + else + { + LOGGER.warning(getClass().getSimpleName() + ": SpawnTable: Bad data for territory: " + declaredName + " in file: " + file.getName()); + continue; + } + } + else if (zoneShape.equalsIgnoreCase("Cylinder")) + { + // A Cylinder zone requires a center point at x,y and a radius. + final int zoneRad = Integer.parseInt(attrs.getNamedItem("rad").getNodeValue()); + if ((coords.length == 1) && (zoneRad > 0)) + { + zoneForm = new ZoneCylinder(coords[0][0], coords[0][1], minZ, maxZ, zoneRad); + } + else + { + LOGGER.warning(getClass().getSimpleName() + ": SpawnTable: Bad data for territory: " + declaredName + " in file: " + file.getName()); + continue; + } + } + else + { + LOGGER.warning(getClass().getSimpleName() + ": SpawnTable: Unknown shape: \"" + zoneShape + "\" for territory: " + declaredName + " in file: " + file.getName()); + continue; + } + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": SpawnTable: Failed to load territory " + declaredName + " coordinates: " + e.getMessage(), e); + } + + ZoneManager.getInstance().addSpawnTerritory(declaredName, new NpcSpawnTerritory(declaredName, zoneForm)); + } + // Check for NPC banned spawn territories. + else if (npctag.getNodeName().equalsIgnoreCase("banned_territory")) + { + if (!ZoneManager.getInstance().spawnTerritoryExists(territoryName)) + { + LOGGER.warning(getClass().getSimpleName() + ": SpawnTable: banned_territory " + territoryName + " must be set after normal territory."); + continue; + } + + final int minZ = parseInteger(attrs, "minZ"); + final int maxZ = parseInteger(attrs, "maxZ"); + final String zoneShape = parseString(attrs, "shape", "NPoly"); + + final List rs = new ArrayList<>(); + int[][] coords; + ZoneForm zoneForm = null; + try + { + for (Node c = npctag.getFirstChild(); c != null; c = c.getNextSibling()) + { + if ("node".equalsIgnoreCase(c.getNodeName())) + { + attrs = c.getAttributes(); + final int[] point = new int[2]; + point[0] = parseInteger(attrs, "x"); + point[1] = parseInteger(attrs, "y"); + rs.add(point); + } + } + + coords = rs.toArray(new int[0][2]); + rs.clear(); + + if ((coords == null) || (coords.length == 0)) + { + LOGGER.warning(getClass().getSimpleName() + ": SpawnTable: missing data for spawn banned_territory: " + territoryName + " XML file: " + file.getName()); + continue; + } + + // Create this zone. Parsing for cuboids is a bit different than for other polygons cuboids need exactly 2 points to be defined. + // Other polygons need at least 3 (one per vertex). + if (zoneShape.equalsIgnoreCase("Cuboid")) + { + if (coords.length == 2) + { + zoneForm = new ZoneCuboid(coords[0][0], coords[1][0], coords[0][1], coords[1][1], minZ, maxZ); + } + else + { + LOGGER.warning(getClass().getSimpleName() + ": SpawnTable: Missing cuboid vertex data for banned_territory: " + territoryName + " in file: " + file.getName()); + continue; + } + } + else if (zoneShape.equalsIgnoreCase("NPoly")) + { + // nPoly needs to have at least 3 vertices. + if (coords.length > 2) + { + final int[] aX = new int[coords.length]; + final int[] aY = new int[coords.length]; + for (int i = 0; i < coords.length; i++) + { + aX[i] = coords[i][0]; + aY[i] = coords[i][1]; + } + + zoneForm = new ZoneNPoly(aX, aY, minZ, maxZ); + } + else + { + LOGGER.warning(getClass().getSimpleName() + ": SpawnTable: Bad data for banned_territory: " + territoryName + " in file: " + file.getName()); + continue; + } + } + else if (zoneShape.equalsIgnoreCase("Cylinder")) + { + // A Cylinder zone requires a center point at x,y and a radius. + final int zoneRad = Integer.parseInt(attrs.getNamedItem("rad").getNodeValue()); + if ((coords.length == 1) && (zoneRad > 0)) + { + zoneForm = new ZoneCylinder(coords[0][0], coords[0][1], minZ, maxZ, zoneRad); + } + else + { + LOGGER.warning(getClass().getSimpleName() + ": SpawnTable: Bad data for banned_territory: " + territoryName + " in file: " + file.getName()); + continue; + } + } + else + { + LOGGER.warning(getClass().getSimpleName() + ": SpawnTable: Unknown shape: \"" + zoneShape + "\" for banned_territory: " + territoryName + " in file: " + file.getName()); + continue; + } + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": SpawnTable: Failed to load banned_territory " + territoryName + " coordinates: " + e.getMessage(), e); + } + + ZoneManager.getInstance().getSpawnTerritory(territoryName).addBannedTerritory(zoneForm); + } + // Check for NPC spawns. + else if (npctag.getNodeName().equalsIgnoreCase("npc")) + { + // Mandatory. + final int templateId = parseInteger(attrs, "id"); + + // Avoid spawning unwanted spawns. + if (!checkTemplate(templateId)) + { + continue; + } + + // Coordinates are optional, if territory is specified; mandatory otherwise. + int x = 0; + int y = 0; + int z = 0; + + try + { + x = parseInteger(attrs, "x"); + y = parseInteger(attrs, "y"); + z = parseInteger(attrs, "z"); + } + catch (NullPointerException npe) + { + // x, y, z can be unspecified, if this spawn is territory based, do nothing. + } + + if ((x == 0) && (y == 0) && (territoryName == null)) // Both coordinates and zone are unspecified. + { + LOGGER.warning("XML Spawnlist: Spawn could not be initialized, both coordinates and zone are unspecified for ID " + templateId); + continue; + } + + final Node countNode = attrs.getNamedItem("count"); + final int count = countNode == null ? 1 : parseInteger(attrs, "count"); + + final StatSet spawnInfo = new StatSet(); + spawnInfo.set("npcTemplateid", templateId); + spawnInfo.set("x", x); + spawnInfo.set("y", y); + spawnInfo.set("z", z); + spawnInfo.set("territoryName", territoryName); + spawnInfo.set("spawnName", spawnName); + + // Trying to read optional parameters. + if (attrs.getNamedItem("heading") != null) + { + spawnInfo.set("heading", parseInteger(attrs, "heading")); + } + + if (attrs.getNamedItem("respawnDelay") != null) + { + spawnInfo.set("respawnDelay", parseInteger(attrs, "respawnDelay")); + } + + if (attrs.getNamedItem("respawnRandom") != null) + { + spawnInfo.set("respawnRandom", parseInteger(attrs, "respawnRandom")); + } + + if (attrs.getNamedItem("chaseRange") != null) + { + spawnInfo.set("chaseRange", parseInteger(attrs, "chaseRange")); + } + + if (attrs.getNamedItem("periodOfDay") != null) + { + final String period = attrs.getNamedItem("periodOfDay").getNodeValue(); + if (period.equalsIgnoreCase("day") || period.equalsIgnoreCase("night")) + { + spawnInfo.set("periodOfDay", period.equalsIgnoreCase("day") ? 1 : 2); + } + } + + spawnInfo.set("fileName", file.getPath()); + + // Group entries keep their count as the entry quota, one spawn slot per territory manages them. + if (group != null) + { + if (spawnInfo.getInt("periodOfDay", 0) != 0) + { + // No retail maker mixes rolling with the day night gate and DayNightSpawnManager bypasses decreaseCount. + LOGGER.warning(getClass().getSimpleName() + ": periodOfDay cannot be combined with an entry selection, skipping NPC " + templateId + " in " + file.getName()); + continue; + } + + addGroupSpawn(group, spawnInfo, map, count, territoryNames); + continue; + } + + for (int i = 0; i < count; i++) + { + _spawnCount += addSpawn(spawnInfo, map); + } + } + } + } + } + } + } + } + + /** + * Creates NPC spawn + * @param spawnInfo StatSet of spawn parameters + * @param aiData Map of specific AI parameters for this spawn + * @return count NPC instances, spawned by this spawn + */ + private int addSpawn(StatSet spawnInfo, Map aiData) + { + Spawn spawnDat; + int ret = 0; + try + { + spawnDat = createSpawn(spawnInfo, aiData); + switch (spawnInfo.getInt("periodOfDay", 0)) + { + case 0: // Default + { + ret += spawnDat.init(); + break; + } + case 1: // Day + { + DayNightSpawnManager.getInstance().addDayCreature(spawnDat); + ret = 1; + break; + } + case 2: // Night + { + DayNightSpawnManager.getInstance().addNightCreature(spawnDat); + ret = 1; + break; + } + } + + registerSpawn(spawnDat, spawnInfo.getString("fileName", "None")); + } + catch (Exception e) + { + // Problem with initializing spawn, go to next one. + LOGGER.log(Level.WARNING, "Spawn could not be initialized: " + e.getMessage(), e); + } + + return ret; + } + + /** + * Creates a spawn group entry backed by one spawn slot per territory and adds it to the group.
    + * Group slots never call {@link Spawn#init()}, the group performs the spawning itself. + * @param group the spawn group the entry belongs to + * @param spawnInfo StatSet of spawn parameters + * @param aiData Map of specific AI parameters for this spawn + * @param total the unit quota of the entry + * @param territoryNames the territories of the group, {@code null} when the block declares a single zone + */ + private void addGroupSpawn(SpawnGroup group, StatSet spawnInfo, Map aiData, int total, String[] territoryNames) + { + try + { + final SpawnGroupEntry entry = new SpawnGroupEntry(total, spawnInfo.getInt("respawnDelay", 0), spawnInfo.getInt("respawnRandom", 0)); + final int slotCount = territoryNames != null ? territoryNames.length : 1; + for (int i = 0; i < slotCount; i++) + { + if (territoryNames != null) + { + spawnInfo.set("territoryName", territoryNames[i]); + } + + final Spawn spawnDat = createSpawn(spawnInfo, aiData); + spawnDat.setAmount(total); + if ((group.getSelection() != SpawnSelection.ALL) || (spawnInfo.getInt("respawnDelay", 0) <= 0)) + { + // Rolling groups replace their own units; ALL slots keep the regular per slot respawn with its fixed species. + spawnDat.stopRespawn(); + } + + final NpcSpawnTerritory slotTerritory = spawnDat.getSpawnTerritory(); + if (slotTerritory != null) + { + // Location equality would collapse unspawned slots inside the SpawnTable set, seed each slot with its own territory point. + spawnDat.setXYZ(slotTerritory.getRandomPoint()); + } + + spawnDat.setGroup(group); + entry.addSlot(spawnDat); + } + + // Register only after every slot was created, a failed slot must not leave half an entry behind. + for (Spawn slot : entry.getSlots()) + { + registerSpawn(slot, spawnInfo.getString("fileName", "None")); + } + + group.addEntry(entry); + } + catch (Exception e) + { + // Problem with initializing spawn, go to next one. + LOGGER.log(Level.WARNING, "Group spawn could not be initialized: " + e.getMessage(), e); + } + } + + /** + * Builds and configures a {@link Spawn} from the given parameters. + * @param spawnInfo StatSet of spawn parameters + * @param aiData Map of specific AI parameters for this spawn + * @return the configured spawn + * @throws Exception when the spawn could not be constructed + */ + private Spawn createSpawn(StatSet spawnInfo, Map aiData) throws Exception + { + final Spawn spawnDat = new Spawn(spawnInfo.getInt("npcTemplateid")); + spawnDat.setAmount(spawnInfo.getInt("count", 1)); + spawnDat.setXYZ(spawnInfo.getInt("x", 0), spawnInfo.getInt("y", 0), spawnInfo.getInt("z", 0)); + spawnDat.setHeading(spawnInfo.getInt("heading", -1)); + spawnDat.setRespawnDelay(spawnInfo.getInt("respawnDelay", 0), spawnInfo.getInt("respawnRandom", 0)); + spawnDat.setChaseRange(spawnInfo.getInt("chaseRange", 0)); + spawnDat.setLocationId(spawnInfo.getInt("locId", 0)); + final String territoryName = spawnInfo.getString("territoryName", ""); + final String spawnName = spawnInfo.getString("spawnName", ""); + if (!spawnName.isEmpty()) + { + spawnDat.setName(spawnName); + } + + if (!territoryName.isEmpty()) + { + spawnDat.setSpawnTerritory(ZoneManager.getInstance().getSpawnTerritory(territoryName)); + } + + // Register AI Data for this spawn. + NpcPersonalAIData.getInstance().storeData(spawnDat, aiData); + return spawnDat; + } + + /** + * Assigns the spawn template id of the source file and registers the spawn on the {@link SpawnTable}. + * @param spawnDat the spawn to register + * @param fileName the datapack file the spawn was declared in + */ + private void registerSpawn(Spawn spawnDat, String fileName) + { + if (_spawnTemplates.values().contains(fileName)) + { + for (Entry entry : _spawnTemplates.entrySet()) + { + if (entry.getValue().equals(fileName)) + { + spawnDat.setSpawnTemplateId(entry.getKey()); + break; + } + } + } + else + { + final int newId = _spawnTemplates.size(); + _spawnTemplates.put(newId, fileName); + spawnDat.setSpawnTemplateId(newId); + } + + SpawnTable.getInstance().addSpawn(spawnDat); + } + + /** + * Adds a new spawn to the {@link SpawnTable} and writes its details to an XML file.
    + * If the XML file for the corresponding coordinates already exists, the spawn is appended to it.
    + * If the file does not exist, a new one is created. + * @param spawn the {@link Spawn} object to add. + */ + public synchronized void addNewSpawn(Spawn spawn) + { + SpawnTable.getInstance().addSpawn(spawn); + + // Create output directory if it doesn't exist. + final File outputDirectory = new File(OTHER_XML_FOLDER); + if (!outputDirectory.exists()) + { + boolean result = false; + try + { + outputDirectory.mkdir(); + result = true; + } + catch (SecurityException se) + { + // Ignore. + } + + if (result) + { + LOGGER.info(getClass().getSimpleName() + ": Created directory: " + OTHER_XML_FOLDER); + } + } + + // XML file for spawn. + final int x = ((spawn.getX() - World.WORLD_X_MIN) >> 15) + World.TILE_X_MIN; + final int y = ((spawn.getY() - World.WORLD_Y_MIN) >> 15) + World.TILE_Y_MIN; + final File spawnFile = new File(OTHER_XML_FOLDER + "/" + x + "_" + y + ".xml"); + + // Write info to XML. + final String spawnId = String.valueOf(spawn.getId()); + final String spawnCount = String.valueOf(spawn.getAmount()); + final String spawnX = String.valueOf(spawn.getX()); + final String spawnY = String.valueOf(spawn.getY()); + final String spawnZ = String.valueOf(spawn.getZ()); + final String spawnHeading = String.valueOf(spawn.getHeading()); + final String spawnDelay = String.valueOf(spawn.getRespawnDelay() / 1000); + if (spawnFile.exists()) // Update. + { + final File tempFile = new File(spawnFile.getAbsolutePath().substring(ServerConfig.DATAPACK_ROOT.getAbsolutePath().length() + 1).replace('\\', '/') + ".tmp"); + try + { + final BufferedReader reader = new BufferedReader(new FileReader(spawnFile)); + final BufferedWriter writer = new BufferedWriter(new FileWriter(tempFile)); + String currentLine; + while ((currentLine = reader.readLine()) != null) + { + if (currentLine.contains("
    ")) + { + final NpcTemplate template = NpcData.getInstance().getTemplate(spawn.getId()); + final String title = template.getTitle(); + final String name = title.isEmpty() ? template.getName() : template.getName() + " - " + title; + writer.write(" 1 ? "\" count=\"" + spawnCount : "") + "\" x=\"" + spawnX + "\" y=\"" + spawnY + "\" z=\"" + spawnZ + (spawn.getHeading() > 0 ? "\" heading=\"" + spawnHeading : "") + "\" respawnDelay=\"" + spawnDelay + "\" /> " + System.lineSeparator()); + writer.write(currentLine + System.lineSeparator()); + continue; + } + + writer.write(currentLine + System.lineSeparator()); + } + + writer.close(); + reader.close(); + spawnFile.delete(); + tempFile.renameTo(spawnFile); + } + catch (Exception e) + { + LOGGER.warning(getClass().getSimpleName() + ": Could not store spawn in the spawn XML files: " + e); + } + } + else // New file. + { + try + { + final NpcTemplate template = NpcData.getInstance().getTemplate(spawn.getId()); + final String title = template.getTitle(); + final String name = title.isEmpty() ? template.getName() : template.getName() + " - " + title; + final BufferedWriter writer = new BufferedWriter(new FileWriter(spawnFile)); + writer.write("" + System.lineSeparator()); + writer.write("" + System.lineSeparator()); + writer.write(" " + System.lineSeparator()); + writer.write(" 1 ? "\" count=\"" + spawnCount : "") + "\" x=\"" + spawnX + "\" y=\"" + spawnY + "\" z=\"" + spawnZ + (spawn.getHeading() > 0 ? "\" heading=\"" + spawnHeading : "") + "\" respawnDelay=\"" + spawnDelay + "\" /> " + System.lineSeparator()); + writer.write(" " + System.lineSeparator()); + writer.write("" + System.lineSeparator()); + writer.close(); + LOGGER.info(getClass().getSimpleName() + ": Created file: " + OTHER_XML_FOLDER + "/" + x + "_" + y + ".xml"); + } + catch (Exception e) + { + LOGGER.warning(getClass().getSimpleName() + ": Spawn " + spawn + " could not be added to the spawn XML files: " + e); + } + } + } + + /** + * Deletes a spawn from the {@link SpawnTable} and its associated XML entry if it exists.
    + * If the file becomes empty after deletion, it is removed. + * @param spawn the {@link Spawn} object to delete. + */ + public synchronized void deleteSpawn(Spawn spawn) + { + SpawnTable.getInstance().removeSpawn(spawn); + + final int x = ((spawn.getX() - World.WORLD_X_MIN) >> 15) + World.TILE_X_MIN; + final int y = ((spawn.getY() - World.WORLD_Y_MIN) >> 15) + World.TILE_Y_MIN; + final int npcSpawnTemplateId = spawn.getNpcSpawnTemplateId(); + final Location spawnLocation = spawn.getSpawnLocation(); + final File spawnFile = npcSpawnTemplateId > 0 ? new File(_spawnTemplates.get(npcSpawnTemplateId)) : new File(OTHER_XML_FOLDER + "/" + x + "_" + y + ".xml"); + final File tempFile = new File(spawnFile.getAbsolutePath().substring(ServerConfig.DATAPACK_ROOT.getAbsolutePath().length() + 1).replace('\\', '/') + ".tmp"); + try + { + final BufferedReader reader = new BufferedReader(new FileReader(spawnFile)); + final BufferedWriter writer = new BufferedWriter(new FileWriter(tempFile)); + + boolean found = false; // In XML you can have more than one spawn with same coords. + boolean isMultiLine = false; // In case spawn has more stats. + boolean lastLineFound = false; // Used to check for empty file. + int lineCount = 0; + String currentLine; + + final NpcSpawnTerritory npcSpawnTerritory = spawn.getSpawnTerritory(); + if (npcSpawnTerritory == null) + { + final String spawnId = String.valueOf(spawn.getId()); + final String spawnX = String.valueOf(spawnLocation != null ? spawnLocation.getX() : spawn.getX()); + final String spawnY = String.valueOf(spawnLocation != null ? spawnLocation.getY() : spawn.getY()); + final String spawnZ = String.valueOf(spawnLocation != null ? spawnLocation.getZ() : spawn.getZ()); + + while ((currentLine = reader.readLine()) != null) + { + if (!found) + { + if (isMultiLine) + { + if (currentLine.contains("
    ")) + { + found = true; + } + continue; + } + + if (currentLine.contains(spawnId) && currentLine.contains(spawnX) && currentLine.contains(spawnY) && currentLine.contains(spawnZ)) + { + if (!currentLine.contains("/>") && !currentLine.contains("
    ")) + { + isMultiLine = true; + } + else + { + found = true; + } + continue; + } + } + + writer.write(currentLine + System.lineSeparator()); + if (currentLine.contains("")) + { + lastLineFound = true; + } + + if (!lastLineFound) + { + lineCount++; + } + } + } + else + { + SEARCH: while ((currentLine = reader.readLine()) != null) + { + if (!found) + { + if (isMultiLine) + { + if (currentLine.contains("
    ")) + { + found = true; + } + continue; + } + + if (currentLine.contains('"' + npcSpawnTerritory.getName() + '"')) + { + isMultiLine = true; + continue SEARCH; + } + } + + writer.write(currentLine + System.lineSeparator()); + if (currentLine.contains("")) + { + lastLineFound = true; + } + + if (!lastLineFound) + { + lineCount++; + } + } + } + + writer.close(); + reader.close(); + spawnFile.delete(); + tempFile.renameTo(spawnFile); + + // Delete empty file. + if (lineCount < 5) + { + LOGGER.info(getClass().getSimpleName() + ": Deleted empty file: " + spawnFile.getAbsolutePath().substring(ServerConfig.DATAPACK_ROOT.getAbsolutePath().length() + 1).replace('\\', '/')); + spawnFile.delete(); + } + } + catch (Exception e) + { + LOGGER.warning(getClass().getSimpleName() + ": Spawn " + spawn + " could not be removed from the spawn XML files: " + e); + } + } + + /** + * Retrieves the file path associated with the specified spawn template ID. + * @param templateId the unique ID of the spawn template. + * @return the file path corresponding to the specified template ID, or {@code null} if not found. + */ + public String getSpawnFile(int templateId) + { + return _spawnTemplates.get(templateId); + } + + public static SpawnData getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final SpawnData INSTANCE = new SpawnData(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/StaticObjectData.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/StaticObjectData.java new file mode 100644 index 0000000..06247cb --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/StaticObjectData.java @@ -0,0 +1,111 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.data.xml; + +import java.io.File; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import org.w3c.dom.Document; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; + +import org.l2jmobius.commons.util.IXmlReader; +import org.l2jmobius.gameserver.entity.actor.instance.StaticObject; +import org.l2jmobius.gameserver.entity.actor.templates.CreatureTemplate; +import org.l2jmobius.gameserver.util.StatSet; + +/** + * @author UnAfraid + */ +public class StaticObjectData implements IXmlReader +{ + private final Map _staticObjects = new HashMap<>(); + + protected StaticObjectData() + { + load(); + } + + @Override + public void load() + { + _staticObjects.clear(); + parseDatapackFile("data/StaticObjects.xml"); + LOGGER.info(getClass().getSimpleName() + ": Loaded " + _staticObjects.size() + " static object templates."); + } + + @Override + public void parseDocument(Document document, File file) + { + for (Node n = document.getFirstChild(); n != null; n = n.getNextSibling()) + { + if ("list".equalsIgnoreCase(n.getNodeName())) + { + for (Node d = n.getFirstChild(); d != null; d = d.getNextSibling()) + { + if ("object".equalsIgnoreCase(d.getNodeName())) + { + final NamedNodeMap attrs = d.getAttributes(); + final StatSet set = new StatSet(); + for (int i = 0; i < attrs.getLength(); i++) + { + final Node att = attrs.item(i); + set.set(att.getNodeName(), att.getNodeValue()); + } + + addObject(set); + } + } + } + } + } + + /** + * Initialize an static object based on the stats set and add it to the map. + * @param set the stats set to add. + */ + private void addObject(StatSet set) + { + final StaticObject obj = new StaticObject(new CreatureTemplate(new StatSet()), set.getInt("id")); + obj.setType(set.getInt("type", 0)); + obj.setName(set.getString("name")); + obj.setMap(set.getString("texture", "none"), set.getInt("map_x", 0), set.getInt("map_y", 0)); + obj.spawnMe(set.getInt("x"), set.getInt("y"), set.getInt("z")); + _staticObjects.put(obj.getObjectId(), obj); + } + + /** + * Retrieves the collection of all initialized static objects. + * @return a {@link Collection} of {@link StaticObject} instances representing the current static objects in the map. + */ + public Collection getStaticObjects() + { + return _staticObjects.values(); + } + + public static StaticObjectData getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final StaticObjectData INSTANCE = new StaticObjectData(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/TeleporterData.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/TeleporterData.java new file mode 100644 index 0000000..f82a6ef --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/data/xml/TeleporterData.java @@ -0,0 +1,148 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.data.xml; + +import java.io.File; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Logger; + +import org.w3c.dom.Document; +import org.w3c.dom.NamedNodeMap; + +import org.l2jmobius.commons.util.IXmlReader; +import org.l2jmobius.gameserver.entity.actor.enums.player.TeleportType; +import org.l2jmobius.gameserver.entity.teleporter.TeleportHolder; +import org.l2jmobius.gameserver.util.StatSet; + +/** + * @author UnAfraid + */ +public class TeleporterData implements IXmlReader +{ + private static final Logger LOGGER = Logger.getLogger(TeleporterData.class.getName()); + + private final Map> _teleporters = new ConcurrentHashMap<>(); + + protected TeleporterData() + { + load(); + } + + @Override + public void load() + { + _teleporters.clear(); + parseDatapackDirectory("data/teleporters", true); + LOGGER.info(getClass().getSimpleName() + ": Loaded " + _teleporters.size() + " npc teleporters."); + } + + @Override + public void parseDocument(Document document, File file) + { + forEach(document, "list", list -> forEach(list, "npc", npc -> + { + final Map teleList = new HashMap<>(); + + // Parse npc node child. + final int npcId = parseInteger(npc.getAttributes(), "id"); + forEach(npc, node -> + { + switch (node.getNodeName()) + { + case "teleport": + { + final NamedNodeMap nodeAttrs = node.getAttributes(); + + // Parse attributes + final TeleportType type = parseEnum(nodeAttrs, TeleportType.class, "type"); + final String name = parseString(nodeAttrs, "name", type.name()); + + // Parse locations + final TeleportHolder holder = new TeleportHolder(name, type); + forEach(node, "location", location -> holder.registerLocation(new StatSet(parseAttributes(location)))); + + // Register holder + if (teleList.putIfAbsent(name, holder) != null) + { + LOGGER.warning("Duplicate teleport list (" + name + ") has been found for NPC: " + npcId); + } + break; + } + case "npcs": + { + forEach(node, "npc", npcNode -> + { + final int id = parseInteger(npcNode.getAttributes(), "id"); + registerTeleportList(id, teleList); + }); + break; + } + } + }); + registerTeleportList(npcId, teleList); + })); + } + + /** + * Retrieves the total count of teleporters registered in the data. + * @return the number of teleporters available + */ + public int getTeleporterCount() + { + return _teleporters.size(); + } + + /** + * Registers teleport data for a specific NPC to the global teleporter list. + *

    + * If a teleporter with the given {@code npcId} already exists, the new {@code teleList} data will be merged into the existing data. + *

    + * @param npcId the template ID of the teleporter NPC + * @param teleList a map of teleport data associated with the NPC + */ + private void registerTeleportList(int npcId, Map teleList) + { + _teleporters.computeIfAbsent(npcId, k -> new ConcurrentHashMap<>()).putAll(teleList); + } + + /** + * Retrieves the teleport data associated with a specified NPC and list name. + *

    + * This method searches for the teleport list by {@code npcId} and {@code listName}. If found, the corresponding {@link TeleportHolder} is returned; otherwise, {@code null} is returned. + *

    + * @param npcId the template ID of the teleporter NPC + * @param listName the name of the teleport list + * @return the {@link TeleportHolder} for the specified NPC and list name, or {@code null} if not found + */ + public TeleportHolder getHolder(int npcId, String listName) + { + return _teleporters.getOrDefault(npcId, Collections.emptyMap()).get(listName); + } + + public static TeleporterData getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final TeleporterData INSTANCE = new TeleporterData(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/Location.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/Location.java new file mode 100644 index 0000000..f4fa028 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/Location.java @@ -0,0 +1,277 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity; + +import java.util.Objects; + +import org.l2jmobius.gameserver.interfaces.ILocational; +import org.l2jmobius.gameserver.interfaces.IPositionable; +import org.l2jmobius.gameserver.util.StatSet; + +/** + * Represents a 3D coordinate (x, y, z) with an optional heading and instance id. + */ +public class Location implements IPositionable +{ + private static final int MAX_HEADING = 65535; // Max unsigned 16-bit value. + + protected volatile int _x; + protected volatile int _y; + protected volatile int _z; + protected volatile int _heading; + protected volatile int _instanceId; + + /** + * Constructs a Location at a specified x, y and z coordinate, with a default heading and instance id of 0. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + */ + public Location(int x, int y, int z) + { + _x = x; + _y = y; + _z = z; + _heading = 0; + _instanceId = 0; + } + + /** + * Constructs a Location at a specified x, y, z coordinate and heading, with a default instance id of 0. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param heading the heading (direction) + */ + public Location(int x, int y, int z, int heading) + { + _x = x; + _y = y; + _z = z; + _heading = heading; + _instanceId = 0; + } + + /** + * Constructs a Location based on the position and heading of a WorldObject. + * @param obj the WorldObject to derive the location from. + */ + public Location(WorldObject obj) + { + this(obj.getX(), obj.getY(), obj.getZ(), obj.getHeading(), obj.getInstanceId()); + } + + /** + * Constructs a Location based on the position, heading and instance id of a WorldObject. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param heading the heading (direction) + * @param instanceId the instance id + */ + public Location(int x, int y, int z, int heading, int instanceId) + { + _x = x; + _y = y; + _z = z; + _heading = heading; + _instanceId = instanceId; + } + + /** + * Constructs a Location from a {@link StatSet}, retrieving values for x, y, z, heading and instance id. + * @param set a StatSet containing x, y, z and heading properties. + */ + public Location(StatSet set) + { + _x = set.getInt("x", 0); + _y = set.getInt("y", 0); + _z = set.getInt("z", 0); + _heading = set.getInt("heading", 0); + _instanceId = set.getInt("instanceId", 0); + } + + /** + * Retrieves the x coordinate. + * @return the x coordinate + */ + @Override + public int getX() + { + return _x; + } + + /** + * Retrieves the y coordinate. + * @return the y coordinate + */ + @Override + public int getY() + { + return _y; + } + + /** + * Retrieves the z coordinate. + * @return the z coordinate + */ + @Override + public int getZ() + { + return _z; + } + + /** + * Sets the x, y and z coordinates of this Location. + * @param x the new x coordinate + * @param y the new y coordinate + * @param z the new z coordinate + */ + @Override + public void setXYZ(int x, int y, int z) + { + _x = x; + _y = y; + _z = z; + } + + /** + * Updates the x, y and z coordinates of this Location based on another {@link ILocational}. + * @param loc the source ILocational to copy coordinates from. + */ + @Override + public void setXYZ(ILocational loc) + { + setXYZ(loc.getX(), loc.getY(), loc.getZ()); + } + + /** + * Retrieves the heading (direction) of this Location. + * @return the heading of this location. + */ + @Override + public int getHeading() + { + return _heading; + } + + /** + * Sets the heading (direction) for this Location. + * @param heading the new the heading for this location. + */ + @Override + public void setHeading(int heading) + { + _heading = Math.clamp(heading, 0, MAX_HEADING); + } + + /** + * Get the instance Id. + * @return the instance Id + */ + @Override + public int getInstanceId() + { + return _instanceId; + } + + /** + * Set the instance Id. + * @param instanceId the instance Id to set + */ + @Override + public void setInstanceId(int instanceId) + { + _instanceId = instanceId; + } + + /** + * Provides the current Location instance, as this class implements {@link IPositionable}. + * @return the current Location instance + */ + @Override + public IPositionable getLocation() + { + return this; + } + + /** + * Sets this Location's coordinates and heading to match another Location's. + * @param loc the source Location to copy from. + */ + @Override + public void setLocation(Location loc) + { + _x = loc.getX(); + _y = loc.getY(); + _z = loc.getZ(); + _heading = loc.getHeading(); + _instanceId = loc.getInstanceId(); + } + + /** + * Creates a shallow copy of this Location. + * @return a new Location instance with the same x, y, z, heading and instance id values. + */ + @Override + public Location clone() + { + return new Location(_x, _y, _z, _heading, _instanceId); + } + + /** + * Computes a hash code based on this Location's coordinates and heading. + * @return the hash code for this Location. + */ + @Override + public int hashCode() + { + return (31 * Objects.hash(_x, _y)) + Objects.hash(_z); + } + + /** + * Checks equality between this Location and another object.
    + * Two Locations are considered equal if they have the same x, y, z coordinates and heading. + * @param obj the object to compare + * @return {@code true} if the given object is a Location with the same x, y, z and heading values; otherwise, {@code false} + */ + @Override + public boolean equals(Object obj) + { + if (obj instanceof Location) + { + final Location loc = (Location) obj; + return (getX() == loc.getX()) && (getY() == loc.getY()) && (getZ() == loc.getZ()) && (getHeading() == loc.getHeading()) && (getInstanceId() == loc.getInstanceId()); + } + + return false; + } + + /** + * Provides a string representation of this Location. + * @return a formatted string containing the class name, x, y, z coordinates and heading. + */ + @Override + public String toString() + { + return "[" + getClass().getSimpleName() + "] X: " + _x + " Y: " + _y + " Z: " + _z + " Heading: " + _heading + " InstanceId: " + _instanceId; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/World.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/World.java new file mode 100644 index 0000000..4651e38 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/World.java @@ -0,0 +1,2600 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Consumer; +import java.util.function.Predicate; +import java.util.logging.Logger; + +import org.l2jmobius.commons.config.InterfaceConfig; +import org.l2jmobius.commons.util.Rnd; +import org.l2jmobius.gameserver.ai.CreatureAI; +import org.l2jmobius.gameserver.ai.Intention; +import org.l2jmobius.gameserver.config.custom.FactionSystemConfig; +import org.l2jmobius.gameserver.data.sql.CharInfoTable; +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.Npc; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.Summon; +import org.l2jmobius.gameserver.entity.actor.instance.Pet; +import org.l2jmobius.gameserver.entity.spawns.Spawn; +import org.l2jmobius.gameserver.network.Disconnection; +import org.l2jmobius.gameserver.network.enums.ChatType; +import org.l2jmobius.gameserver.network.serverpackets.CreatureSay; +import org.l2jmobius.gameserver.network.serverpackets.DeleteObject; +import org.l2jmobius.gameserver.network.serverpackets.ExShowScreenMessage; +import org.l2jmobius.gameserver.network.serverpackets.LeaveWorld; +import org.l2jmobius.gameserver.network.serverpackets.ServerPacket; + +public class World +{ + private static final Logger LOGGER = Logger.getLogger(World.class.getName()); + + public static volatile int MAX_CONNECTED_COUNT = 0; + public static volatile int OFFLINE_TRADE_COUNT = 0; + + /** Bit shift, defines number of regions note, shifting by 15 will result in regions corresponding to map tiles shifting by 11 divides one tile to 16x16 regions. */ + public static final int SHIFT_BY = 11; + + public static final int TILE_SIZE = 32768; + + /** Map dimensions. */ + public static final int TILE_X_MIN = 16; + public static final int TILE_Y_MIN = 10; + public static final int TILE_X_MAX = 26; + public static final int TILE_Y_MAX = 25; + public static final int TILE_ZERO_COORD_X = 20; + public static final int TILE_ZERO_COORD_Y = 18; + public static final int WORLD_X_MIN = (TILE_X_MIN - TILE_ZERO_COORD_X) * TILE_SIZE; + public static final int WORLD_Y_MIN = (TILE_Y_MIN - TILE_ZERO_COORD_Y) * TILE_SIZE; + + public static final int WORLD_X_MAX = ((TILE_X_MAX - TILE_ZERO_COORD_X) + 1) * TILE_SIZE; + public static final int WORLD_Y_MAX = ((TILE_Y_MAX - TILE_ZERO_COORD_Y) + 1) * TILE_SIZE; + + /** Z coordinate limits */ + public static final int WORLD_Z_MIN = -16000; + public static final int WORLD_Z_MAX = 16000; + public static final int Z_REGION_SIZE = 2000; + + /** Calculated offset used so top left region is 0,0 */ + public static final int OFFSET_X = Math.abs(WORLD_X_MIN >> SHIFT_BY); + public static final int OFFSET_Y = Math.abs(WORLD_Y_MIN >> SHIFT_BY); + + /** Number of regions. */ + private static final int REGIONS_X = (WORLD_X_MAX >> SHIFT_BY) + OFFSET_X; + private static final int REGIONS_Y = (WORLD_Y_MAX >> SHIFT_BY) + OFFSET_Y; + private static final int REGIONS_Z = (Math.abs(WORLD_Z_MIN) + Math.abs(WORLD_Z_MAX)) / Z_REGION_SIZE; + + /** Map containing all the players in game. */ + private static final Map _allPlayers = new ConcurrentHashMap<>(); + /** Map containing all the Good players in game. */ + private static final Map _allGoodPlayers = new ConcurrentHashMap<>(); + /** Map containing all the Evil players in game. */ + private static final Map _allEvilPlayers = new ConcurrentHashMap<>(); + /** Map containing all visible objects. */ + private static final Map _allObjects = new ConcurrentHashMap<>(); + /** Map with the pets instances and their owner ID. */ + private static final Map _petsInstance = new ConcurrentHashMap<>(); + + private static final WorldRegion[][][] _worldRegions = new WorldRegion[REGIONS_X + 1][REGIONS_Y + 1][REGIONS_Z]; + + private World() + { + } + + public static void init() + { + // Initialize regions. + for (int x = 0; x <= REGIONS_X; x++) + { + for (int y = 0; y <= REGIONS_Y; y++) + { + for (int z = 0; z < REGIONS_Z; z++) + { + _worldRegions[x][y][z] = new WorldRegion(x, y, z); + } + } + } + + // Set surrounding regions. + for (int rx = 0; rx <= REGIONS_X; rx++) + { + for (int ry = 0; ry <= REGIONS_Y; ry++) + { + for (int rz = 0; rz < REGIONS_Z; rz++) + { + final List surroundingRegions = new ArrayList<>(); + for (int sx = rx - 1; sx <= (rx + 1); sx++) + { + for (int sy = ry - 1; sy <= (ry + 1); sy++) + { + for (int sz = rz - 1; sz <= (rz + 1); sz++) + { + if (((sx >= 0) && (sx < REGIONS_X) && (sy >= 0) && (sy < REGIONS_Y) && (sz >= 0) && (sz < REGIONS_Z))) + { + surroundingRegions.add(_worldRegions[sx][sy][sz]); + } + } + } + } + + WorldRegion[] regionArray = new WorldRegion[surroundingRegions.size()]; + regionArray = surroundingRegions.toArray(regionArray); + _worldRegions[rx][ry][rz].setSurroundingRegions(regionArray); + } + } + } + + // When GUI is enabled World is called early. So we need to skip this log. + if (!InterfaceConfig.ENABLE_GUI) + { + LOGGER.info(World.class.getSimpleName() + ": (" + REGIONS_X + " by " + REGIONS_Y + ") World Region Grid set up."); + } + } + + /** + * Adds an object to the world.
    + *
    + * Example of use: + *
      + *
    • Withdraw an item from the warehouse, create an item
    • + *
    • Spawn a Creature (PC, NPC, Pet)
    • + *
    + * @param object + */ + public static void addObject(WorldObject object) + { + _allObjects.putIfAbsent(object.getObjectId(), object); + + // if (_allObjects.putIfAbsent(object.getObjectId(), object) != null) + // { + // LOGGER.warning(World.class.getSimpleName() + ": Object " + object + " already exists in the world. Stack Trace: " + CommonUtil.getTraceString(Thread.currentThread().getStackTrace())); + // } + + if (object.isPlayer()) + { + final Player newPlayer = object.asPlayer(); + if (newPlayer.isTeleporting()) // TODO: Drop when we stop removing player from the world while teleporting. + { + return; + } + + final Player existingPlayer = _allPlayers.putIfAbsent(object.getObjectId(), newPlayer); + if (existingPlayer != null) + { + Disconnection.of(existingPlayer).storeAndDeleteWith(LeaveWorld.STATIC_PACKET); + Disconnection.of(newPlayer).storeAndDeleteWith(LeaveWorld.STATIC_PACKET); + LOGGER.warning(World.class.getSimpleName() + ": Duplicate character!? Disconnected both characters (" + newPlayer.getName() + ")"); + } + else if (FactionSystemConfig.FACTION_SYSTEM_ENABLED) + { + addFactionPlayerToWorld(newPlayer); + } + } + } + + /** + * Removes an object from the world.
    + *
    + * Example of use: + *
      + *
    • Delete item from inventory, transfer Item from inventory to warehouse
    • + *
    • Crystallize item
    • + *
    • Remove NPC/PC/Pet from the world
    • + *
    + * @param object the object to remove + */ + public static void removeObject(WorldObject object) + { + _allObjects.remove(object.getObjectId()); + if (object.isPlayer()) + { + final Player player = object.asPlayer(); + if (player.isTeleporting()) // TODO: Drop when we stop removing player from the world while teleporting. + { + return; + } + + _allPlayers.remove(object.getObjectId()); + + if (FactionSystemConfig.FACTION_SYSTEM_ENABLED) + { + if (player.isGood()) + { + _allGoodPlayers.remove(player.getObjectId()); + } + else if (player.isEvil()) + { + _allEvilPlayers.remove(player.getObjectId()); + } + } + } + } + + /** + * Example of use: + *
      + *
    • Client packets : Action, AttackRequest, RequestJoinParty, RequestJoinPledge...
    • + *
    + * @param objectId Identifier of the WorldObject + * @return the WorldObject object that belongs to an ID or null if no object found. + */ + public static WorldObject findObject(int objectId) + { + return _allObjects.get(objectId); + } + + public static Collection getVisibleObjects() + { + return _allObjects.values(); + } + + /** + * Get the count of all visible objects in world. + * @return count off all World objects + */ + public static int getVisibleObjectsCount() + { + return _allObjects.size(); + } + + public static Collection getPlayers() + { + return _allPlayers.values(); + } + + public static Collection getAllGoodPlayers() + { + return _allGoodPlayers.values(); + } + + public static Collection getAllEvilPlayers() + { + return _allEvilPlayers.values(); + } + + /** + * If you have access to player objectId use {@link #getPlayer(int playerObjId)} + * @param name Name of the player to get Instance + * @return the player instance corresponding to the given name. + */ + public static Player getPlayer(String name) + { + return getPlayer(CharInfoTable.getInstance().getIdByName(name)); + } + + /** + * @param objectId of the player to get Instance + * @return the player instance corresponding to the given object ID. + */ + public static Player getPlayer(int objectId) + { + return _allPlayers.get(objectId); + } + + /** + * @param ownerId ID of the owner + * @return the pet instance from the given ownerId. + */ + public static Pet getPet(int ownerId) + { + return _petsInstance.get(ownerId); + } + + /** + * Add the given pet instance from the given ownerId. + * @param ownerId ID of the owner + * @param pet Pet of the pet + * @return + */ + public static Pet addPet(int ownerId, Pet pet) + { + return _petsInstance.put(ownerId, pet); + } + + /** + * Remove the given pet instance. + * @param ownerId ID of the owner + */ + public static void removePet(int ownerId) + { + _petsInstance.remove(ownerId); + } + + /** + * This operation is quite heave as it iterates all world visible objects. + * @param npcId the id of the NPC to find. + * @return the first NPC found corresponding to the given id. + */ + public static Npc getNpc(int npcId) + { + for (WorldObject wo : getVisibleObjects()) + { + if (wo.isNpc() && (wo.getId() == npcId)) + { + return wo.asNpc(); + } + } + + return null; + } + + /** + * Add a WorldObject in the world. Concept: WorldObject (including Player) are identified in _visibleObjects of his current WorldRegion and in _knownObjects of other surrounding Creatures
    + * Player are identified in _allPlayers of World, in _allPlayers of his current WorldRegion and in _knownPlayer of other surrounding Creatures Actions: + *
  • Add the WorldObject object in _allPlayers* of World
  • + *
  • Add the WorldObject object in _gmList** of GmListTable
  • + *
  • Add object in _knownObjects and _knownPlayer* of all surrounding WorldRegion Creatures
  • + *
  • If object is a Creature, add all surrounding WorldObject in its _knownObjects and all surrounding Player in its _knownPlayer

  • + * * only if object is a Player
    + * ** only if object is a GM Player Caution: This method DOESN'T ADD the object in _visibleObjects and _allPlayers* of WorldRegion (need synchronisation)
    + * Caution: This method DOESN'T ADD the object to _allObjects and _allPlayers* of World (need synchronisation) Example of use: + *
  • Drop an Item
  • + *
  • Spawn a Creature
  • + *
  • Apply Death Penalty of a Player

  • + * @param object L2object to add in the world + * @param newRegion WorldRegion in wich the object will be add (not used) + */ + public static void addVisibleObject(WorldObject object, WorldRegion newRegion) + { + if (!newRegion.isActive()) + { + return; + } + + forEachVisibleObject(object, WorldObject.class, wo -> + { + if (object.isPlayer() && wo.isVisibleFor(object.asPlayer())) + { + wo.sendInfo(object.asPlayer()); + if (wo.isCreature()) + { + final CreatureAI ai = wo.asCreature().getAI(); + if (ai != null) + { + ai.describeStateToPlayer(object.asPlayer()); + if (wo.isMonster() && (ai.getIntention() == Intention.IDLE)) + { + ai.setIntentionActive(); + } + } + } + } + + if (wo.isPlayer() && object.isVisibleFor(wo.asPlayer())) + { + object.sendInfo(wo.asPlayer()); + if (object.isCreature()) + { + final CreatureAI ai = object.asCreature().getAI(); + if (ai != null) + { + ai.describeStateToPlayer(wo.asPlayer()); + if (object.isMonster() && (ai.getIntention() == Intention.IDLE)) + { + ai.setIntentionActive(); + } + } + } + } + }); + } + + public static void addFactionPlayerToWorld(Player player) + { + if (player.isGood()) + { + _allGoodPlayers.put(player.getObjectId(), player); + } + else if (player.isEvil()) + { + _allEvilPlayers.put(player.getObjectId(), player); + } + } + + /** + * Remove a WorldObject from the world. Concept: WorldObject (including Player) are identified in _visibleObjects of his current WorldRegion and in _knownObjects of other surrounding Creatures
    + * Player are identified in _allPlayers of World, in _allPlayers of his current WorldRegion and in _knownPlayer of other surrounding Creatures Actions: + *
  • Remove the WorldObject object from _allPlayers* of World
  • + *
  • Remove the WorldObject object from _visibleObjects and _allPlayers* of WorldRegion
  • + *
  • Remove the WorldObject object from _gmList** of GmListTable
  • + *
  • Remove object from _knownObjects and _knownPlayer* of all surrounding WorldRegion Creatures
  • + *
  • If object is a Creature, remove all WorldObject from its _knownObjects and all Player from its _knownPlayer
  • Caution: This method DOESN'T REMOVE the object from _allObjects of World * only if object is a Player
    + * ** only if object is a GM Player Example of use: + *
  • Pickup an Item
  • + *
  • Decay a Creature

  • + * @param object L2object to remove from the world + * @param oldRegion WorldRegion in which the object was before removing + */ + public static void removeVisibleObject(WorldObject object, WorldRegion oldRegion) + { + if ((object == null) || (oldRegion == null)) + { + return; + } + + oldRegion.removeVisibleObject(object); + + // Go through all surrounding WorldRegion Creatures. + final WorldRegion[] surroundingRegions = oldRegion.getSurroundingRegions(); + for (int i = 0; i < surroundingRegions.length; i++) + { + final Collection visibleObjects = surroundingRegions[i].getVisibleObjects(); + if (visibleObjects.isEmpty()) + { + continue; + } + + for (WorldObject wo : visibleObjects) + { + if (wo == object) + { + continue; + } + + if (object.isCreature()) + { + final Creature objectCreature = object.asCreature(); + final CreatureAI ai = objectCreature.getAI(); + if (ai != null) + { + ai.notifyActionForgetObject(wo); + } + + if (objectCreature.getTarget() == wo) + { + objectCreature.setTarget(null); + } + + if (object.isPlayer()) + { + object.sendPacket(new DeleteObject(wo)); + } + } + + if (wo.isCreature()) + { + final Creature woCreature = wo.asCreature(); + final CreatureAI ai = woCreature.getAI(); + if (ai != null) + { + ai.notifyActionForgetObject(object); + } + + if (woCreature.getTarget() == object) + { + woCreature.setTarget(null); + } + + if (wo.isPlayer()) + { + wo.sendPacket(new DeleteObject(object)); + } + } + } + } + } + + public static void switchRegion(WorldObject object, WorldRegion newRegion) + { + final WorldRegion oldRegion = object.getWorldRegion(); + if ((oldRegion == null) || (oldRegion == newRegion)) + { + return; + } + + final WorldRegion[] oldSurroundingRegions = oldRegion.getSurroundingRegions(); + for (int i = 0; i < oldSurroundingRegions.length; i++) + { + final WorldRegion worldRegion = oldSurroundingRegions[i]; + if (newRegion.isSurroundingRegion(worldRegion)) + { + continue; + } + + final Collection visibleObjects = worldRegion.getVisibleObjects(); + if (visibleObjects.isEmpty()) + { + continue; + } + + for (WorldObject wo : visibleObjects) + { + if (wo == object) + { + continue; + } + + if (object.isCreature()) + { + final Creature objectCreature = object.asCreature(); + final CreatureAI ai = objectCreature.getAI(); + if (ai != null) + { + ai.notifyActionForgetObject(wo); + } + + if (objectCreature.getTarget() == wo) + { + objectCreature.setTarget(null); + } + + if (object.isPlayer()) + { + object.sendPacket(new DeleteObject(wo)); + } + } + + if (wo.isCreature()) + { + final Creature woCreature = wo.asCreature(); + final CreatureAI ai = woCreature.getAI(); + if (ai != null) + { + ai.notifyActionForgetObject(object); + } + + if (woCreature.getTarget() == object) + { + woCreature.setTarget(null); + } + + if (wo.isPlayer()) + { + wo.sendPacket(new DeleteObject(object)); + } + } + } + } + + final WorldRegion[] newSurroundingRegions = newRegion.getSurroundingRegions(); + for (int i = 0; i < newSurroundingRegions.length; i++) + { + final WorldRegion worldRegion = newSurroundingRegions[i]; + if (oldRegion.isSurroundingRegion(worldRegion)) + { + continue; + } + + final Collection visibleObjects = worldRegion.getVisibleObjects(); + if (visibleObjects.isEmpty()) + { + continue; + } + + for (WorldObject wo : visibleObjects) + { + if ((wo == object) || (wo.getInstanceId() != object.getInstanceId())) + { + continue; + } + + if (object.isPlayer() && wo.isVisibleFor(object.asPlayer())) + { + wo.sendInfo(object.asPlayer()); + if (wo.isCreature()) + { + final CreatureAI ai = wo.asCreature().getAI(); + if (ai != null) + { + ai.describeStateToPlayer(object.asPlayer()); + if (wo.isMonster() && (ai.getIntention() == Intention.IDLE)) + { + ai.setIntentionActive(); + } + } + } + } + + if (wo.isPlayer() && object.isVisibleFor(wo.asPlayer())) + { + object.sendInfo(wo.asPlayer()); + if (object.isCreature()) + { + final CreatureAI ai = object.asCreature().getAI(); + if (ai != null) + { + ai.describeStateToPlayer(wo.asPlayer()); + if (object.isMonster() && (ai.getIntention() == Intention.IDLE)) + { + ai.setIntentionActive(); + } + } + } + } + } + } + } + + /** + * Collects every visible {@link WorldObject} of type {@code T} in the reference object's surrounding regions that share its instance world. The reference object itself is excluded. + *

    + * If the caller does not need a materialized {@link List}, prefer {@link #forEachVisibleObject(WorldObject, Class, Consumer)} to avoid {@link ArrayList} allocation, resizes and population overhead, or {@link #getFirstVisibleObject(WorldObject, Class)} / + * {@link #forFirstVisibleObject(WorldObject, Class, Consumer)} when a single match is enough. + * @param the {@link WorldObject} subtype to collect + * @param object the reference object whose surrounding regions are scanned + * @param clazz the type filter + * @return a freshly allocated {@link List} of matches; empty when {@code object} is {@code null} or has no region + */ + public static List getVisibleObjects(WorldObject object, Class clazz) + { + final List result = new ArrayList<>(); + if (object == null) + { + return result; + } + + final WorldRegion worldRegion = getRegion(object); + if (worldRegion == null) + { + return result; + } + + final WorldRegion[] surroundingRegions = worldRegion.getSurroundingRegions(); + for (int i = 0; i < surroundingRegions.length; i++) + { + final Collection visibleObjects = surroundingRegions[i].getVisibleObjects(); + if (visibleObjects.isEmpty()) + { + continue; + } + + for (WorldObject wo : visibleObjects) + { + if ((wo == object) || !clazz.isInstance(wo) || (wo.getInstanceId() != object.getInstanceId())) + { + continue; + } + + result.add(clazz.cast(wo)); + } + } + + return result; + } + + /** + * Collects every visible {@link WorldObject} of type {@code T} in the reference object's surrounding regions that share its instance world and lie within {@code range} 3D-distance from it. The reference object itself is excluded. + *

    + * If the caller does not need a materialized {@link List}, prefer {@link #forEachVisibleObjectInRange(WorldObject, Class, int, Consumer)} to avoid {@link ArrayList} allocation, resizes and population overhead, or {@link #getFirstVisibleObjectInRange(WorldObject, Class, int)} / + * {@link #forFirstVisibleObjectInRange(WorldObject, Class, int, Consumer)} when a single match is enough. + * @param the {@link WorldObject} subtype to collect + * @param object the reference object whose surrounding regions are scanned + * @param clazz the type filter + * @param range the maximum inclusive 3D distance from {@code object} + * @return a freshly allocated {@link List} of matches; empty when {@code object} is {@code null} or has no region + */ + public static List getVisibleObjectsInRange(WorldObject object, Class clazz, int range) + { + final List result = new ArrayList<>(); + if (object == null) + { + return result; + } + + final WorldRegion worldRegion = getRegion(object); + if (worldRegion == null) + { + return result; + } + + final WorldRegion[] surroundingRegions = worldRegion.getSurroundingRegions(); + for (int i = 0; i < surroundingRegions.length; i++) + { + final Collection visibleObjects = surroundingRegions[i].getVisibleObjects(); + if (visibleObjects.isEmpty()) + { + continue; + } + + for (WorldObject wo : visibleObjects) + { + if ((wo == object) || !clazz.isInstance(wo) || (wo.getInstanceId() != object.getInstanceId()) || (wo.calculateDistance3D(object) > range)) + { + continue; + } + + result.add(clazz.cast(wo)); + } + } + + return result; + } + + /** + * Collects every visible {@link WorldObject} of type {@code T} in the reference object's surrounding regions that share its instance world and satisfy {@code predicate}. The reference object itself is excluded. + *

    + * If the caller does not need a materialized {@link List}, prefer {@link #forEachVisibleObject(WorldObject, Class, Consumer)} (testing the predicate inline) to avoid {@link ArrayList} allocation, resizes and population overhead, or {@link #getFirstVisibleObject(WorldObject, Class, Predicate)} / + * {@link #forFirstVisibleObject(WorldObject, Class, Predicate, Consumer)} when a single match is enough. + * @param the {@link WorldObject} subtype to collect + * @param object the reference object whose surrounding regions are scanned + * @param clazz the type filter + * @param predicate additional filter applied to each candidate already matching {@code clazz} + * @return a freshly allocated {@link List} of matches; empty when {@code object} is {@code null} or has no region + */ + public static List getVisibleObjects(WorldObject object, Class clazz, Predicate predicate) + { + final List result = new ArrayList<>(); + if (object == null) + { + return result; + } + + final WorldRegion worldRegion = getRegion(object); + if (worldRegion == null) + { + return result; + } + + final WorldRegion[] surroundingRegions = worldRegion.getSurroundingRegions(); + for (int i = 0; i < surroundingRegions.length; i++) + { + final Collection visibleObjects = surroundingRegions[i].getVisibleObjects(); + if (visibleObjects.isEmpty()) + { + continue; + } + + for (WorldObject wo : visibleObjects) + { + if ((wo == object) || !clazz.isInstance(wo) || (wo.getInstanceId() != object.getInstanceId())) + { + continue; + } + + final T cast = clazz.cast(wo); + if (predicate.test(cast)) + { + result.add(cast); + } + } + } + + return result; + } + + /** + * Collects every visible {@link WorldObject} of type {@code T} in the reference object's surrounding regions that share its instance world, lie within {@code range} 3D-distance from it and satisfy {@code predicate}. The reference object itself is excluded. + *

    + * If the caller does not need a materialized {@link List}, prefer {@link #forEachVisibleObjectInRange(WorldObject, Class, int, Consumer)} (testing the predicate inline) to avoid {@link ArrayList} allocation, resizes and population overhead, or + * {@link #getFirstVisibleObjectInRange(WorldObject, Class, int, Predicate)} / {@link #forFirstVisibleObjectInRange(WorldObject, Class, int, Predicate, Consumer)} when a single match is enough. + * @param the {@link WorldObject} subtype to collect + * @param object the reference object whose surrounding regions are scanned + * @param clazz the type filter + * @param range the maximum inclusive 3D distance from {@code object} + * @param predicate additional filter applied to each candidate already matching {@code clazz} and within range + * @return a freshly allocated {@link List} of matches; empty when {@code object} is {@code null} or has no region + */ + public static List getVisibleObjectsInRange(WorldObject object, Class clazz, int range, Predicate predicate) + { + final List result = new ArrayList<>(); + if (object == null) + { + return result; + } + + final WorldRegion worldRegion = getRegion(object); + if (worldRegion == null) + { + return result; + } + + final WorldRegion[] surroundingRegions = worldRegion.getSurroundingRegions(); + for (int i = 0; i < surroundingRegions.length; i++) + { + final Collection visibleObjects = surroundingRegions[i].getVisibleObjects(); + if (visibleObjects.isEmpty()) + { + continue; + } + + for (WorldObject wo : visibleObjects) + { + if ((wo == object) || !clazz.isInstance(wo) || (wo.getInstanceId() != object.getInstanceId()) || (wo.calculateDistance3D(object) > range)) + { + continue; + } + + final T cast = clazz.cast(wo); + if (predicate.test(cast)) + { + result.add(cast); + } + } + } + + return result; + } + + /** + * Applies {@code c} to every visible {@link WorldObject} of type {@code T} in the reference object's surrounding regions that share its instance world. The reference object itself is skipped. Returns silently when {@code object} is {@code null} or has no region. + * @param the {@link WorldObject} subtype to consume + * @param object the reference object whose surrounding regions are scanned + * @param clazz the type filter + * @param c action invoked for each matching object + */ + public static void forEachVisibleObject(WorldObject object, Class clazz, Consumer c) + { + if (object == null) + { + return; + } + + final WorldRegion worldRegion = getRegion(object); + if (worldRegion == null) + { + return; + } + + final WorldRegion[] surroundingRegions = worldRegion.getSurroundingRegions(); + for (int i = 0; i < surroundingRegions.length; i++) + { + final Collection visibleObjects = surroundingRegions[i].getVisibleObjects(); + if (visibleObjects.isEmpty()) + { + continue; + } + + for (WorldObject wo : visibleObjects) + { + if ((wo == object) || !clazz.isInstance(wo) || (wo.getInstanceId() != object.getInstanceId())) + { + continue; + } + + c.accept(clazz.cast(wo)); + } + } + } + + /** + * Applies {@code c} to every visible {@link WorldObject} of type {@code T} in the reference object's surrounding regions that share its instance world and lie within {@code range} 3D-distance from it. The reference object itself is skipped. Returns silently when {@code object} is {@code null} + * or has no region. + * @param the {@link WorldObject} subtype to consume + * @param object the reference object whose surrounding regions are scanned + * @param clazz the type filter + * @param range the maximum inclusive 3D distance from {@code object} + * @param c action invoked for each matching object + */ + public static void forEachVisibleObjectInRange(WorldObject object, Class clazz, int range, Consumer c) + { + if (object == null) + { + return; + } + + final WorldRegion worldRegion = getRegion(object); + if (worldRegion == null) + { + return; + } + + final WorldRegion[] surroundingRegions = worldRegion.getSurroundingRegions(); + for (int i = 0; i < surroundingRegions.length; i++) + { + final Collection visibleObjects = surroundingRegions[i].getVisibleObjects(); + if (visibleObjects.isEmpty()) + { + continue; + } + + for (WorldObject wo : visibleObjects) + { + if ((wo == object) || !clazz.isInstance(wo) || (wo.getInstanceId() != object.getInstanceId()) || (wo.calculateDistance3D(object) > range)) + { + continue; + } + + c.accept(clazz.cast(wo)); + } + } + } + + /** + * Returns the first visible {@link WorldObject} of type {@code T} encountered in the reference object's surrounding regions that shares its instance world, or {@code null} if none is found (also when {@code object} is {@code null} or has no region). The reference object itself is skipped. + *

    + * "First" is the first hit in region iteration order, not the closest match. + * @param the {@link WorldObject} subtype to return + * @param object the reference object whose surrounding regions are scanned + * @param clazz the type filter + * @return the first matching object, or {@code null} + */ + public static T getFirstVisibleObject(WorldObject object, Class clazz) + { + if (object == null) + { + return null; + } + + final WorldRegion worldRegion = getRegion(object); + if (worldRegion == null) + { + return null; + } + + final WorldRegion[] surroundingRegions = worldRegion.getSurroundingRegions(); + for (int i = 0; i < surroundingRegions.length; i++) + { + final Collection visibleObjects = surroundingRegions[i].getVisibleObjects(); + if (visibleObjects.isEmpty()) + { + continue; + } + + for (WorldObject wo : visibleObjects) + { + if ((wo == object) || !clazz.isInstance(wo) || (wo.getInstanceId() != object.getInstanceId())) + { + continue; + } + + return clazz.cast(wo); + } + } + + return null; + } + + /** + * Returns the first visible {@link WorldObject} of type {@code T} encountered in the reference object's surrounding regions that shares its instance world and lies within {@code range} 3D-distance from it, or {@code null} if none is found (also when {@code object} is {@code null} or has no + * region). The reference object itself is skipped. + *

    + * "First" is the first hit in region iteration order, not the closest match within range. + * @param the {@link WorldObject} subtype to return + * @param object the reference object whose surrounding regions are scanned + * @param clazz the type filter + * @param range the maximum inclusive 3D distance from {@code object} + * @return the first matching object, or {@code null} + */ + public static T getFirstVisibleObjectInRange(WorldObject object, Class clazz, int range) + { + if (object == null) + { + return null; + } + + final WorldRegion worldRegion = getRegion(object); + if (worldRegion == null) + { + return null; + } + + final WorldRegion[] surroundingRegions = worldRegion.getSurroundingRegions(); + for (int i = 0; i < surroundingRegions.length; i++) + { + final Collection visibleObjects = surroundingRegions[i].getVisibleObjects(); + if (visibleObjects.isEmpty()) + { + continue; + } + + for (WorldObject wo : visibleObjects) + { + if ((wo == object) || !clazz.isInstance(wo) || (wo.getInstanceId() != object.getInstanceId()) || (wo.calculateDistance3D(object) > range)) + { + continue; + } + + return clazz.cast(wo); + } + } + + return null; + } + + /** + * Returns the first visible {@link WorldObject} of type {@code T} encountered in the reference object's surrounding regions that shares its instance world and satisfies {@code predicate}, or {@code null} if none is found (also when {@code object} is {@code null} or has no region). The reference + * object itself is skipped. + *

    + * "First" is the first hit in region iteration order, not the closest match. + * @param the {@link WorldObject} subtype to return + * @param object the reference object whose surrounding regions are scanned + * @param clazz the type filter + * @param predicate additional filter applied to each candidate already matching {@code clazz} + * @return the first matching object, or {@code null} + */ + public static T getFirstVisibleObject(WorldObject object, Class clazz, Predicate predicate) + { + if (object == null) + { + return null; + } + + final WorldRegion worldRegion = getRegion(object); + if (worldRegion == null) + { + return null; + } + + final WorldRegion[] surroundingRegions = worldRegion.getSurroundingRegions(); + for (int i = 0; i < surroundingRegions.length; i++) + { + final Collection visibleObjects = surroundingRegions[i].getVisibleObjects(); + if (visibleObjects.isEmpty()) + { + continue; + } + + for (WorldObject wo : visibleObjects) + { + if ((wo == object) || !clazz.isInstance(wo) || (wo.getInstanceId() != object.getInstanceId())) + { + continue; + } + + final T cast = clazz.cast(wo); + if (predicate.test(cast)) + { + return cast; + } + } + } + + return null; + } + + /** + * Returns the first visible {@link WorldObject} of type {@code T} encountered in the reference object's surrounding regions that shares its instance world, lies within {@code range} 3D-distance from it and satisfies {@code predicate}, or {@code null} if none is found (also when {@code object} + * is {@code null} or has no region). The reference object itself is skipped. + *

    + * "First" is the first hit in region iteration order, not the closest match within range. + * @param the {@link WorldObject} subtype to return + * @param object the reference object whose surrounding regions are scanned + * @param clazz the type filter + * @param range the maximum inclusive 3D distance from {@code object} + * @param predicate additional filter applied to each candidate already matching {@code clazz} and within range + * @return the first matching object, or {@code null} + */ + public static T getFirstVisibleObjectInRange(WorldObject object, Class clazz, int range, Predicate predicate) + { + if (object == null) + { + return null; + } + + final WorldRegion worldRegion = getRegion(object); + if (worldRegion == null) + { + return null; + } + + final WorldRegion[] surroundingRegions = worldRegion.getSurroundingRegions(); + for (int i = 0; i < surroundingRegions.length; i++) + { + final Collection visibleObjects = surroundingRegions[i].getVisibleObjects(); + if (visibleObjects.isEmpty()) + { + continue; + } + + for (WorldObject wo : visibleObjects) + { + if ((wo == object) || !clazz.isInstance(wo) || (wo.getInstanceId() != object.getInstanceId()) || (wo.calculateDistance3D(object) > range)) + { + continue; + } + + final T cast = clazz.cast(wo); + if (predicate.test(cast)) + { + return cast; + } + } + } + + return null; + } + + /** + * Invokes {@code action} on the first visible {@link WorldObject} of type {@code T} encountered in the reference object's surrounding regions that shares its instance world, then returns. Does nothing if no match is found, or if {@code object} is {@code null} or has no region. The reference + * object itself is skipped. + *

    + * "First" is the first hit in region iteration order, not the closest match. Prefer this over {@link #getFirstVisibleObject(WorldObject, Class)} when the hit can be acted on inline without a {@code null} check. + * @param the {@link WorldObject} subtype to consume + * @param object the reference object whose surrounding regions are scanned + * @param clazz the type filter + * @param action action invoked at most once, on the first matching object + */ + public static void forFirstVisibleObject(WorldObject object, Class clazz, Consumer action) + { + if (object == null) + { + return; + } + + final WorldRegion worldRegion = getRegion(object); + if (worldRegion == null) + { + return; + } + + final WorldRegion[] surroundingRegions = worldRegion.getSurroundingRegions(); + for (int i = 0; i < surroundingRegions.length; i++) + { + final Collection visibleObjects = surroundingRegions[i].getVisibleObjects(); + if (visibleObjects.isEmpty()) + { + continue; + } + + for (WorldObject wo : visibleObjects) + { + if ((wo == object) || !clazz.isInstance(wo) || (wo.getInstanceId() != object.getInstanceId())) + { + continue; + } + + action.accept(clazz.cast(wo)); + return; + } + } + } + + /** + * Invokes {@code action} on the first visible {@link WorldObject} of type {@code T} encountered in the reference object's surrounding regions that shares its instance world and lies within {@code range} 3D-distance from it, then returns. Does nothing if no match is found, or if {@code object} + * is {@code null} or has no region. The reference object itself is skipped. + *

    + * "First" is the first hit in region iteration order, not the closest match within range. Prefer this over {@link #getFirstVisibleObjectInRange(WorldObject, Class, int)} when the hit can be acted on inline without a {@code null} check. + * @param the {@link WorldObject} subtype to consume + * @param object the reference object whose surrounding regions are scanned + * @param clazz the type filter + * @param range the maximum inclusive 3D distance from {@code object} + * @param action action invoked at most once, on the first matching object + */ + public static void forFirstVisibleObjectInRange(WorldObject object, Class clazz, int range, Consumer action) + { + if (object == null) + { + return; + } + + final WorldRegion worldRegion = getRegion(object); + if (worldRegion == null) + { + return; + } + + final WorldRegion[] surroundingRegions = worldRegion.getSurroundingRegions(); + for (int i = 0; i < surroundingRegions.length; i++) + { + final Collection visibleObjects = surroundingRegions[i].getVisibleObjects(); + if (visibleObjects.isEmpty()) + { + continue; + } + + for (WorldObject wo : visibleObjects) + { + if ((wo == object) || !clazz.isInstance(wo) || (wo.getInstanceId() != object.getInstanceId()) || (wo.calculateDistance3D(object) > range)) + { + continue; + } + + action.accept(clazz.cast(wo)); + return; + } + } + } + + /** + * Invokes {@code action} on the first visible {@link WorldObject} of type {@code T} encountered in the reference object's surrounding regions that shares its instance world and satisfies {@code predicate}, then returns. Does nothing if no match is found, or if {@code object} is {@code null} or + * has no region. The reference object itself is skipped. + *

    + * "First" is the first hit in region iteration order, not the closest match. Prefer this over {@link #getFirstVisibleObject(WorldObject, Class, Predicate)} when the hit can be acted on inline without a {@code null} check. + * @param the {@link WorldObject} subtype to consume + * @param object the reference object whose surrounding regions are scanned + * @param clazz the type filter + * @param predicate additional filter applied to each candidate already matching {@code clazz} + * @param action action invoked at most once, on the first matching object + */ + public static void forFirstVisibleObject(WorldObject object, Class clazz, Predicate predicate, Consumer action) + { + if (object == null) + { + return; + } + + final WorldRegion worldRegion = getRegion(object); + if (worldRegion == null) + { + return; + } + + final WorldRegion[] surroundingRegions = worldRegion.getSurroundingRegions(); + for (int i = 0; i < surroundingRegions.length; i++) + { + final Collection visibleObjects = surroundingRegions[i].getVisibleObjects(); + if (visibleObjects.isEmpty()) + { + continue; + } + + for (WorldObject wo : visibleObjects) + { + if ((wo == object) || !clazz.isInstance(wo) || (wo.getInstanceId() != object.getInstanceId())) + { + continue; + } + + final T cast = clazz.cast(wo); + if (predicate.test(cast)) + { + action.accept(cast); + return; + } + } + } + } + + /** + * Invokes {@code action} on the first visible {@link WorldObject} of type {@code T} encountered in the reference object's surrounding regions that shares its instance world, lies within {@code range} 3D-distance from it and satisfies {@code predicate}, then returns. Does nothing if no match is + * found, or if {@code object} is {@code null} or has no region. The reference object itself is skipped. + *

    + * "First" is the first hit in region iteration order, not the closest match within range. Prefer this over {@link #getFirstVisibleObjectInRange(WorldObject, Class, int, Predicate)} when the hit can be acted on inline without a {@code null} check. + * @param the {@link WorldObject} subtype to consume + * @param object the reference object whose surrounding regions are scanned + * @param clazz the type filter + * @param range the maximum inclusive 3D distance from {@code object} + * @param predicate additional filter applied to each candidate already matching {@code clazz} and within range + * @param action action invoked at most once, on the first matching object + */ + public static void forFirstVisibleObjectInRange(WorldObject object, Class clazz, int range, Predicate predicate, Consumer action) + { + if (object == null) + { + return; + } + + final WorldRegion worldRegion = getRegion(object); + if (worldRegion == null) + { + return; + } + + final WorldRegion[] surroundingRegions = worldRegion.getSurroundingRegions(); + for (int i = 0; i < surroundingRegions.length; i++) + { + final Collection visibleObjects = surroundingRegions[i].getVisibleObjects(); + if (visibleObjects.isEmpty()) + { + continue; + } + + for (WorldObject wo : visibleObjects) + { + if ((wo == object) || !clazz.isInstance(wo) || (wo.getInstanceId() != object.getInstanceId()) || (wo.calculateDistance3D(object) > range)) + { + continue; + } + + final T cast = clazz.cast(wo); + if (predicate.test(cast)) + { + action.accept(cast); + return; + } + } + } + } + + /** + * Returns the nearest visible {@link WorldObject} of type {@code T} in the reference object's surrounding regions that shares its instance world, or {@code null} if none is found (also when {@code object} is {@code null} or has no region). The reference object itself is skipped. + *

    + * "Nearest" is computed by 3D distance to {@code object} via a single-pass scan that tracks the best candidate, with no {@link ArrayList} allocation. Prefer {@link #getFirstVisibleObject(WorldObject, Class)} unless the actual closest match is required - the simple {@code getFirst*} variant can + * short-circuit on the first hit while this method must inspect every candidate. + * @param the {@link WorldObject} subtype to return + * @param object the reference object whose surrounding regions are scanned + * @param clazz the type filter + * @return the nearest matching object, or {@code null} + */ + public static T getNearestVisibleObject(WorldObject object, Class clazz) + { + if (object == null) + { + return null; + } + + final WorldRegion worldRegion = getRegion(object); + if (worldRegion == null) + { + return null; + } + + T nearest = null; + double bestDistance = Double.MAX_VALUE; + final WorldRegion[] surroundingRegions = worldRegion.getSurroundingRegions(); + for (int i = 0; i < surroundingRegions.length; i++) + { + final Collection visibleObjects = surroundingRegions[i].getVisibleObjects(); + if (visibleObjects.isEmpty()) + { + continue; + } + + for (WorldObject wo : visibleObjects) + { + if ((wo == object) || !clazz.isInstance(wo) || (wo.getInstanceId() != object.getInstanceId())) + { + continue; + } + + final double distance = wo.calculateDistance3D(object); + if (distance < bestDistance) + { + bestDistance = distance; + nearest = clazz.cast(wo); + } + } + } + + return nearest; + } + + /** + * Returns the nearest visible {@link WorldObject} of type {@code T} in the reference object's surrounding regions that shares its instance world and lies within {@code range} 3D-distance from it, or {@code null} if none is found (also when {@code object} is {@code null} or has no region). The + * reference object itself is skipped. + *

    + * "Nearest" is computed by 3D distance to {@code object} via a single-pass scan that tracks the best candidate, with no {@link ArrayList} allocation. Prefer {@link #getFirstVisibleObjectInRange(WorldObject, Class, int)} unless the actual closest match is required - the simple {@code getFirst*} + * variant can short-circuit on the first hit while this method must inspect every candidate within range. + * @param the {@link WorldObject} subtype to return + * @param object the reference object whose surrounding regions are scanned + * @param clazz the type filter + * @param range the maximum inclusive 3D distance from {@code object} + * @return the nearest matching object, or {@code null} + */ + public static T getNearestVisibleObjectInRange(WorldObject object, Class clazz, int range) + { + if (object == null) + { + return null; + } + + final WorldRegion worldRegion = getRegion(object); + if (worldRegion == null) + { + return null; + } + + T nearest = null; + double bestDistance = Double.MAX_VALUE; + final WorldRegion[] surroundingRegions = worldRegion.getSurroundingRegions(); + for (int i = 0; i < surroundingRegions.length; i++) + { + final Collection visibleObjects = surroundingRegions[i].getVisibleObjects(); + if (visibleObjects.isEmpty()) + { + continue; + } + + for (WorldObject wo : visibleObjects) + { + if ((wo == object) || !clazz.isInstance(wo) || (wo.getInstanceId() != object.getInstanceId())) + { + continue; + } + + final double distance = wo.calculateDistance3D(object); + if ((distance <= range) && (distance < bestDistance)) + { + bestDistance = distance; + nearest = clazz.cast(wo); + } + } + } + + return nearest; + } + + /** + * Returns the nearest visible {@link WorldObject} of type {@code T} in the reference object's surrounding regions that shares its instance world and satisfies {@code predicate}, or {@code null} if none is found (also when {@code object} is {@code null} or has no region). The reference object + * itself is skipped. + *

    + * "Nearest" is computed by 3D distance to {@code object} via a single-pass scan that tracks the best candidate, with no {@link ArrayList} allocation. Prefer {@link #getFirstVisibleObject(WorldObject, Class, Predicate)} unless the actual closest match is required - the simple {@code getFirst*} + * variant can short-circuit on the first hit while this method must inspect every candidate. + * @param the {@link WorldObject} subtype to return + * @param object the reference object whose surrounding regions are scanned + * @param clazz the type filter + * @param predicate additional filter applied to each candidate already matching {@code clazz} + * @return the nearest matching object, or {@code null} + */ + public static T getNearestVisibleObject(WorldObject object, Class clazz, Predicate predicate) + { + if (object == null) + { + return null; + } + + final WorldRegion worldRegion = getRegion(object); + if (worldRegion == null) + { + return null; + } + + T nearest = null; + double bestDistance = Double.MAX_VALUE; + final WorldRegion[] surroundingRegions = worldRegion.getSurroundingRegions(); + for (int i = 0; i < surroundingRegions.length; i++) + { + final Collection visibleObjects = surroundingRegions[i].getVisibleObjects(); + if (visibleObjects.isEmpty()) + { + continue; + } + + for (WorldObject wo : visibleObjects) + { + if ((wo == object) || !clazz.isInstance(wo) || (wo.getInstanceId() != object.getInstanceId())) + { + continue; + } + + final T cast = clazz.cast(wo); + if (predicate.test(cast)) + { + final double distance = wo.calculateDistance3D(object); + if (distance < bestDistance) + { + bestDistance = distance; + nearest = cast; + } + } + } + } + + return nearest; + } + + /** + * Returns the nearest visible {@link WorldObject} of type {@code T} in the reference object's surrounding regions that shares its instance world, lies within {@code range} 3D-distance from it and satisfies {@code predicate}, or {@code null} if none is found (also when {@code object} is {@code + * null} or has no region). The reference object itself is skipped. + *

    + * "Nearest" is computed by 3D distance to {@code object} via a single-pass scan that tracks the best candidate, with no {@link ArrayList} allocation. Prefer {@link #getFirstVisibleObjectInRange(WorldObject, Class, int, Predicate)} unless the actual closest match is required - the simple {@code + * getFirst*} variant can short-circuit on the first hit while this method must inspect every candidate within range. + * @param the {@link WorldObject} subtype to return + * @param object the reference object whose surrounding regions are scanned + * @param clazz the type filter + * @param range the maximum inclusive 3D distance from {@code object} + * @param predicate additional filter applied to each candidate already matching {@code clazz} and within range + * @return the nearest matching object, or {@code null} + */ + public static T getNearestVisibleObjectInRange(WorldObject object, Class clazz, int range, Predicate predicate) + { + if (object == null) + { + return null; + } + + final WorldRegion worldRegion = getRegion(object); + if (worldRegion == null) + { + return null; + } + + T nearest = null; + double bestDistance = Double.MAX_VALUE; + final WorldRegion[] surroundingRegions = worldRegion.getSurroundingRegions(); + for (int i = 0; i < surroundingRegions.length; i++) + { + final Collection visibleObjects = surroundingRegions[i].getVisibleObjects(); + if (visibleObjects.isEmpty()) + { + continue; + } + + for (WorldObject wo : visibleObjects) + { + if ((wo == object) || !clazz.isInstance(wo) || (wo.getInstanceId() != object.getInstanceId())) + { + continue; + } + + final double distance = wo.calculateDistance3D(object); + if (distance <= range) + { + final T cast = clazz.cast(wo); + if (predicate.test(cast) && (distance < bestDistance)) + { + bestDistance = distance; + nearest = cast; + } + } + } + } + + return nearest; + } + + /** + * Invokes {@code action} on the nearest visible {@link WorldObject} of type {@code T} in the reference object's surrounding regions that shares its instance world, then returns. Does nothing if no match is found, or if {@code object} is {@code null} or has no region. The reference object itself + * is skipped. + *

    + * "Nearest" is computed by 3D distance to {@code object} via a single-pass scan that tracks the best candidate, with no {@link ArrayList} allocation. Prefer {@link #forFirstVisibleObject(WorldObject, Class, Consumer)} unless the actual closest match is required - the simple {@code forFirst*} + * variant can short-circuit on the first hit while this method must inspect every candidate. + * @param the {@link WorldObject} subtype to consume + * @param object the reference object whose surrounding regions are scanned + * @param clazz the type filter + * @param action action invoked at most once, on the nearest matching object + */ + public static void forNearestVisibleObject(WorldObject object, Class clazz, Consumer action) + { + if (object == null) + { + return; + } + + final WorldRegion worldRegion = getRegion(object); + if (worldRegion == null) + { + return; + } + + T nearest = null; + double bestDistance = Double.MAX_VALUE; + final WorldRegion[] surroundingRegions = worldRegion.getSurroundingRegions(); + for (int i = 0; i < surroundingRegions.length; i++) + { + final Collection visibleObjects = surroundingRegions[i].getVisibleObjects(); + if (visibleObjects.isEmpty()) + { + continue; + } + + for (WorldObject wo : visibleObjects) + { + if ((wo == object) || !clazz.isInstance(wo) || (wo.getInstanceId() != object.getInstanceId())) + { + continue; + } + + final double distance = wo.calculateDistance3D(object); + if (distance < bestDistance) + { + bestDistance = distance; + nearest = clazz.cast(wo); + } + } + } + + if (nearest != null) + { + action.accept(nearest); + } + } + + /** + * Invokes {@code action} on the nearest visible {@link WorldObject} of type {@code T} in the reference object's surrounding regions that shares its instance world and lies within {@code range} 3D-distance from it, then returns. Does nothing if no match is found, or if {@code object} is + * {@code null} or has no region. The reference object itself is skipped. + *

    + * "Nearest" is computed by 3D distance to {@code object} via a single-pass scan that tracks the best candidate, with no {@link ArrayList} allocation. Prefer {@link #forFirstVisibleObjectInRange(WorldObject, Class, int, Consumer)} unless the actual closest match is required - the simple {@code + * forFirst*} variant can short-circuit on the first hit while this method must inspect every candidate within range. + * @param the {@link WorldObject} subtype to consume + * @param object the reference object whose surrounding regions are scanned + * @param clazz the type filter + * @param range the maximum inclusive 3D distance from {@code object} + * @param action action invoked at most once, on the nearest matching object + */ + public static void forNearestVisibleObjectInRange(WorldObject object, Class clazz, int range, Consumer action) + { + if (object == null) + { + return; + } + + final WorldRegion worldRegion = getRegion(object); + if (worldRegion == null) + { + return; + } + + T nearest = null; + double bestDistance = Double.MAX_VALUE; + final WorldRegion[] surroundingRegions = worldRegion.getSurroundingRegions(); + for (int i = 0; i < surroundingRegions.length; i++) + { + final Collection visibleObjects = surroundingRegions[i].getVisibleObjects(); + if (visibleObjects.isEmpty()) + { + continue; + } + + for (WorldObject wo : visibleObjects) + { + if ((wo == object) || !clazz.isInstance(wo) || (wo.getInstanceId() != object.getInstanceId())) + { + continue; + } + + final double distance = wo.calculateDistance3D(object); + if ((distance <= range) && (distance < bestDistance)) + { + bestDistance = distance; + nearest = clazz.cast(wo); + } + } + } + + if (nearest != null) + { + action.accept(nearest); + } + } + + /** + * Invokes {@code action} on the nearest visible {@link WorldObject} of type {@code T} in the reference object's surrounding regions that shares its instance world and satisfies {@code predicate}, then returns. Does nothing if no match is found, or if {@code object} is {@code null} or has no + * region. The reference object itself is skipped. + *

    + * "Nearest" is computed by 3D distance to {@code object} via a single-pass scan that tracks the best candidate, with no {@link ArrayList} allocation. Prefer {@link #forFirstVisibleObject(WorldObject, Class, Predicate, Consumer)} unless the actual closest match is required - the simple {@code + * forFirst*} variant can short-circuit on the first hit while this method must inspect every candidate. + * @param the {@link WorldObject} subtype to consume + * @param object the reference object whose surrounding regions are scanned + * @param clazz the type filter + * @param predicate additional filter applied to each candidate already matching {@code clazz} + * @param action action invoked at most once, on the nearest matching object + */ + public static void forNearestVisibleObject(WorldObject object, Class clazz, Predicate predicate, Consumer action) + { + if (object == null) + { + return; + } + + final WorldRegion worldRegion = getRegion(object); + if (worldRegion == null) + { + return; + } + + T nearest = null; + double bestDistance = Double.MAX_VALUE; + final WorldRegion[] surroundingRegions = worldRegion.getSurroundingRegions(); + for (int i = 0; i < surroundingRegions.length; i++) + { + final Collection visibleObjects = surroundingRegions[i].getVisibleObjects(); + if (visibleObjects.isEmpty()) + { + continue; + } + + for (WorldObject wo : visibleObjects) + { + if ((wo == object) || !clazz.isInstance(wo) || (wo.getInstanceId() != object.getInstanceId())) + { + continue; + } + + final T cast = clazz.cast(wo); + if (predicate.test(cast)) + { + final double distance = wo.calculateDistance3D(object); + if (distance < bestDistance) + { + bestDistance = distance; + nearest = cast; + } + } + } + } + + if (nearest != null) + { + action.accept(nearest); + } + } + + /** + * Invokes {@code action} on the nearest visible {@link WorldObject} of type {@code T} in the reference object's surrounding regions that shares its instance world, lies within {@code range} 3D-distance from it and satisfies {@code predicate}, then returns. Does nothing if no match is found, or + * if {@code object} is {@code null} or has no region. The reference object itself is skipped. + *

    + * "Nearest" is computed by 3D distance to {@code object} via a single-pass scan that tracks the best candidate, with no {@link ArrayList} allocation. Prefer {@link #forFirstVisibleObjectInRange(WorldObject, Class, int, Predicate, Consumer)} unless the actual closest match is required - the + * simple {@code forFirst*} variant can short-circuit on the first hit while this method must inspect every candidate within range. + * @param the {@link WorldObject} subtype to consume + * @param object the reference object whose surrounding regions are scanned + * @param clazz the type filter + * @param range the maximum inclusive 3D distance from {@code object} + * @param predicate additional filter applied to each candidate already matching {@code clazz} and within range + * @param action action invoked at most once, on the nearest matching object + */ + public static void forNearestVisibleObjectInRange(WorldObject object, Class clazz, int range, Predicate predicate, Consumer action) + { + if (object == null) + { + return; + } + + final WorldRegion worldRegion = getRegion(object); + if (worldRegion == null) + { + return; + } + + T nearest = null; + double bestDistance = Double.MAX_VALUE; + final WorldRegion[] surroundingRegions = worldRegion.getSurroundingRegions(); + for (int i = 0; i < surroundingRegions.length; i++) + { + final Collection visibleObjects = surroundingRegions[i].getVisibleObjects(); + if (visibleObjects.isEmpty()) + { + continue; + } + + for (WorldObject wo : visibleObjects) + { + if ((wo == object) || !clazz.isInstance(wo) || (wo.getInstanceId() != object.getInstanceId())) + { + continue; + } + + final double distance = wo.calculateDistance3D(object); + if (distance <= range) + { + final T cast = clazz.cast(wo); + if (predicate.test(cast) && (distance < bestDistance)) + { + bestDistance = distance; + nearest = cast; + } + } + } + } + + if (nearest != null) + { + action.accept(nearest); + } + } + + /** + * Returns a uniformly-random visible {@link WorldObject} of type {@code T} in the reference object's surrounding regions that shares its instance world, or {@code null} if none is found (also when {@code object} is {@code null} or has no region). The reference object itself is skipped. + *

    + * Uses single-pass reservoir sampling (Algorithm R with k=1) - no {@link ArrayList} allocation, O(1) memory. Prefer {@link #getFirstVisibleObject(WorldObject, Class)} when first-in-iteration-order is acceptable; this method's full scan is only needed when uniform randomness over all matching + * candidates is required. + * @param the {@link WorldObject} subtype to return + * @param object the reference object whose surrounding regions are scanned + * @param clazz the type filter + * @return a uniformly-random matching object, or {@code null} + */ + public static T getRandomVisibleObject(WorldObject object, Class clazz) + { + if (object == null) + { + return null; + } + + final WorldRegion worldRegion = getRegion(object); + if (worldRegion == null) + { + return null; + } + + T selected = null; + int count = 0; + final WorldRegion[] surroundingRegions = worldRegion.getSurroundingRegions(); + for (int i = 0; i < surroundingRegions.length; i++) + { + final Collection visibleObjects = surroundingRegions[i].getVisibleObjects(); + if (visibleObjects.isEmpty()) + { + continue; + } + + for (WorldObject wo : visibleObjects) + { + if ((wo == object) || !clazz.isInstance(wo) || (wo.getInstanceId() != object.getInstanceId())) + { + continue; + } + + count++; + if (Rnd.get(count) == 0) + { + selected = clazz.cast(wo); + } + } + } + + return (selected != null) ? selected : getFirstVisibleObject(object, clazz); + } + + /** + * Returns a uniformly-random visible {@link WorldObject} of type {@code T} in the reference object's surrounding regions that shares its instance world and lies within {@code range} 3D-distance from it, or {@code null} if none is found (also when {@code object} is {@code null} or has no + * region). The reference object itself is skipped. + *

    + * Uses single-pass reservoir sampling (Algorithm R with k=1) - no {@link ArrayList} allocation, O(1) memory. Prefer {@link #getFirstVisibleObjectInRange(WorldObject, Class, int)} when first-in-iteration-order is acceptable; this method's full scan is only needed when uniform randomness over all + * matching candidates within range is required. + * @param the {@link WorldObject} subtype to return + * @param object the reference object whose surrounding regions are scanned + * @param clazz the type filter + * @param range the maximum inclusive 3D distance from {@code object} + * @return a uniformly-random matching object, or {@code null} + */ + public static T getRandomVisibleObjectInRange(WorldObject object, Class clazz, int range) + { + if (object == null) + { + return null; + } + + final WorldRegion worldRegion = getRegion(object); + if (worldRegion == null) + { + return null; + } + + T selected = null; + int count = 0; + final WorldRegion[] surroundingRegions = worldRegion.getSurroundingRegions(); + for (int i = 0; i < surroundingRegions.length; i++) + { + final Collection visibleObjects = surroundingRegions[i].getVisibleObjects(); + if (visibleObjects.isEmpty()) + { + continue; + } + + for (WorldObject wo : visibleObjects) + { + if ((wo == object) || !clazz.isInstance(wo) || (wo.getInstanceId() != object.getInstanceId()) || (wo.calculateDistance3D(object) > range)) + { + continue; + } + + count++; + if (Rnd.get(count) == 0) + { + selected = clazz.cast(wo); + } + } + } + + return (selected != null) ? selected : getFirstVisibleObjectInRange(object, clazz, range); + } + + /** + * Returns a uniformly-random visible {@link WorldObject} of type {@code T} in the reference object's surrounding regions that shares its instance world and satisfies {@code predicate}, or {@code null} if none is found (also when {@code object} is {@code null} or has no region). The reference + * object itself is skipped. + *

    + * Uses single-pass reservoir sampling (Algorithm R with k=1) - no {@link ArrayList} allocation, O(1) memory. Prefer {@link #getFirstVisibleObject(WorldObject, Class, Predicate)} when first-in-iteration-order is acceptable; this method's full scan is only needed when uniform randomness over all + * matching candidates is required. + * @param the {@link WorldObject} subtype to return + * @param object the reference object whose surrounding regions are scanned + * @param clazz the type filter + * @param predicate additional filter applied to each candidate already matching {@code clazz} + * @return a uniformly-random matching object, or {@code null} + */ + public static T getRandomVisibleObject(WorldObject object, Class clazz, Predicate predicate) + { + if (object == null) + { + return null; + } + + final WorldRegion worldRegion = getRegion(object); + if (worldRegion == null) + { + return null; + } + + T selected = null; + T firstValid = null; + int count = 0; + final WorldRegion[] surroundingRegions = worldRegion.getSurroundingRegions(); + for (int i = 0; i < surroundingRegions.length; i++) + { + final Collection visibleObjects = surroundingRegions[i].getVisibleObjects(); + if (visibleObjects.isEmpty()) + { + continue; + } + + for (WorldObject wo : visibleObjects) + { + if ((wo == object) || !clazz.isInstance(wo) || (wo.getInstanceId() != object.getInstanceId())) + { + continue; + } + + final T cast = clazz.cast(wo); + if (predicate.test(cast)) + { + if (firstValid == null) + { + firstValid = cast; + } + + count++; + if (Rnd.get(count) == 0) + { + selected = cast; + } + } + } + } + + return (selected != null) ? selected : firstValid; + } + + /** + * Returns a uniformly-random visible {@link WorldObject} of type {@code T} in the reference object's surrounding regions that shares its instance world, lies within {@code range} 3D-distance from it and satisfies {@code predicate}, or {@code null} if none is found (also when {@code object} is + * {@code null} or has no region). The reference object itself is skipped. + *

    + * Uses single-pass reservoir sampling (Algorithm R with k=1) - no {@link ArrayList} allocation, O(1) memory. Prefer {@link #getFirstVisibleObjectInRange(WorldObject, Class, int, Predicate)} when first-in-iteration-order is acceptable; this method's full scan is only needed when uniform + * randomness over all matching candidates within range is required. + * @param the {@link WorldObject} subtype to return + * @param object the reference object whose surrounding regions are scanned + * @param clazz the type filter + * @param range the maximum inclusive 3D distance from {@code object} + * @param predicate additional filter applied to each candidate already matching {@code clazz} and within range + * @return a uniformly-random matching object, or {@code null} + */ + public static T getRandomVisibleObjectInRange(WorldObject object, Class clazz, int range, Predicate predicate) + { + if (object == null) + { + return null; + } + + final WorldRegion worldRegion = getRegion(object); + if (worldRegion == null) + { + return null; + } + + T selected = null; + T firstValid = null; + int count = 0; + final WorldRegion[] surroundingRegions = worldRegion.getSurroundingRegions(); + for (int i = 0; i < surroundingRegions.length; i++) + { + final Collection visibleObjects = surroundingRegions[i].getVisibleObjects(); + if (visibleObjects.isEmpty()) + { + continue; + } + + for (WorldObject wo : visibleObjects) + { + if ((wo == object) || !clazz.isInstance(wo) || (wo.getInstanceId() != object.getInstanceId()) || (wo.calculateDistance3D(object) > range)) + { + continue; + } + + final T cast = clazz.cast(wo); + if (predicate.test(cast)) + { + if (firstValid == null) + { + firstValid = cast; + } + + count++; + if (Rnd.get(count) == 0) + { + selected = cast; + } + } + } + } + + return (selected != null) ? selected : firstValid; + } + + /** + * Invokes {@code action} on a uniformly-random visible {@link WorldObject} of type {@code T} in the reference object's surrounding regions that shares its instance world. Does nothing if no match is found, or if {@code object} is {@code null} or has no region. The reference object itself is + * skipped. + *

    + * Uses single-pass reservoir sampling (Algorithm R with k=1) - no {@link ArrayList} allocation, O(1) memory. Prefer {@link #forFirstVisibleObject(WorldObject, Class, Consumer)} when first-in-iteration-order is acceptable; this method's full scan is only needed when uniform randomness is + * required. + * @param the {@link WorldObject} subtype to consume + * @param object the reference object whose surrounding regions are scanned + * @param clazz the type filter + * @param action action invoked at most once, on the randomly-selected matching object + */ + public static void forRandomVisibleObject(WorldObject object, Class clazz, Consumer action) + { + if (object == null) + { + return; + } + + final WorldRegion worldRegion = getRegion(object); + if (worldRegion == null) + { + return; + } + + T selected = null; + T firstValid = null; + int count = 0; + final WorldRegion[] surroundingRegions = worldRegion.getSurroundingRegions(); + for (int i = 0; i < surroundingRegions.length; i++) + { + final Collection visibleObjects = surroundingRegions[i].getVisibleObjects(); + if (visibleObjects.isEmpty()) + { + continue; + } + + for (WorldObject wo : visibleObjects) + { + if ((wo == object) || !clazz.isInstance(wo) || (wo.getInstanceId() != object.getInstanceId())) + { + continue; + } + + final T cast = clazz.cast(wo); + if (firstValid == null) + { + firstValid = cast; + } + + count++; + if (Rnd.get(count) == 0) + { + selected = cast; + } + } + } + + final T target = (selected != null) ? selected : firstValid; + if (target != null) + { + action.accept(target); + } + } + + /** + * Invokes {@code action} on a uniformly-random visible {@link WorldObject} of type {@code T} in the reference object's surrounding regions that shares its instance world and lies within {@code range} 3D-distance from it. Does nothing if no match is found, or if {@code object} is {@code null} or + * has no region. The reference object itself is skipped. + *

    + * Uses single-pass reservoir sampling (Algorithm R with k=1) - no {@link ArrayList} allocation, O(1) memory. Prefer {@link #forFirstVisibleObjectInRange(WorldObject, Class, int, Consumer)} when first-in-iteration-order is acceptable; this method's full scan is only needed when uniform + * randomness over all matching candidates within range is required. + * @param the {@link WorldObject} subtype to consume + * @param object the reference object whose surrounding regions are scanned + * @param clazz the type filter + * @param range the maximum inclusive 3D distance from {@code object} + * @param action action invoked at most once, on the randomly-selected matching object + */ + public static void forRandomVisibleObjectInRange(WorldObject object, Class clazz, int range, Consumer action) + { + if (object == null) + { + return; + } + + final WorldRegion worldRegion = getRegion(object); + if (worldRegion == null) + { + return; + } + + T selected = null; + T firstValid = null; + int count = 0; + final WorldRegion[] surroundingRegions = worldRegion.getSurroundingRegions(); + for (int i = 0; i < surroundingRegions.length; i++) + { + final Collection visibleObjects = surroundingRegions[i].getVisibleObjects(); + if (visibleObjects.isEmpty()) + { + continue; + } + + for (WorldObject wo : visibleObjects) + { + if ((wo == object) || !clazz.isInstance(wo) || (wo.getInstanceId() != object.getInstanceId()) || (wo.calculateDistance3D(object) > range)) + { + continue; + } + + final T cast = clazz.cast(wo); + if (firstValid == null) + { + firstValid = cast; + } + + count++; + if (Rnd.get(count) == 0) + { + selected = cast; + } + } + } + + final T target = (selected != null) ? selected : firstValid; + if (target != null) + { + action.accept(target); + } + } + + /** + * Invokes {@code action} on a uniformly-random visible {@link WorldObject} of type {@code T} in the reference object's surrounding regions that shares its instance world and satisfies {@code predicate}. Does nothing if no match is found, or if {@code object} is {@code null} or has no region. + * The reference object itself is skipped. + *

    + * Uses single-pass reservoir sampling (Algorithm R with k=1) - no {@link ArrayList} allocation, O(1) memory. Prefer {@link #forFirstVisibleObject(WorldObject, Class, Predicate, Consumer)} when first-in-iteration-order is acceptable; this method's full scan is only needed when uniform randomness + * is required. + * @param the {@link WorldObject} subtype to consume + * @param object the reference object whose surrounding regions are scanned + * @param clazz the type filter + * @param predicate additional filter applied to each candidate already matching {@code clazz} + * @param action action invoked at most once, on the randomly-selected matching object + */ + public static void forRandomVisibleObject(WorldObject object, Class clazz, Predicate predicate, Consumer action) + { + if (object == null) + { + return; + } + + final WorldRegion worldRegion = getRegion(object); + if (worldRegion == null) + { + return; + } + + T selected = null; + T firstValid = null; + int count = 0; + final WorldRegion[] surroundingRegions = worldRegion.getSurroundingRegions(); + for (int i = 0; i < surroundingRegions.length; i++) + { + final Collection visibleObjects = surroundingRegions[i].getVisibleObjects(); + if (visibleObjects.isEmpty()) + { + continue; + } + + for (WorldObject wo : visibleObjects) + { + if ((wo == object) || !clazz.isInstance(wo) || (wo.getInstanceId() != object.getInstanceId())) + { + continue; + } + + final T cast = clazz.cast(wo); + if (predicate.test(cast)) + { + if (firstValid == null) + { + firstValid = cast; + } + + count++; + if (Rnd.get(count) == 0) + { + selected = cast; + } + } + } + } + + final T target = (selected != null) ? selected : firstValid; + if (target != null) + { + action.accept(target); + } + } + + /** + * Invokes {@code action} on a uniformly-random visible {@link WorldObject} of type {@code T} in the reference object's surrounding regions that shares its instance world, lies within {@code range} 3D-distance from it and satisfies {@code predicate}. Does nothing if no match is found, or if + * {@code + * object} is {@code null} or has no region. The reference object itself is skipped. + *

    + * Uses single-pass reservoir sampling (Algorithm R with k=1) - no {@link ArrayList} allocation, O(1) memory. Prefer {@link #forFirstVisibleObjectInRange(WorldObject, Class, int, Predicate, Consumer)} when first-in-iteration-order is acceptable; this method's full scan is only needed when + * uniform randomness over all matching candidates within range is required. + * @param the {@link WorldObject} subtype to consume + * @param object the reference object whose surrounding regions are scanned + * @param clazz the type filter + * @param range the maximum inclusive 3D distance from {@code object} + * @param predicate additional filter applied to each candidate already matching {@code clazz} and within range + * @param action action invoked at most once, on the randomly-selected matching object + */ + public static void forRandomVisibleObjectInRange(WorldObject object, Class clazz, int range, Predicate predicate, Consumer action) + { + if (object == null) + { + return; + } + + final WorldRegion worldRegion = getRegion(object); + if (worldRegion == null) + { + return; + } + + T selected = null; + T firstValid = null; + int count = 0; + final WorldRegion[] surroundingRegions = worldRegion.getSurroundingRegions(); + for (int i = 0; i < surroundingRegions.length; i++) + { + final Collection visibleObjects = surroundingRegions[i].getVisibleObjects(); + if (visibleObjects.isEmpty()) + { + continue; + } + + for (WorldObject wo : visibleObjects) + { + if ((wo == object) || !clazz.isInstance(wo) || (wo.getInstanceId() != object.getInstanceId()) || (wo.calculateDistance3D(object) > range)) + { + continue; + } + + final T cast = clazz.cast(wo); + if (predicate.test(cast)) + { + if (firstValid == null) + { + firstValid = cast; + } + + count++; + if (Rnd.get(count) == 0) + { + selected = cast; + } + } + } + } + + final T target = (selected != null) ? selected : firstValid; + if (target != null) + { + action.accept(target); + } + } + + /** + * Sends a packet to every other player visible from the given object's surrounding regions, sharing the same instance world.
    + * The source object itself is excluded from the broadcast. + * @param object the reference WorldObject whose surrounding regions define the broadcast area + * @param packet the ServerPacket to send to each nearby player + */ + public static void broadcastToVisiblePlayers(WorldObject object, ServerPacket packet) + { + if (object == null) + { + return; + } + + final WorldRegion worldRegion = getRegion(object); + if (worldRegion == null) + { + return; + } + + packet.sendInBroadcast(); + + final WorldRegion[] regions = worldRegion.getSurroundingRegions(); + if (regions != null) + { + for (int i = 0; i < regions.length; i++) + { + final Collection objects = regions[i].getVisibleObjects(); + if (objects.isEmpty()) + { + continue; + } + + for (WorldObject nearby : objects) + { + if ((nearby == object) || !nearby.isPlayer() || (nearby.getInstanceId() != object.getInstanceId())) + { + continue; + } + + nearby.asPlayer().sendPacket(packet); + } + } + } + } + + /** + * Sends a packet to every other player visible from the given object's surrounding regions, sharing the same instance world and within the specified range.
    + * The source object itself is excluded from the broadcast. + * @param object the reference WorldObject whose surrounding regions define the broadcast area + * @param packet the ServerPacket to send to each nearby player + * @param range the maximum 3D distance from the object at which a player will receive the packet + */ + public static void broadcastToVisiblePlayersInRange(WorldObject object, ServerPacket packet, int range) + { + if (object == null) + { + return; + } + + final WorldRegion worldRegion = getRegion(object); + if (worldRegion == null) + { + return; + } + + packet.sendInBroadcast(); + + final WorldRegion[] regions = worldRegion.getSurroundingRegions(); + if (regions != null) + { + for (int i = 0; i < regions.length; i++) + { + final Collection objects = regions[i].getVisibleObjects(); + if (objects.isEmpty()) + { + continue; + } + + for (WorldObject nearby : objects) + { + if ((nearby == object) || !nearby.isPlayer() || (nearby.getInstanceId() != object.getInstanceId()) || (nearby.calculateDistance3D(object) > range)) + { + continue; + } + + nearby.asPlayer().sendPacket(packet); + } + } + } + } + + /** + * Sends a packet to the given object (if it is a player) and to every other player visible from its surrounding regions, sharing the same instance world. + * @param object the reference WorldObject whose surrounding regions define the broadcast area + * @param packet the ServerPacket to send to the object and each nearby player + */ + public static void broadcastToSelfAndVisiblePlayers(WorldObject object, ServerPacket packet) + { + if (object == null) + { + return; + } + + final WorldRegion worldRegion = getRegion(object); + if (worldRegion == null) + { + return; + } + + packet.sendInBroadcast(); + + final WorldRegion[] regions = worldRegion.getSurroundingRegions(); + if (regions != null) + { + for (int i = 0; i < regions.length; i++) + { + final Collection objects = regions[i].getVisibleObjects(); + if (objects.isEmpty()) + { + continue; + } + + for (WorldObject nearby : objects) + { + if (!nearby.isPlayer() || (nearby.getInstanceId() != object.getInstanceId())) + { + continue; + } + + nearby.asPlayer().sendPacket(packet); + } + } + } + } + + /** + * Sends a packet to the given object (if it is a player) and to every other player visible from its surrounding regions, sharing the same instance world and within the specified range. + * @param object the reference WorldObject whose surrounding regions define the broadcast area + * @param packet the ServerPacket to send to the object and each nearby player + * @param range the maximum 3D distance from the object at which a player will receive the packet + */ + public static void broadcastToSelfAndVisiblePlayersInRange(WorldObject object, ServerPacket packet, int range) + { + if (object == null) + { + return; + } + + final WorldRegion worldRegion = getRegion(object); + if (worldRegion == null) + { + return; + } + + packet.sendInBroadcast(); + + final WorldRegion[] regions = worldRegion.getSurroundingRegions(); + if (regions != null) + { + for (int i = 0; i < regions.length; i++) + { + final Collection objects = regions[i].getVisibleObjects(); + if (objects.isEmpty()) + { + continue; + } + + for (WorldObject nearby : objects) + { + if (!nearby.isPlayer() || (nearby.getInstanceId() != object.getInstanceId()) || (nearby.calculateDistance3D(object) > range)) + { + continue; + } + + nearby.asPlayer().sendPacket(packet); + } + } + } + } + + /** + * Sends a packet to every online player currently registered in the world, regardless of instance or location. + * @param packet the ServerPacket to send to all online players + */ + public static void broadcastToAllOnlinePlayers(ServerPacket packet) + { + packet.sendInBroadcast(); + + for (Player player : _allPlayers.values()) + { + if (player.isOnline()) + { + player.sendPacket(packet); + } + } + } + + /** + * Broadcasts a non-critical announcement chat message to every online player. + * @param text the text to be sent as a regular announcement + */ + public static void broadcastToAllOnlinePlayers(String text) + { + broadcastToAllOnlinePlayers(text, false); + } + + /** + * Broadcasts an announcement chat message to every online player, optionally as a critical announcement. + * @param text the text to be sent as an announcement + * @param isCritical {@code true} to send as a CRITICAL_ANNOUNCE chat type, {@code false} to send as a regular ANNOUNCEMENT + */ + public static void broadcastToAllOnlinePlayers(String text, boolean isCritical) + { + broadcastToAllOnlinePlayers(new CreatureSay(null, isCritical ? ChatType.CRITICAL_ANNOUNCE : ChatType.ANNOUNCEMENT, "", text)); + } + + /** + * Broadcasts an on-screen message to every online player for a default duration of 10 seconds. + * @param text the text to display on each player's screen + */ + public static void broadcastToAllOnlinePlayersOnScreen(String text) + { + broadcastToAllOnlinePlayers(new ExShowScreenMessage(text, 10000)); + } + + /** + * Calculate the current WorldRegions of the object according to its position (x,y). Example of use: + *

  • Set position of a new WorldObject (drop, spawn...)
  • + *
  • Update position of a WorldObject after a movement

  • + * @param object the object + * @return + */ + public static WorldRegion getRegion(WorldObject object) + { + try + { + final int z = object.getZ(); + final int regionZ = calculateRegionZ(z); + return _worldRegions[(object.getX() >> SHIFT_BY) + OFFSET_X][(object.getY() >> SHIFT_BY) + OFFSET_Y][regionZ]; + } + catch (ArrayIndexOutOfBoundsException e) // Precaution. Moved at invalid region? + { + disposeOutOfBoundsObject(object); + return null; + } + } + + public static WorldRegion getRegion(int x, int y, int z) + { + try + { + final int regionZ = calculateRegionZ(z); + return _worldRegions[(x >> SHIFT_BY) + OFFSET_X][(y >> SHIFT_BY) + OFFSET_Y][regionZ]; + } + catch (ArrayIndexOutOfBoundsException e) + { + LOGGER.warning(World.class.getSimpleName() + ": Incorrect world region X: " + ((x >> SHIFT_BY) + OFFSET_X) + " Y: " + ((y >> SHIFT_BY) + OFFSET_Y) + " Z: " + calculateRegionZ(z)); + return null; + } + } + + /** + * Calculate the region Z index based on Z coordinate + * @param z The Z coordinate + * @return The region Z index (0-3) + */ + private static int calculateRegionZ(int z) + { + // Handle values outside the normal range. + if (z < WORLD_Z_MIN) + { + return 0; // Bottom region. + } + if (z > WORLD_Z_MAX) + { + return REGIONS_Z - 1; // Top region. + } + + // Calculate the region Z index (0-based). + return (z - WORLD_Z_MIN) / Z_REGION_SIZE; + } + + /** + * Returns the whole 3d array containing the world regions used by ZoneData.java to setup zones inside the world regions + * @return + */ + public static WorldRegion[][][] getWorldRegions() + { + return _worldRegions; + } + + public static synchronized void disposeOutOfBoundsObject(WorldObject object) + { + if (object.isPlayer()) + { + object.asCreature().stopMove(object.asPlayer().getLastServerPosition()); + } + else if (object.isSummon()) + { + final Summon summon = object.asSummon(); + summon.unSummon(summon.getOwner()); + } + else if (_allObjects.remove(object.getObjectId()) != null) + { + if (object.isNpc()) + { + final Npc npc = object.asNpc(); + LOGGER.warning("Deleting npc " + object.getName() + " NPCID[" + npc.getId() + "] from invalid location X:" + object.getX() + " Y:" + object.getY() + " Z:" + object.getZ()); + npc.deleteMe(); + + final Spawn spawn = npc.getSpawn(); + if (spawn != null) + { + LOGGER.warning("Spawn location X:" + spawn.getX() + " Y:" + spawn.getY() + " Z:" + spawn.getZ() + " Heading:" + spawn.getHeading()); + } + } + else if (object.isCreature()) + { + LOGGER.warning("Deleting object " + object.getName() + " OID[" + object.getObjectId() + "] from invalid location X:" + object.getX() + " Y:" + object.getY() + " Z:" + object.getZ()); + object.asCreature().deleteMe(); + } + + if (object.getWorldRegion() != null) + { + object.getWorldRegion().removeVisibleObject(object); + } + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/WorldObject.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/WorldObject.java new file mode 100644 index 0000000..8e68f6a --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/WorldObject.java @@ -0,0 +1,1061 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.l2jmobius.gameserver.entity.actor.Attackable; +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.Npc; +import org.l2jmobius.gameserver.entity.actor.Playable; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.Summon; +import org.l2jmobius.gameserver.entity.actor.enums.creature.InstanceType; +import org.l2jmobius.gameserver.entity.actor.instance.Door; +import org.l2jmobius.gameserver.entity.actor.instance.Monster; +import org.l2jmobius.gameserver.entity.actor.instance.Pet; +import org.l2jmobius.gameserver.entity.actor.instance.Servitor; +import org.l2jmobius.gameserver.entity.instancezone.Instance; +import org.l2jmobius.gameserver.entity.item.enums.ShotType; +import org.l2jmobius.gameserver.entity.zone.ZoneId; +import org.l2jmobius.gameserver.handler.ActionClickHandler; +import org.l2jmobius.gameserver.handler.ActionShiftHandler; +import org.l2jmobius.gameserver.handler.IActionClickHandler; +import org.l2jmobius.gameserver.handler.IActionShiftHandler; +import org.l2jmobius.gameserver.interfaces.ILocational; +import org.l2jmobius.gameserver.interfaces.IPositionable; +import org.l2jmobius.gameserver.managers.IdManager; +import org.l2jmobius.gameserver.managers.InstanceManager; +import org.l2jmobius.gameserver.mechanics.events.ListenersContainer; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.serverpackets.ActionFailed; +import org.l2jmobius.gameserver.network.serverpackets.DeleteObject; +import org.l2jmobius.gameserver.network.serverpackets.ServerPacket; +import org.l2jmobius.gameserver.util.LocationUtil; + +/** + * Base class for all interactive objects. + */ +public abstract class WorldObject extends ListenersContainer implements IPositionable +{ + /** Name */ + private String _name; + /** Object ID */ + private int _objectId; + /** World Region */ + private WorldRegion _worldRegion; + /** Location */ + private final Location _location = new Location(0, 0, -10000); + /** Instance type */ + private InstanceType _instanceType; + private boolean _isSpawned; + private boolean _isInvisible; + private Map _scripts; + + public WorldObject(int objectId) + { + setInstanceType(InstanceType.WorldObject); + _objectId = objectId; + } + + /** + * Gets the instance type of object. + * @return the instance type + */ + public InstanceType getInstanceType() + { + return _instanceType; + } + + /** + * Sets the instance type. + * @param newInstanceType the instance type to set + */ + protected void setInstanceType(InstanceType newInstanceType) + { + _instanceType = newInstanceType; + } + + /** + * Verifies if object is of any given instance types. + * @param instanceTypes the instance types to verify + * @return {@code true} if object is of any given instance types, {@code false} otherwise + */ + public boolean isInstanceTypes(InstanceType... instanceTypes) + { + return _instanceType.isTypes(instanceTypes); + } + + public void onAction(Player player) + { + onAction(player, true); + } + + public void onAction(Player player, boolean interact) + { + final IActionClickHandler handler = ActionClickHandler.getInstance().getHandler(getInstanceType()); + if (handler != null) + { + handler.onAction(player, this, interact); + } + + player.sendPacket(ActionFailed.STATIC_PACKET); + } + + public void onActionShift(Player player) + { + final IActionShiftHandler handler = ActionShiftHandler.getInstance().getHandler(getInstanceType()); + if (handler != null) + { + handler.onAction(player, this, true); + } + + player.sendPacket(ActionFailed.STATIC_PACKET); + } + + public void onForcedAttack(Player player) + { + player.sendPacket(ActionFailed.STATIC_PACKET); + } + + public void onSpawn() + { + } + + public boolean decayMe() + { + _isSpawned = false; + World.removeVisibleObject(this, _worldRegion); + World.removeObject(this); + return true; + } + + public void refreshId() + { + World.removeObject(this); + IdManager.getInstance().releaseId(getObjectId()); + _objectId = IdManager.getInstance().getNextId(); + } + + public boolean spawnMe() + { + synchronized (this) + { + // Set the x,y,z position of the WorldObject spawn and update its _worldregion. + _isSpawned = true; + setWorldRegion(World.getRegion(this)); + + // Add the WorldObject spawn in the _allobjects of World. + World.addObject(this); + + // Add the WorldObject spawn to _visibleObjects and if necessary to _allplayers of its WorldRegion. + _worldRegion.addVisibleObject(this); + } + + // This can synchronize on others instances, so it's out of synchronized, to avoid deadlocks. + // Add the WorldObject spawn in the world as a visible object. + World.addVisibleObject(this, getWorldRegion()); + + onSpawn(); + + return true; + } + + public void spawnMe(int x, int y, int z) + { + synchronized (this) + { + int spawnX = x; + if (spawnX > World.WORLD_X_MAX) + { + spawnX = World.WORLD_X_MAX - 5000; + } + + if (spawnX < World.WORLD_X_MIN) + { + spawnX = World.WORLD_X_MIN + 5000; + } + + int spawnY = y; + if (spawnY > World.WORLD_Y_MAX) + { + spawnY = World.WORLD_Y_MAX - 5000; + } + + if (spawnY < World.WORLD_Y_MIN) + { + spawnY = World.WORLD_Y_MIN + 5000; + } + + // Set the x,y,z position of the WorldObject. If flagged with _isSpawned, setXYZ will automatically update world region, so avoid that. + setXYZ(spawnX, spawnY, z); + } + + // Spawn and update its _worldregion. + spawnMe(); + } + + /** + * Verify if object can be attacked. + * @return {@code true} if object can be attacked, {@code false} otherwise + */ + public boolean canBeAttacked() + { + return false; + } + + public abstract boolean isAutoAttackable(Creature attacker); + + public boolean isSpawned() + { + return _isSpawned; + } + + public void setSpawned(boolean value) + { + _isSpawned = value; + } + + public String getName() + { + return _name; + } + + public void setName(String value) + { + _name = value; + } + + public int getId() + { + return 0; + } + + public int getObjectId() + { + return _objectId; + } + + public abstract void sendInfo(Player player); + + public void sendPacket(ServerPacket packet) + { + } + + public void sendPacket(SystemMessageId id) + { + } + + /** + * Returns this object as an {@code Attackable} if it is an instance of the {@code Attackable} class.
    + * If this object does not extend the {@code Attackable} class, it returns {@code null}. + * @return the current object as an {@code Attackable} if applicable, otherwise {@code null}. + */ + public Attackable asAttackable() + { + return null; + } + + /** + * Returns this object as a {@code Creature} if it is an instance of the {@code Creature} class.
    + * If this object does not extend the {@code Creature} class, it returns {@code null}. + * @return the current object as a {@code Creature} if applicable, otherwise {@code null}. + */ + public Creature asCreature() + { + return null; + } + + /** + * Returns this object as a {@code Door} if it is an instance of the {@code Door} class.
    + * If this object does not extend the {@code Door} class, it returns {@code null}. + * @return the current object as a {@code Door} if applicable, otherwise {@code null}. + */ + public Door asDoor() + { + return null; + } + + /** + * Returns this object as a {@code Monster} if it is an instance of the {@code Monster} class.
    + * If this object does not extend the {@code Monster} class, it returns {@code null}. + * @return the current object as a {@code Monster} if applicable, otherwise {@code null}. + */ + public Monster asMonster() + { + return null; + } + + /** + * Returns this object as an {@code Npc} if it is an instance of the {@code Npc} class.
    + * If this object does not extend the {@code Npc} class, it returns {@code null}. + * @return the current object as an {@code Npc} if applicable, otherwise {@code null}. + */ + public Npc asNpc() + { + return null; + } + + /** + * Returns this object as a {@code Pet} if it is an instance of the {@code Pet} class.
    + * If this object does not extend the {@code Pet} class, it returns {@code null}. + * @return the current object as a {@code Pet} if applicable, otherwise {@code null}. + */ + public Pet asPet() + { + return null; + } + + /** + * Returns this object as a {@code Playable} if it is an instance of the {@code Playable} class.
    + * If this object does not extend the {@code Playable} class, it returns {@code null}. + * @return the current object as a {@code Playable} if applicable, otherwise {@code null}. + */ + public Playable asPlayable() + { + return null; + } + + /** + * Returns this object as a {@code Player} if it is an instance of the {@code Player} class.
    + * If this object does not extend the {@code Player} class, it returns {@code null}. + * @return the current object as a {@code Player} if applicable, otherwise {@code null}. + */ + public Player asPlayer() + { + return null; + } + + /** + * Returns this object as a {@code Servitor} if it is an instance of the {@code Servitor} class.
    + * If this object does not extend the {@code Servitor} class, it returns {@code null}. + * @return the current object as a {@code Servitor} if applicable, otherwise {@code null}. + */ + public Servitor asServitor() + { + return null; + } + + /** + * Returns this object as a {@code Summon} if it is an instance of the {@code Summon} class.
    + * If this object does not extend the {@code Summon} class, it returns {@code null}. + * @return the current object as a {@code Summon} if applicable, otherwise {@code null}. + */ + public Summon asSummon() + { + return null; + } + + /** + * Verify if object is instance of Artefact. + * @return {@code true} if object is instance of Artefact, {@code false} otherwise. + */ + public boolean isArtefact() + { + return false; + } + + /** + * Verify if object is instance of Attackable. + * @return {@code true} if object is instance of Attackable, {@code false} otherwise. + */ + public boolean isAttackable() + { + return false; + } + + /** + * Verify if object is instance of Creature. + * @return {@code true} if object is instance of Creature, {@code false} otherwise. + */ + public boolean isCreature() + { + return false; + } + + /** + * Verify if object is instance of Cubic. + * @return {@code true} if object is instance of Cubic, {@code false} otherwise. + */ + public boolean isCubic() + { + return false; + } + + /** + * Verify if object is instance of Door. + * @return {@code true} if object is instance of Door, {@code false} otherwise. + */ + public boolean isDoor() + { + return false; + } + + /** + * Verify if object is a fake player. + * @return {@code true} if object is a fake player, {@code false} otherwise. + */ + public boolean isFakePlayer() + { + return false; + } + + /** + * Verifies if this object is a fence. + * @return {@code true} if object is Fence, {@code false} otherwise. + */ + public boolean isFence() + { + return false; + } + + /** + * Verify if object is instance of Item. + * @return {@code true} if object is instance of Item, {@code false} otherwise. + */ + public boolean isItem() + { + return false; + } + + /** + * Verify if object is instance of Monster. + * @return {@code true} if object is instance of Monster, {@code false} otherwise. + */ + public boolean isMonster() + { + return false; + } + + /** + * Verify if object is instance of Npc. + * @return {@code true} if object is instance of Npc, {@code false} otherwise. + */ + public boolean isNpc() + { + return false; + } + + /** + * Verify if object is instance of Pet. + * @return {@code true} if object is instance of Pet, {@code false} otherwise. + */ + public boolean isPet() + { + return false; + } + + /** + * Verify if object is instance of Playable. + * @return {@code true} if object is instance of Playable, {@code false} otherwise. + */ + public boolean isPlayable() + { + return false; + } + + /** + * Verify if object is instance of Player. + * @return {@code true} if object is instance of Player, {@code false} otherwise. + */ + public boolean isPlayer() + { + return false; + } + + /** + * Verify if object is instance of Servitor. + * @return {@code true} if object is instance of Servitor, {@code false} otherwise. + */ + public boolean isServitor() + { + return false; + } + + /** + * Verify if object is instance of Summon. + * @return {@code true} if object is instance of Summon, {@code false} otherwise. + */ + public boolean isSummon() + { + return false; + } + + /** + * Verify if object is instance of Trap. + * @return {@code true} if object is instance of Trap, {@code false} otherwise. + */ + public boolean isTrap() + { + return false; + } + + /** + * Verifies if this object is a vehicle. + * @return {@code true} if object is Vehicle, {@code false} otherwise. + */ + public boolean isVehicle() + { + return false; + } + + /** + * Verifies if the object is a walker NPC. + * @return {@code true} if object is a walker NPC, {@code false} otherwise. + */ + public boolean isWalker() + { + return false; + } + + /** + * @return {@code true} if object Can be targeted + */ + public boolean isTargetable() + { + return true; + } + + /** + * Check if the object is in the given zone Id. + * @param zone the zone Id to check + * @return {@code true} if the object is in that zone Id + */ + public boolean isInsideZone(ZoneId zone) + { + return false; + } + + /** + * Check if current object has charged shot. + * @param type of the shot to be checked. + * @return {@code true} if the object has charged shot + */ + public boolean isChargedShot(ShotType type) + { + return false; + } + + /** + * Charging shot into the current object. + * @param type of the shot to be charged. + * @param charged + */ + public void setChargedShot(ShotType type, boolean charged) + { + } + + /** + * Try to recharge a shot. + * @param physical skill are using Soul shots. + * @param magical skill are using Spirit shots. + */ + public void rechargeShots(boolean physical, boolean magical) + { + } + + /** + * @param + * @param script + * @return + */ + public T addScript(T script) + { + if (_scripts == null) + { + // Double-checked locking + synchronized (this) + { + if (_scripts == null) + { + _scripts = new ConcurrentHashMap<>(); + } + } + } + + _scripts.put(script.getClass().getName(), script); + return script; + } + + /** + * @param + * @param script + * @return + */ + @SuppressWarnings("unchecked") + public T removeScript(Class script) + { + if (_scripts == null) + { + return null; + } + + return (T) _scripts.remove(script.getName()); + } + + /** + * @param + * @param script + * @return + */ + @SuppressWarnings("unchecked") + public T getScript(Class script) + { + if (_scripts == null) + { + return null; + } + + return (T) _scripts.get(script.getName()); + } + + public void removeStatusListener(Creature object) + { + } + + public void setXYZInvisible(int x, int y, int z) + { + int correctX = x; + if (correctX > World.WORLD_X_MAX) + { + correctX = World.WORLD_X_MAX - 5000; + } + + if (correctX < World.WORLD_X_MIN) + { + correctX = World.WORLD_X_MIN + 5000; + } + + int correctY = y; + if (correctY > World.WORLD_Y_MAX) + { + correctY = World.WORLD_Y_MAX - 5000; + } + + if (correctY < World.WORLD_Y_MIN) + { + correctY = World.WORLD_Y_MIN + 5000; + } + + setXYZ(correctX, correctY, z); + setSpawned(false); + } + + public void setLocationInvisible(ILocational loc) + { + setXYZInvisible(loc.getX(), loc.getY(), loc.getZ()); + } + + public WorldRegion getWorldRegion() + { + return _worldRegion; + } + + public void setWorldRegion(WorldRegion region) + { + if ((region == null) && (_worldRegion != null)) + { + _worldRegion.removeVisibleObject(this); + } + + _worldRegion = region; + } + + /** + * Gets the X coordinate. + * @return the X coordinate + */ + @Override + public int getX() + { + return _location.getX(); + } + + /** + * Gets the Y coordinate. + * @return the Y coordinate + */ + @Override + public int getY() + { + return _location.getY(); + } + + /** + * Gets the Z coordinate. + * @return the Z coordinate + */ + @Override + public int getZ() + { + return _location.getZ(); + } + + /** + * Gets the heading. + * @return the heading + */ + @Override + public int getHeading() + { + return _location.getHeading(); + } + + /** + * Gets the instance ID. + * @return the instance ID + */ + @Override + public int getInstanceId() + { + return _location.getInstanceId(); + } + + /** + * Check if object is inside instance world. + * @return {@code true} when object is inside any instance world, otherwise {@code false} + */ + public boolean isInInstance() + { + return _location.getInstanceId() > 0; + } + + /** + * Gets the location object. + * @return the location object + */ + @Override + public Location getLocation() + { + return _location; + } + + /** + * Sets the x, y, z coordinate. + * @param newX the X coordinate + * @param newY the Y coordinate + * @param newZ the Z coordinate + */ + @Override + public void setXYZ(int newX, int newY, int newZ) + { + _location.setXYZ(newX, newY, newZ); + + if (_isSpawned) + { + final WorldRegion newRegion = World.getRegion(this); + if ((newRegion != null) && (newRegion != _worldRegion)) + { + if (_worldRegion != null) + { + _worldRegion.removeVisibleObject(this); + } + + newRegion.addVisibleObject(this); + World.switchRegion(this, newRegion); + setWorldRegion(newRegion); + } + } + } + + /** + * Sets the x, y, z coordinate. + * @param loc the location object + */ + @Override + public void setXYZ(ILocational loc) + { + setXYZ(loc.getX(), loc.getY(), loc.getZ()); + } + + /** + * Sets heading of object. + * @param newHeading the new heading + */ + @Override + public void setHeading(int newHeading) + { + _location.setHeading(newHeading); + } + + /** + * Sets the instance ID of object.
    + * 0 - Global + * @param instanceId the ID of the instance + */ + @Override + public void setInstanceId(int instanceId) + { + final int oldInstanceId = getInstanceId(); + if ((instanceId < 0) || (oldInstanceId == instanceId)) + { + return; + } + + final Instance newI = InstanceManager.getInstance().getInstance(instanceId); + if (newI == null) + { + return; + } + + final Instance oldI = InstanceManager.getInstance().getInstance(oldInstanceId); + + if (isPlayer()) + { + final Player player = asPlayer(); + if ((oldInstanceId > 0) && (oldI != null)) + { + oldI.removePlayer(_objectId); + if (oldI.isShowTimer()) + { + sendInstanceUpdate(oldI, true); + } + } + + if (instanceId > 0) + { + newI.addPlayer(_objectId); + if (newI.isShowTimer()) + { + sendInstanceUpdate(newI, false); + } + } + + if (player.hasSummon()) + { + player.getSummon().setInstanceId(instanceId); + } + } + else if (isNpc()) + { + final Npc npc = asNpc(); + if ((oldInstanceId > 0) && (oldI != null)) + { + oldI.removeNpc(npc); + } + + if (instanceId > 0) + { + newI.addNpc(npc); + } + } + + _location.setInstanceId(instanceId); + } + + /** + * Sends an instance update for player. + * @param instance the instance to update + * @param hide if {@code true} hide the player + */ + private void sendInstanceUpdate(Instance instance, boolean hide) + { + // final int startTime = (int) ((System.currentTimeMillis() - instance.getInstanceStartTime()) / 1000); + // final int endTime = (int) ((instance.getInstanceEndTime() - instance.getInstanceStartTime()) / 1000); + // if (instance.isTimerIncrease()) + // { + // sendPacket(new ExSendUIEvent(asPlayer(), hide, true, startTime, endTime, instance.getTimerText())); + // } + // else + // { + // sendPacket(new ExSendUIEvent(asPlayer(), hide, false, endTime - startTime, 0, instance.getTimerText())); + // } + } + + /** + * Sets location of object. + * @param loc the location object + */ + @Override + public void setLocation(Location loc) + { + _location.setXYZ(loc.getX(), loc.getY(), loc.getZ()); + _location.setHeading(loc.getHeading()); + _location.setInstanceId(loc.getInstanceId()); + } + + /** + * Calculates 2D distance between this WorldObject and given x, y, z. + * @param x the X coordinate + * @param y the Y coordinate + * @param z the Z coordinate + * @return distance between object and given x, y, z. + */ + public double calculateDistance2D(int x, int y, int z) + { + final double dx = x - getX(); + final double dy = y - getY(); + return Math.sqrt((dx * dx) + (dy * dy)); + } + + /** + * Calculates the 2D distance between this WorldObject and given location. + * @param loc the location object + * @return distance between object and given location. + */ + public double calculateDistance2D(ILocational loc) + { + if (loc == null) + { + return Double.MAX_VALUE; + } + + return calculateDistance2D(loc.getX(), loc.getY(), loc.getZ()); + } + + /** + * Calculates the 3D distance between this WorldObject and given x, y, z. + * @param x the X coordinate + * @param y the Y coordinate + * @param z the Z coordinate + * @return distance between object and given x, y, z. + */ + public double calculateDistance3D(int x, int y, int z) + { + final double dx = x - getX(); + final double dy = y - getY(); + final double dz = z - getZ(); + return Math.sqrt((dx * dx) + (dy * dy) + (dz * dz)); + } + + /** + * Calculates 3D distance between this WorldObject and given location. + * @param loc the location object + * @return distance between object and given location. + */ + public double calculateDistance3D(ILocational loc) + { + if (loc == null) + { + return Double.MAX_VALUE; + } + + return calculateDistance3D(loc.getX(), loc.getY(), loc.getZ()); + } + + /** + * Calculates the angle in degrees from this object to the given object.
    + * The return value can be described as how much this object has to turn
    + * to have the given object directly in front of it. + * @param target the object to which to calculate the angle + * @return the angle this object has to turn to have the given object in front of it + */ + public double calculateDirectionTo(ILocational target) + { + return LocationUtil.calculateAngleFrom(this, target); + } + + /** + * @return {@code true} if this object is invisible, {@code false} otherwise. + */ + public boolean isInvisible() + { + return _isInvisible; + } + + /** + * Sets this object as invisible or not + * @param invisible + */ + public void setInvisible(boolean invisible) + { + _isInvisible = invisible; + + if (invisible) + { + final DeleteObject deletePacket = new DeleteObject(this); + World.forEachVisibleObject(this, Player.class, player -> + { + if (!isVisibleFor(player)) + { + player.sendPacket(deletePacket); + } + }); + } + + // Broadcast information regarding the object to those which are suppose to see. + broadcastInfo(); + } + + /** + * @param player + * @return {@code true} if player can see an invisible object if it's invisible, {@code false} otherwise. + */ + public boolean isVisibleFor(Player player) + { + return !_isInvisible || player.isGM(); + } + + /** + * Broadcasts describing info to known players. + */ + public void broadcastInfo() + { + World.forEachVisibleObject(this, Player.class, player -> + { + if (isVisibleFor(player)) + { + sendInfo(player); + } + }); + } + + public boolean isInSurroundingRegion(WorldObject worldObject) + { + if (worldObject == null) + { + return false; + } + + final WorldRegion worldRegion = worldObject.getWorldRegion(); + if (worldRegion == null) + { + return false; + } + + if (_worldRegion == null) + { + return false; + } + + return worldRegion.isSurroundingRegion(_worldRegion); + } + + @Override + public boolean equals(Object obj) + { + return (obj instanceof WorldObject) && (((WorldObject) obj).getObjectId() == getObjectId()); + } + + @Override + public String toString() + { + final StringBuilder sb = new StringBuilder(); + sb.append(getClass().getSimpleName()); + sb.append(':'); + sb.append(_name); + sb.append('['); + sb.append(_objectId); + sb.append(']'); + return sb.toString(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/WorldRegion.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/WorldRegion.java new file mode 100644 index 0000000..b6a4b8f --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/WorldRegion.java @@ -0,0 +1,435 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity; + +import java.util.Collection; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.atomic.AtomicInteger; + +import org.l2jmobius.commons.threads.ThreadPool; +import org.l2jmobius.gameserver.config.GeneralConfig; +import org.l2jmobius.gameserver.config.NpcConfig; +import org.l2jmobius.gameserver.entity.actor.Attackable; +import org.l2jmobius.gameserver.entity.actor.instance.Door; +import org.l2jmobius.gameserver.entity.actor.instance.Fence; +import org.l2jmobius.gameserver.entity.spawns.Spawn; +import org.l2jmobius.gameserver.taskmanagers.RandomAnimationTaskManager; + +public class WorldRegion +{ + /** Set containing visible objects in this world region. */ + private final Set _visibleObjects = ConcurrentHashMap.newKeySet(); + /** Set containing doors in this world region. */ + private final Set _doors = ConcurrentHashMap.newKeySet(); + /** Set containing fences in this world region. */ + private final Set _fences = ConcurrentHashMap.newKeySet(); + /** Array containing nearby regions forming this world region's effective area. */ + private WorldRegion[] _surroundingRegions; + private final ConcurrentHashMap _surroundingRegionCache = new ConcurrentHashMap<>(); + private final int _regionX; + private final int _regionY; + private final int _regionZ; + private boolean _active = GeneralConfig.GRIDS_ALWAYS_ON; + private ScheduledFuture _neighborsTask = null; + private final AtomicInteger _activeNeighbors = new AtomicInteger(); + + public WorldRegion(int regionX, int regionY, int regionZ) + { + _regionX = regionX; + _regionY = regionY; + _regionZ = regionZ; + } + + private void switchAI(boolean isOn) + { + if (_visibleObjects.isEmpty()) + { + return; + } + + if (!isOn) + { + for (WorldObject wo : _visibleObjects) + { + if (wo.isAttackable()) + { + final Attackable mob = wo.asAttackable(); + + // Set target to null and cancel attack or cast. + mob.setTarget(null); + + // Stop movement. + mob.stopMove(null); + + // Stop all active skills effects in progress on the Creature. + mob.stopAllEffects(); + + mob.clearAggroList(); + mob.getAttackByList().clear(); + + // Teleport to spawn when too far away. + final Spawn spawn = mob.getSpawn(); + if ((spawn != null) && (mob.calculateDistance2D(spawn) > NpcConfig.MAX_DRIFT_RANGE)) + { + mob.teleToLocation(spawn); + } + + // Stop the AI tasks. + if (mob.hasAI()) + { + mob.getAI().setIntentionIdle(); + mob.getAI().stopAITask(); + } + + // Stop attack task. + mob.abortAttack(); + + RandomAnimationTaskManager.getInstance().remove(mob); + } + else if (wo.isNpc()) + { + RandomAnimationTaskManager.getInstance().remove(wo.asNpc()); + } + } + } + else + { + for (WorldObject wo : _visibleObjects) + { + if (wo.isAttackable()) + { + // Start HP/MP/CP regeneration task. + wo.asAttackable().getStatus().startHpMpRegeneration(); + RandomAnimationTaskManager.getInstance().add(wo.asNpc()); + } + else if (wo.isNpc()) + { + RandomAnimationTaskManager.getInstance().add(wo.asNpc()); + } + } + } + } + + public boolean isActive() + { + return _active; + } + + public void incrementActiveNeighbors() + { + _activeNeighbors.incrementAndGet(); + } + + public void decrementActiveNeighbors() + { + _activeNeighbors.decrementAndGet(); + } + + public boolean areNeighborsActive() + { + return GeneralConfig.GRIDS_ALWAYS_ON || (_activeNeighbors.get() > 0); + } + + public boolean areNeighborsEmpty() + { + for (int i = 0; i < _surroundingRegions.length; i++) + { + final WorldRegion worldRegion = _surroundingRegions[i]; + if (worldRegion.isActive()) + { + final Collection regionObjects = worldRegion.getVisibleObjects(); + if (regionObjects.isEmpty()) + { + continue; + } + + for (WorldObject wo : regionObjects) + { + if ((wo != null) && wo.isPlayable()) + { + return false; + } + } + } + } + + return true; + } + + /** + * This function turns this region's AI ON or OFF. + * @param value + */ + public synchronized void setActive(boolean value) + { + if (_active == value) + { + return; + } + + _active = value; + + if (value) + { + for (int i = 0; i < _surroundingRegions.length; i++) + { + _surroundingRegions[i].incrementActiveNeighbors(); + } + } + else + { + for (int i = 0; i < _surroundingRegions.length; i++) + { + _surroundingRegions[i].decrementActiveNeighbors(); + } + } + + // Turn the AI on or off to match the region's activation. + switchAI(value); + } + + /** + * Immediately sets self as active and starts a timer to set neighbors as active this timer is to avoid turning on neighbors in the case when a person just teleported into a region and then teleported out immediately...there is no reason to activate all the neighbors in that case. + */ + private void startActivation() + { + // First set self to active and do self-tasks... + setActive(true); + + // If the timer to deactivate neighbors is running, cancel it. + synchronized (this) + { + if (_neighborsTask != null) + { + _neighborsTask.cancel(true); + _neighborsTask = null; + } + + // Then, set a timer to activate the neighbors. + _neighborsTask = ThreadPool.schedule(() -> + { + for (int i = 0; i < _surroundingRegions.length; i++) + { + _surroundingRegions[i].setActive(true); + } + }, 1000 * GeneralConfig.GRID_NEIGHBOR_TURNON_TIME); + } + } + + /** + * starts a timer to set neighbors (including self) as inactive this timer is to avoid turning off neighbors in the case when a person just moved out of a region that he may very soon return to. There is no reason to turn self & neighbors off in that case. + */ + private void startDeactivation() + { + // If the timer to activate neighbors is running, cancel it. + synchronized (this) + { + if (_neighborsTask != null) + { + _neighborsTask.cancel(true); + _neighborsTask = null; + } + + // Start a timer to "suggest" a deactivate to self and neighbors. + // Suggest means: first check if a neighbor has Players in it. If not, deactivate. + _neighborsTask = ThreadPool.schedule(() -> + { + for (int i = 0; i < _surroundingRegions.length; i++) + { + final WorldRegion worldRegion = _surroundingRegions[i]; + if (worldRegion.areNeighborsEmpty()) + { + worldRegion.setActive(false); + } + } + }, 1000 * GeneralConfig.GRID_NEIGHBOR_TURNOFF_TIME); + } + } + + /** + * Add the WorldObject in the WorldObjectHashSet(WorldObject) _visibleObjects containing WorldObject visible in this WorldRegion
    + * If WorldObject is a Player, Add the Player in the HashSet(Player) _allPlayable containing Player of all player in game in this WorldRegion + * @param object + */ + public synchronized void addVisibleObject(WorldObject object) + { + if (object == null) + { + return; + } + + _visibleObjects.add(object); + + if (object.isDoor()) + { + for (int i = 0; i < _surroundingRegions.length; i++) + { + _surroundingRegions[i].addDoor(object.asDoor()); + } + } + else if (object.isFence()) + { + for (int i = 0; i < _surroundingRegions.length; i++) + { + _surroundingRegions[i].addFence((Fence) object); + } + } + + // If this is the first player to enter the region, activate self and neighbors. + if (object.isPlayable() && !_active && !GeneralConfig.GRIDS_ALWAYS_ON) + { + startActivation(); + } + } + + /** + * Remove the WorldObject from the WorldObjectHashSet(WorldObject) _visibleObjects in this WorldRegion. If WorldObject is a Player, remove it from the HashSet(Player) _allPlayable of this WorldRegion + * @param object + */ + public synchronized void removeVisibleObject(WorldObject object) + { + if (object == null) + { + return; + } + + if (_visibleObjects.isEmpty()) + { + return; + } + + _visibleObjects.remove(object); + + if (object.isDoor()) + { + for (int i = 0; i < _surroundingRegions.length; i++) + { + _surroundingRegions[i].removeDoor(object.asDoor()); + } + } + else if (object.isFence()) + { + for (int i = 0; i < _surroundingRegions.length; i++) + { + _surroundingRegions[i].removeFence((Fence) object); + } + } + + if (object.isPlayable() && areNeighborsEmpty() && !GeneralConfig.GRIDS_ALWAYS_ON) + { + startDeactivation(); + } + } + + public Collection getVisibleObjects() + { + return _visibleObjects; + } + + public void addDoor(Door door) + { + _doors.add(door); + } + + private void removeDoor(Door door) + { + _doors.remove(door); + } + + public Collection getDoors() + { + return _doors; + } + + public void addFence(Fence fence) + { + _fences.add(fence); + } + + private void removeFence(Fence fence) + { + _fences.remove(fence); + } + + public Collection getFences() + { + return _fences; + } + + public void setSurroundingRegions(WorldRegion[] regions) + { + _surroundingRegions = regions; + + // Make sure that this region is always the first region to improve bulk operations when this region should be updated first. + for (int i = 0; i < _surroundingRegions.length; i++) + { + if (_surroundingRegions[i] == this) + { + final WorldRegion first = _surroundingRegions[0]; + _surroundingRegions[0] = this; + _surroundingRegions[i] = first; + } + } + } + + public WorldRegion[] getSurroundingRegions() + { + return _surroundingRegions; + } + + /** + * Checks if the given region is a surrounding region of this region. + * @param region region to check + * @return true if the region is surrounding this region + */ + public boolean isSurroundingRegion(WorldRegion region) + { + if (region == null) + { + return false; + } + + return _surroundingRegionCache.computeIfAbsent(region, r -> (_regionX >= (r.getRegionX() - 1)) && (_regionX <= (r.getRegionX() + 1)) && (_regionY >= (r.getRegionY() - 1)) && (_regionY <= (r.getRegionY() + 1)) && (_regionZ >= (r.getRegionZ() - 1)) && (_regionZ <= (r.getRegionZ() + 1))); + } + + public int getRegionX() + { + return _regionX; + } + + public int getRegionY() + { + return _regionY; + } + + public int getRegionZ() + { + return _regionZ; + } + + @Override + public String toString() + { + return "(" + _regionX + ", " + _regionY + ", " + _regionZ + ")"; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/Attackable.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/Attackable.java new file mode 100644 index 0000000..4f95312 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/Attackable.java @@ -0,0 +1,1911 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicReference; +import java.util.logging.Level; + +import org.l2jmobius.commons.threads.ThreadPool; +import org.l2jmobius.gameserver.ai.AttackableAI; +import org.l2jmobius.gameserver.ai.CreatureAI; +import org.l2jmobius.gameserver.ai.Intention; +import org.l2jmobius.gameserver.ai.SiegeGuardAI; +import org.l2jmobius.gameserver.config.GeneralConfig; +import org.l2jmobius.gameserver.config.NpcConfig; +import org.l2jmobius.gameserver.config.PlayerConfig; +import org.l2jmobius.gameserver.config.RatesConfig; +import org.l2jmobius.gameserver.config.custom.ChampionMonstersConfig; +import org.l2jmobius.gameserver.config.custom.FakePlayersConfig; +import org.l2jmobius.gameserver.config.custom.PremiumSystemConfig; +import org.l2jmobius.gameserver.data.enums.CategoryType; +import org.l2jmobius.gameserver.data.xml.ItemData; +import org.l2jmobius.gameserver.entity.WorldObject; +import org.l2jmobius.gameserver.entity.actor.enums.creature.InstanceType; +import org.l2jmobius.gameserver.entity.actor.enums.creature.Race; +import org.l2jmobius.gameserver.entity.actor.enums.creature.Team; +import org.l2jmobius.gameserver.entity.actor.enums.npc.DropType; +import org.l2jmobius.gameserver.entity.actor.holders.npc.AbsorberInfo; +import org.l2jmobius.gameserver.entity.actor.holders.npc.AggroInfo; +import org.l2jmobius.gameserver.entity.actor.holders.npc.DamageDoneInfo; +import org.l2jmobius.gameserver.entity.actor.instance.GrandBoss; +import org.l2jmobius.gameserver.entity.actor.instance.Monster; +import org.l2jmobius.gameserver.entity.actor.status.AttackableStatus; +import org.l2jmobius.gameserver.entity.actor.tasks.attackable.CommandChannelTimer; +import org.l2jmobius.gameserver.entity.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.entity.groups.CommandChannel; +import org.l2jmobius.gameserver.entity.groups.Party; +import org.l2jmobius.gameserver.entity.item.ItemTemplate; +import org.l2jmobius.gameserver.entity.item.holders.ItemHolder; +import org.l2jmobius.gameserver.entity.item.instance.Item; +import org.l2jmobius.gameserver.managers.CursedWeaponsManager; +import org.l2jmobius.gameserver.managers.EventDropManager; +import org.l2jmobius.gameserver.managers.PcCafePointsManager; +import org.l2jmobius.gameserver.managers.WalkingManager; +import org.l2jmobius.gameserver.mechanics.events.EventDispatcher; +import org.l2jmobius.gameserver.mechanics.events.EventType; +import org.l2jmobius.gameserver.mechanics.events.holders.actor.npc.attackable.OnAttackableAggroRangeEnter; +import org.l2jmobius.gameserver.mechanics.events.holders.actor.npc.attackable.OnAttackableAttack; +import org.l2jmobius.gameserver.mechanics.events.holders.actor.npc.attackable.OnAttackableKill; +import org.l2jmobius.gameserver.mechanics.siege.manor.Seed; +import org.l2jmobius.gameserver.mechanics.skill.Skill; +import org.l2jmobius.gameserver.mechanics.skill.holders.SkillHolder; +import org.l2jmobius.gameserver.mechanics.stats.Stat; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.enums.ChatType; +import org.l2jmobius.gameserver.network.serverpackets.CreatureSay; +import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; +import org.l2jmobius.gameserver.taskmanagers.DecayTaskManager; +import org.l2jmobius.gameserver.util.StatSet; + +public class Attackable extends Npc +{ + // Raid + private boolean _isRaid = false; + private boolean _isRaidMinion = false; + + // + private boolean _champion = false; + private final Map _aggroList = new ConcurrentHashMap<>(); + private boolean _canReturnToSpawnPoint = true; + private boolean _seeThroughSilentMove = false; + + // Manor + private boolean _seeded = false; + private Seed _seed = null; + private int _seederObjId = 0; + private final AtomicReference _harvestItem = new AtomicReference<>(); + + // Spoil + private int _spoilerObjectId; + private final AtomicReference> _sweepItems = new AtomicReference<>(); + + // Over-hit + private boolean _overhit; + private double _overhitDamage; + private Creature _overhitAttacker; + + // Command channel + private CommandChannel _firstCommandChannelAttacked = null; + private CommandChannelTimer _commandChannelTimer = null; + private long _commandChannelLastAttack = 0; + + // Soul crystal + private boolean _absorbed; + private final Map _absorbersList = new ConcurrentHashMap<>(); + + // Misc + private boolean _mustGiveExpSp; + private long _spawnTime = System.currentTimeMillis(); + protected int _onKillDelay = 2500; // L2J uses 5000 + + /** + * Creates an attackable NPC. + * @param template the attackable NPC template + */ + public Attackable(NpcTemplate template) + { + super(template); + setInstanceType(InstanceType.Attackable); + setInvul(false); + _mustGiveExpSp = true; + } + + @Override + public AttackableStatus getStatus() + { + return (AttackableStatus) super.getStatus(); + } + + @Override + public void initCharStatus() + { + setStatus(new AttackableStatus(this)); + } + + @Override + protected CreatureAI initAI() + { + return new AttackableAI(this); + } + + public Map getAggroList() + { + return _aggroList; + } + + public boolean canReturnToSpawnPoint() + { + return _canReturnToSpawnPoint; + } + + public void setCanReturnToSpawnPoint(boolean value) + { + _canReturnToSpawnPoint = value; + } + + public boolean canSeeThroughSilentMove() + { + return _seeThroughSilentMove; + } + + public void setSeeThroughSilentMove(boolean value) + { + _seeThroughSilentMove = value; + } + + /** + * Use the skill if minimum checks are pass. + * @param skill the skill + */ + public void useMagic(Skill skill) + { + if ((skill == null) || isAlikeDead() || skill.isPassive() || isCastingNow() || isSkillDisabled(skill)) + { + return; + } + + if ((getCurrentMp() < (getStat().getMpConsume(skill) + getStat().getMpInitialConsume(skill))) || (getCurrentHp() <= skill.getHpConsume())) + { + return; + } + + if (!skill.isStatic()) + { + if (skill.isMagic()) + { + if (isMuted()) + { + return; + } + } + else if (isPhysicalMuted()) + { + return; + } + } + + final WorldObject target = skill.getFirstOfTargetList(this); + if (target != null) + { + getAI().setIntentionCast(skill, target); + } + } + + /** + * Reduce the current HP of the Attackable. + * @param damage The HP decrease value + * @param attacker The Creature who attacks + */ + @Override + public void reduceCurrentHp(double damage, Creature attacker, Skill skill) + { + reduceCurrentHp(damage, attacker, true, false, skill); + } + + /** + * Reduce the current HP of the Attackable, update its _aggroList and launch the doDie Task if necessary. + * @param damage The HP decrease value + * @param attacker The Creature who attacks + * @param awake The awake state (If True : stop sleeping) + * @param isDOT + * @param skill + */ + @Override + public void reduceCurrentHp(double damage, Creature attacker, boolean awake, boolean isDOT, Skill skill) + { + if (_isRaid && !isMinion() && (attacker != null) && (attacker.getParty() != null) && attacker.getParty().isInCommandChannel() && attacker.getParty().getCommandChannel().meetRaidWarCondition(this)) + { + if (_firstCommandChannelAttacked == null) // looting right isn't set + { + synchronized (this) + { + if (_firstCommandChannelAttacked == null) + { + _firstCommandChannelAttacked = attacker.getParty().getCommandChannel(); + if (_firstCommandChannelAttacked != null) + { + _commandChannelTimer = new CommandChannelTimer(this); + _commandChannelLastAttack = System.currentTimeMillis(); + ThreadPool.schedule(_commandChannelTimer, 10000); // check for last attack + _firstCommandChannelAttacked.broadcastPacket(new CreatureSay(null, ChatType.PARTYROOM_ALL, "", "You have looting rights!")); // TODO: retail msg + } + } + } + } + else if (attacker.getParty().getCommandChannel().equals(_firstCommandChannelAttacked)) // is in same channel + { + _commandChannelLastAttack = System.currentTimeMillis(); // update last attack time + } + } + + // Add damage and hate to the attacker AggroInfo of the Attackable _aggroList. + if (attacker != null) + { + addDamage(attacker, (int) damage, skill); + } + + // If this Attackable is a Monster and it has spawned minions, call its minions to battle. + if (isMonster()) + { + Monster master = asMonster(); + + if (master.hasMinions()) + { + master.getMinionList().onAssist(this, attacker); + } + + master = master.getLeader(); + if ((master != null) && master.hasMinions()) + { + master.getMinionList().onAssist(this, attacker); + } + } + + // Reduce the current HP of the Attackable and launch the doDie Task if necessary. + super.reduceCurrentHp(damage, attacker, awake, isDOT, skill); + } + + public synchronized void setMustRewardExpSp(boolean value) + { + _mustGiveExpSp = value; + } + + public synchronized boolean getMustRewardExpSP() + { + return _mustGiveExpSp && !isFakePlayer(); + } + + /** + * Kill the Attackable (the corpse disappeared after 7 seconds), distribute rewards (EXP, SP, Drops...) and notify Quest Engine.
    + * Actions:
    + * Distribute Exp and SP rewards to Player (including Summon owner) that hit the Attackable and to their Party members
    + * Notify the Quest Engine of the Attackable death if necessary.
    + * Kill the Npc (the corpse disappeared after 7 seconds)
    + * Caution: This method DOESN'T GIVE rewards to Pet. + * @param killer The Creature that has killed the Attackable + */ + @Override + public boolean doDie(Creature killer) + { + // Kill the Npc (the corpse disappeared after 7 seconds). + if (!super.doDie(killer)) + { + return false; + } + + // Delayed notification. + if (killer != null) + { + final Player player = killer.asPlayer(); + if ((player != null) && EventDispatcher.getInstance().hasListener(EventType.ON_ATTACKABLE_KILL, this)) + { + EventDispatcher.getInstance().notifyEventAsyncDelayed(new OnAttackableKill(player, this, killer.isSummon()), this, _onKillDelay); + } + } + + // Notify to minions if there are. + if (isMonster()) + { + final Monster mob = asMonster(); + final Monster leader = mob.getLeader(); + if ((leader != null) && leader.hasMinions()) + { + final int respawnTime = NpcConfig.MINIONS_RESPAWN_TIME.containsKey(getId()) ? NpcConfig.MINIONS_RESPAWN_TIME.get(getId()) * 1000 : -1; + leader.getMinionList().onMinionDie(mob, respawnTime); + } + + if (mob.hasMinions()) + { + mob.getMinionList().onMasterDie(false); + } + } + + return true; + } + + private static class PartyContainer + { + public Party party; + public long damage; + + public PartyContainer(Party party, long damage) + { + this.party = party; + this.damage = damage; + } + } + + /** + * Distribute Exp and SP rewards to Player (including Summon owner) that hit the Attackable and to their Party members.
    + * Actions:
    + * Get the Player owner of the Servitor (if necessary) and Party in progress.
    + * Calculate the Experience and SP rewards in function of the level difference.
    + * Add Exp and SP rewards to Player (including Summon penalty) and to Party members in the known area of the last attacker.
    + * Caution : This method DOESN'T GIVE rewards to Pet. + * @param lastAttacker The Creature that has killed the Attackable + */ + @Override + protected void calculateRewards(Creature lastAttacker) + { + try + { + if (_aggroList.isEmpty()) + { + return; + } + + // NOTE: Concurrent-safe map is used because while iterating to verify all conditions sometimes an entry must be removed. + final Map rewards = new ConcurrentHashMap<>(); + + Player maxDealer = null; + long maxDamage = 0; + long totalDamage = 0; + + // While Iterating over This Map Removing Object is Not Allowed. + // Go through the _aggroList of the Attackable. + for (AggroInfo info : _aggroList.values()) + { + if (info == null) + { + continue; + } + + // Get the Creature corresponding to this attacker. + final Player attacker = info.getAttacker().asPlayer(); + if (attacker == null) + { + continue; + } + + // Get damages done by this attacker. + final long damage = info.getDamage(); + + // Prevent unwanted behavior + if (damage > 1) + { + // Check if damage dealer isn't too far from this (killed monster). + if (calculateDistance3D(attacker) > PlayerConfig.ALT_PARTY_RANGE) + { + continue; + } + + totalDamage += damage; + + // Calculate real damages (Summoners should get own damage plus summon's damage). + final DamageDoneInfo reward = rewards.computeIfAbsent(attacker, DamageDoneInfo::new); + reward.addDamage(damage); + + if (reward.getDamage() > maxDamage) + { + maxDealer = attacker; + maxDamage = reward.getDamage(); + } + } + } + + final List damagingParties = new ArrayList<>(); + for (AggroInfo info : _aggroList.values()) + { + final Creature attacker = info.getAttacker(); + if (attacker == null) + { + continue; + } + + long totalMemberDamage = 0; + final Party party = attacker.getParty(); + if (party == null) + { + continue; + } + + PartyContainer found = null; + for (int i = 0, damagingPartiesSize = damagingParties.size(); i < damagingPartiesSize; i++) + { + final PartyContainer p = damagingParties.get(i); + if (p.party == party) + { + found = p; + break; + } + } + + final PartyContainer container = found != null ? found : new PartyContainer(party, 0L); + final List members = party.getMembers(); + for (Player e : members) + { + final AggroInfo memberAggro = _aggroList.get(e); + if (memberAggro == null) + { + continue; + } + + if (memberAggro.getDamage() > 1) + { + totalMemberDamage += memberAggro.getDamage(); + } + } + + container.damage = totalMemberDamage; + + if (found == null) + { + damagingParties.add(container); + } + } + + final PartyContainer mostDamageParty; + damagingParties.sort(Comparator.comparingLong(c -> -c.damage)); + mostDamageParty = !damagingParties.isEmpty() ? damagingParties.get(0) : null; + + // Manage Base, Quests and Sweep drops of the Attackable + if ((mostDamageParty != null) && (mostDamageParty.damage > maxDamage)) + { + Player leader = mostDamageParty.party.getLeader(); + doItemDrop(leader); + EventDropManager.getInstance().doEventDrop(leader, this); + } + else + { + doItemDrop((maxDealer != null) && maxDealer.isOnline() ? maxDealer : lastAttacker); + EventDropManager.getInstance().doEventDrop(lastAttacker, this); + } + + if (!getMustRewardExpSP()) + { + return; + } + + if (!rewards.isEmpty()) + { + for (DamageDoneInfo reward : rewards.values()) + { + if (reward == null) + { + continue; + } + + // Attacker to be rewarded. + final Player attacker = reward.getAttacker(); + + // Total amount of damage done. + final long damage = reward.getDamage(); + + // Get party + final Party attackerParty = attacker.getParty(); + + // Penalty applied to the attacker's XP. + // If this attacker have servitor, get Exp Penalty applied for the servitor. + final float penalty = attacker.hasServitor() ? attacker.getSummon().asServitor().getExpMultiplier() : 1; + + // If there's NO party in progress. + if (attackerParty == null) + { + // Calculate Exp and SP rewards. + if (isInSurroundingRegion(attacker)) + { + // Calculate the difference of level between this attacker (player or servitor owner) and the Attackable. + // mob = 24, atk = 10, diff = -14 (full xp) + // mob = 24, atk = 28, diff = 4 (some xp) + // mob = 24, atk = 50, diff = 26 (no xp) + final double[] expSp = calculateExpAndSp(attacker.getLevel(), damage, totalDamage); + double exp = expSp[0]; + double sp = expSp[1]; + + if (ChampionMonstersConfig.CHAMPION_ENABLE && _champion) + { + exp *= ChampionMonstersConfig.CHAMPION_REWARDS_EXP_SP; + sp *= ChampionMonstersConfig.CHAMPION_REWARDS_EXP_SP; + } + + exp *= penalty; + + // Check for an over-hit enabled strike. + final Creature overhitAttacker = _overhitAttacker; + if (_overhit && (overhitAttacker != null)) + { + final Player player = overhitAttacker.asPlayer(); + if ((player != null) && (attacker == player)) + { + attacker.sendPacket(SystemMessageId.OVER_HIT); + exp += calculateOverhitExp(exp); + } + } + + // Distribute the Exp and SP between the Player and its Summon. + if (!attacker.isDead()) + { + long addExp = Math.round(attacker.calcStat(Stat.EXPSP_RATE, exp, null, null)); + int addSp = (int) attacker.calcStat(Stat.EXPSP_RATE, sp, null, null); + + // Premium rates + if (attacker.hasPremiumStatus()) + { + addExp *= PremiumSystemConfig.PREMIUM_RATE_XP; + addSp *= PremiumSystemConfig.PREMIUM_RATE_SP; + } + + attacker.addExpAndSp(addExp, addSp, useVitalityRate()); + if ((addExp > 0) && useVitalityRate()) + { + attacker.updateVitalityPoints(getVitalityPoints(attacker.getLevel(), damage), true, false); + PcCafePointsManager.getInstance().givePcCafePoint(attacker, exp); + } + } + } + } + else + { + // Share with party members. + long partyDmg = 0; + double partyMul = 1; + int partyLvl = 0; + + // Get all Creature that can be rewarded in the party. + final List rewardedMembers = new ArrayList<>(); + + // Go through all Player in the party. + final List groupMembers = attackerParty.isInCommandChannel() ? attackerParty.getCommandChannel().getMembers() : attackerParty.getMembers(); + for (Player partyPlayer : groupMembers) + { + if ((partyPlayer == null) || partyPlayer.isDead()) + { + continue; + } + + // Get the RewardInfo of this Player from Attackable rewards. + final DamageDoneInfo reward2 = rewards.get(partyPlayer); + + // If the Player is in the Attackable rewards add its damages to party damages. + if (reward2 != null) + { + if (calculateDistance3D(partyPlayer) < PlayerConfig.ALT_PARTY_RANGE) + { + partyDmg += reward2.getDamage(); // Add Player damages to party damages. + rewardedMembers.add(partyPlayer); + + if (partyPlayer.getLevel() > partyLvl) + { + if (attackerParty.isInCommandChannel()) + { + partyLvl = attackerParty.getCommandChannel().getLevel(); + } + else + { + partyLvl = partyPlayer.getLevel(); + } + } + } + + rewards.remove(partyPlayer); // Remove the Player from the Attackable rewards. + } + else if (calculateDistance3D(partyPlayer) < PlayerConfig.ALT_PARTY_RANGE) + { + rewardedMembers.add(partyPlayer); + if (partyPlayer.getLevel() > partyLvl) + { + if (attackerParty.isInCommandChannel()) + { + partyLvl = attackerParty.getCommandChannel().getLevel(); + } + else + { + partyLvl = partyPlayer.getLevel(); + } + } + } + } + + // If the party didn't killed this Attackable alone. + if (partyDmg < totalDamage) + { + partyMul = ((double) partyDmg / totalDamage); + } + + // Calculate Exp and SP rewards. + final double[] expSp = calculateExpAndSp(partyLvl, partyDmg, totalDamage); + double exp = expSp[0]; + double sp = expSp[1]; + + if (ChampionMonstersConfig.CHAMPION_ENABLE && _champion) + { + exp *= ChampionMonstersConfig.CHAMPION_REWARDS_EXP_SP; + sp *= ChampionMonstersConfig.CHAMPION_REWARDS_EXP_SP; + } + + exp *= partyMul; + sp *= partyMul; + + // Check for an over-hit enabled strike. + // (When in party, the over-hit exp bonus is given to the whole party and splitted proportionally through the party members). + final Creature overhitAttacker = _overhitAttacker; + if (_overhit && (overhitAttacker != null)) + { + final Player player = overhitAttacker.asPlayer(); + if ((player != null) && (attacker == player)) + { + attacker.sendPacket(SystemMessageId.OVER_HIT); + exp += calculateOverhitExp(exp); + } + } + + // Distribute Experience and SP rewards to Player Party members in the known area of the last attacker. + if (partyDmg > 0) + { + attackerParty.distributeXpAndSp(exp, sp, rewardedMembers, partyLvl, partyDmg, this); + } + } + } + } + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "", e); + } + } + + @Override + public void addAttackerToAttackByList(Creature creature) + { + if ((creature == null) || (creature == this)) + { + return; + } + + final Set attackByList = getAttackByList(); + if (attackByList.contains(creature)) + { + return; + } + + attackByList.add(creature); + } + + public Creature getMainDamageDealer() + { + if (_aggroList.isEmpty()) + { + return null; + } + + long damage = 0; + Creature damageDealer = null; + for (AggroInfo info : _aggroList.values()) + { + if ((info != null) && (info.getDamage() > damage) && (calculateDistance3D(info.getAttacker()) < PlayerConfig.ALT_PARTY_RANGE)) + { + damage = info.getDamage(); + damageDealer = info.getAttacker(); + } + } + + return damageDealer; + } + + /** + * Add damage and hate to the attacker AggroInfo of the Attackable _aggroList. + * @param attacker The Creature that gave damages to this Attackable + * @param damage The number of damages given by the attacker Creature + * @param skill + */ + public void addDamage(Creature attacker, int damage, Skill skill) + { + if (attacker == null) + { + return; + } + + // Notify the Attackable AI with ATTACKED. + if (!isDead()) + { + try + { + // If monster is on walk - stop it. + if (isWalker() && !isCoreAIDisabled() && WalkingManager.getInstance().isOnWalk(this)) + { + WalkingManager.getInstance().stopMoving(this, false, true); + } + + getAI().notifyActionAttacked(attacker); + + // Calculate the amount of hate this attackable receives from this attack. + final long hateValue = (long) ((((long) damage * 100) / (getLevel() + 7)) * getHateRatio(attacker)); + addDamageHate(attacker, damage, (int) hateValue); + + final Player player = attacker.asPlayer(); + if ((player != null) && EventDispatcher.getInstance().hasListener(EventType.ON_ATTACKABLE_ATTACK, this)) + { + EventDispatcher.getInstance().notifyEventAsync(new OnAttackableAttack(player, this, damage, skill, attacker.isSummon()), this); + } + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "", e); + } + } + } + + /** + * Calculates the hate multiplier declared by the AI parameters SetHateGroup, SetHateOccupation and SetHateRace,
    + * which increase the hate received from players of a matching class category, class or race. + * @param attacker the creature generating hate + * @return the hate multiplier for this attacker + */ + public double getHateRatio(Creature attacker) + { + if (!attacker.isPlayer()) + { + return 1; + } + + final StatSet params = getTemplate().getParameters(); + if (params.isEmpty()) + { + return 1; + } + + final Player player = attacker.asPlayer(); + int ratio = 0; + final CategoryType group = params.getEnum("SetHateGroup", CategoryType.class, null); + if ((group != null) && player.isInCategory(group)) + { + ratio += params.getInt("SetHateGroupRatio", 0); + } + + if (player.getPlayerClass().getId() == params.getInt("SetHateOccupation", -1)) + { + ratio += params.getInt("SetHateOccupationRatio", 0); + } + + if (player.getRace() == params.getEnum("SetHateRace", Race.class, null)) + { + ratio += params.getInt("SetHateRaceRatio", 0); + } + + return 1 + (ratio / 100.0); + } + + /** + * Adds damage and hate to the attacker aggression list for this character. + * @param attacker The Creature that gave damages to this Attackable + * @param damage The number of damages given by the attacker Creature + * @param aggroValue The hate (=damage) given by the attacker Creature + */ + public void addDamageHate(Creature attacker, long damage, long aggroValue) + { + if ((attacker == null) || (attacker == this)) + { + return; + } + + // Check if fake players should aggro each other. + if (isFakePlayer() && !FakePlayersConfig.FAKE_PLAYER_AGGRO_FPC && attacker.isFakePlayer()) + { + return; + } + + // Get the AggroInfo of the attacker Creature from the _aggroList of the Attackable. + final AggroInfo ai = _aggroList.computeIfAbsent(attacker, AggroInfo::new); + ai.addDamage(damage); + + // Traps does not cause aggro + // Making this hack because not possible to determine if damage made by trap. + // So just check for triggered trap here. + long aggro = aggroValue; + final Player targetPlayer = attacker.asPlayer(); + if ((targetPlayer == null) || (targetPlayer.getTrap() == null) || !targetPlayer.getTrap().isTriggered()) + { + ai.addHate(aggro); + } + + if ((targetPlayer != null) && (aggro == 0)) + { + addDamageHate(attacker, 0, 1); + + // Set the intention to the Attackable to ACTIVE. + if (getAI().getIntention() == Intention.IDLE) + { + getAI().setIntentionActive(); + } + + // Notify to scripts + if (EventDispatcher.getInstance().hasListener(EventType.ON_ATTACKABLE_AGGRO_RANGE_ENTER, this)) + { + EventDispatcher.getInstance().notifyEventAsync(new OnAttackableAggroRangeEnter(this, targetPlayer, attacker.isSummon()), this); + } + } + else if ((targetPlayer == null) && (aggro == 0)) + { + aggro = 1; + ai.addHate(1); + } + + // Set the intention to the Attackable to ACTIVE. + if ((aggro != 0) && (getAI().getIntention() == Intention.IDLE)) + { + getAI().setIntentionActive(); + } + } + + public void reduceHate(Creature target, long amount) + { + if (getAI() instanceof SiegeGuardAI) + { + // TODO: this just prevents error until siege guards are handled properly + stopHating(target); + setTarget(null); + getAI().setIntentionIdle(); + return; + } + + if (target == null) // whole aggrolist + { + final Creature mostHated = getMostHated(); + if (mostHated == null) // makes target passive for a moment more + { + ((AttackableAI) getAI()).setGlobalAggro(-25); + return; + } + + for (AggroInfo ai : _aggroList.values()) + { + ai.addHate(amount); + } + + if (getHating(mostHated) >= 0) + { + ((AttackableAI) getAI()).setGlobalAggro(-25); + clearAggroList(); + getAI().setIntentionActive(); + if (!isFakePlayer()) + { + setWalking(); + } + } + return; + } + + final AggroInfo ai = _aggroList.get(target); + if (ai == null) + { + return; + } + + ai.addHate(amount); + if ((ai.getHate() >= 0) && (getMostHated() == null)) + { + ((AttackableAI) getAI()).setGlobalAggro(-25); + clearAggroList(); + getAI().setIntentionActive(); + if (!isFakePlayer()) + { + setWalking(); + } + } + } + + /** + * Clears _aggroList hate of the Creature without removing from the list. + * @param target + */ + public void stopHating(Creature target) + { + if (target == null) + { + return; + } + + final AggroInfo ai = _aggroList.get(target); + if (ai != null) + { + ai.stopHate(); + } + } + + /** + * @return the most hated Creature of the Attackable _aggroList. + */ + public Creature getMostHated() + { + if (_aggroList.isEmpty() || isAlikeDead()) + { + return null; + } + + Creature mostHated = null; + long maxHate = 0; + + // While Interacting over This Map Removing Object is Not Allowed. + // Go through the aggroList of the Attackable. + for (AggroInfo ai : _aggroList.values()) + { + if (ai == null) + { + continue; + } + + if (ai.checkHate(this) > maxHate) + { + mostHated = ai.getAttacker(); + maxHate = ai.getHate(); + } + } + + return mostHated; + } + + /** + * @return the 2 most hated Creature of the Attackable _aggroList. + */ + public List get2MostHated() + { + if (_aggroList.isEmpty() || isAlikeDead()) + { + return null; + } + + Creature mostHated = null; + Creature secondMostHated = null; + long maxHate = 0; + final List result = new ArrayList<>(); + + // While iterating over this map removing objects is not allowed. + // Go through the aggroList of the Attackable. + for (AggroInfo ai : _aggroList.values()) + { + if (ai == null) + { + continue; + } + + if (ai.checkHate(this) > maxHate) + { + secondMostHated = mostHated; + mostHated = ai.getAttacker(); + maxHate = ai.getHate(); + } + } + + result.add(mostHated); + + if (getAttackByList().contains(secondMostHated)) + { + result.add(secondMostHated); + } + else + { + result.add(null); + } + + return result; + } + + public List getHateList() + { + if (_aggroList.isEmpty() || isAlikeDead()) + { + return null; + } + + final List result = new ArrayList<>(); + for (AggroInfo ai : _aggroList.values()) + { + if (ai == null) + { + continue; + } + + ai.checkHate(this); + + result.add(ai.getAttacker()); + } + + return result; + } + + /** + * @param target The Creature whose hate level must be returned + * @return the hate level of the Attackable against this Creature contained in _aggroList. + */ + public long getHating(Creature target) + { + if (_aggroList.isEmpty() || (target == null)) + { + return 0; + } + + final AggroInfo ai = _aggroList.get(target); + if (ai == null) + { + return 0; + } + + if (ai.getAttacker().isPlayer()) + { + final Player act = ai.getAttacker().asPlayer(); + if (act.isInvisible() || act.isInvul() || act.isSpawnProtected()) + { + // Remove Object Should Use This Method and Can be Blocked While Interacting. + _aggroList.remove(target); + return 0; + } + } + + if (!ai.getAttacker().isSpawned() || ai.getAttacker().isInvisible()) + { + _aggroList.remove(target); + return 0; + } + + if (ai.getAttacker().isAlikeDead()) + { + ai.stopHate(); + return 0; + } + + return ai.getHate(); + } + + public void doItemDrop(Creature mainDamageDealer) + { + doItemDrop(getTemplate(), mainDamageDealer); + } + + /** + * Manage Base, Quests and Special Events drops of Attackable (called by calculateRewards).
    + * Concept:
    + * During a Special Event all Attackable can drop extra Items.
    + * Those extra Items are defined in the table allNpcDateDrops of the EventDroplist.
    + * Each Special Event has a start and end date to stop to drop extra Items automatically.
    + * Actions:
    + * Manage drop of Special Events created by GM for a defined period.
    + * Get all possible drops of this Attackable from NpcTemplate and add it Quest drops.
    + * For each possible drops (base + quests), calculate which one must be dropped (random).
    + * Get each Item quantity dropped (random).
    + * Create this or these Item corresponding to each Item Identifier dropped.
    + * If the autoLoot mode is actif and if the Creature that has killed the Attackable is a Player, Give the item(s) to the Player that has killed the Attackable.
    + * If the autoLoot mode isn't actif or if the Creature that has killed the Attackable is not a Player, add this or these item(s) in the world as a visible object at the position where mob was last. + * @param npcTemplate + * @param mainDamageDealer + */ + public void doItemDrop(NpcTemplate npcTemplate, Creature mainDamageDealer) + { + if (mainDamageDealer == null) + { + return; + } + + final Player player = mainDamageDealer.asPlayer(); + + // Don't drop anything if the last attacker or owner isn't Player. + if (player == null) + { + // Unless its a fake player and they can drop items. + if (mainDamageDealer.isFakePlayer() && FakePlayersConfig.FAKE_PLAYER_CAN_DROP_ITEMS) + { + final Collection deathItems = npcTemplate.calculateDrops(DropType.DROP, this, mainDamageDealer); + if (deathItems != null) + { + for (ItemHolder drop : deathItems) + { + final ItemTemplate item = ItemData.getInstance().getTemplate(drop.getId()); + + // Check if the autoLoot mode is active. + if (PlayerConfig.AUTO_LOOT_ITEM_IDS.contains(item.getId()) || isFlying() || (!item.hasExImmediateEffect() && ((!_isRaid && PlayerConfig.AUTO_LOOT) || (_isRaid && PlayerConfig.AUTO_LOOT_RAIDS)))) + { + // Do nothing. + } + else if (PlayerConfig.AUTO_LOOT_HERBS && item.hasExImmediateEffect()) + { + for (SkillHolder skillHolder : item.getSkills()) + { + doSimultaneousCast(skillHolder.getSkill()); + } + + mainDamageDealer.broadcastInfo(); // ? check if this is necessary + } + else + { + final Item droppedItem = dropItem(mainDamageDealer, drop); // Drop the item on the ground. + if (FakePlayersConfig.FAKE_PLAYER_CAN_PICKUP) + { + mainDamageDealer.getFakePlayerDrops().add(droppedItem); + } + } + } + + deathItems.clear(); + } + } + return; + } + + CursedWeaponsManager.getInstance().checkDrop(this, player); + + if (isSpoiled()) + { + _sweepItems.set(npcTemplate.calculateDrops(DropType.SPOIL, this, player)); + } + + final Collection deathItems = npcTemplate.calculateDrops(DropType.DROP, this, player); + if (deathItems != null) + { + for (ItemHolder drop : deathItems) + { + final ItemTemplate item = ItemData.getInstance().getTemplate(drop.getId()); + + // Check if the autoLoot mode is active. + if (PlayerConfig.AUTO_LOOT_ITEM_IDS.contains(item.getId()) || isFlying() || (!item.hasExImmediateEffect() && ((!_isRaid && PlayerConfig.AUTO_LOOT) || (_isRaid && PlayerConfig.AUTO_LOOT_RAIDS))) || (item.hasExImmediateEffect() && PlayerConfig.AUTO_LOOT_HERBS)) + { + player.doAutoLoot(this, drop); // Give the item(s) to the Player that has killed the Attackable. + } + else + { + dropItem(player, drop); // Drop the item on the ground. + } + + // Broadcast message if RaidBoss was defeated. + if (_isRaid && !_isRaidMinion && (drop.getCount() > 0)) + { + final SystemMessage sm = new SystemMessage(SystemMessageId.S1_DIED_AND_DROPPED_S3_S2); + sm.addString(getName()); + sm.addItemName(item); + sm.addInt(drop.getCount()); + broadcastPacket(sm); + } + } + + deathItems.clear(); + } + } + + /** + * @return the active weapon of this Attackable (= null). + */ + public Item getActiveWeapon() + { + return null; + } + + /** + * Verifies if the creature is in the aggro list. + * @param creature the creature + * @return {@code true} if the creature is in the aggro list, {@code false} otherwise + */ + public boolean isInAggroList(Creature creature) + { + return (creature != null) && _aggroList.containsKey(creature); + } + + /** + * Clear the _aggroList of the Attackable. + */ + public void clearAggroList() + { + _aggroList.clear(); + + // clear overhit values + _overhit = false; + _overhitDamage = 0; + _overhitAttacker = null; + } + + /** + * @return {@code true} if there is a loot to sweep, {@code false} otherwise. + */ + @Override + public boolean isSweepActive() + { + return _sweepItems.get() != null; + } + + /** + * @return a copy of dummy items for the spoil loot. + */ + public List getSpoilLootItems() + { + final Collection sweepItems = _sweepItems.get(); + final List lootItems = new ArrayList<>(); + if (sweepItems != null) + { + for (ItemHolder item : sweepItems) + { + lootItems.add(ItemData.getInstance().getTemplate(item.getId())); + } + } + + return lootItems; + } + + /** + * @return table containing all Item that can be spoiled. + */ + public Collection takeSweep() + { + return _sweepItems.getAndSet(null); + } + + /** + * @return table containing all Item that can be harvested. + */ + public ItemHolder takeHarvest() + { + return _harvestItem.getAndSet(null); + } + + /** + * Checks if the corpse is too old. + * @param attacker the player to validate + * @param remainingTime the time to check + * @param sendMessage if {@code true} will send a message of corpse too old + * @return {@code true} if the corpse is too old + */ + public boolean isOldCorpse(Player attacker, int remainingTime, boolean sendMessage) + { + if (!isDead() || (DecayTaskManager.getInstance().getRemainingTime(this) >= remainingTime)) + { + return false; + } + + if (sendMessage && (attacker != null)) + { + attacker.sendPacket(SystemMessageId.THE_CORPSE_IS_TOO_OLD_THE_SKILL_CANNOT_BE_USED); + } + + return true; + } + + /** + * @param sweeper the player to validate. + * @param sendMessage sendMessage if {@code true} will send a message of sweep not allowed. + * @return {@code true} if is the spoiler or is in the spoiler party. + */ + public boolean checkSpoilOwner(Player sweeper, boolean sendMessage) + { + if ((sweeper.getObjectId() == _spoilerObjectId) || sweeper.isInLooterParty(_spoilerObjectId)) + { + return true; + } + + if (sendMessage) + { + sweeper.sendPacket(SystemMessageId.THERE_ARE_NO_PRIORITY_RIGHTS_ON_A_SWEEPER); + } + + return false; + } + + /** + * Set the over-hit flag on the Attackable. + * @param status The status of the over-hit flag + */ + public void overhitEnabled(boolean status) + { + _overhit = status; + } + + /** + * Set the over-hit values like the attacker who did the strike and the amount of damage done by the skill. + * @param attacker The Creature who hit on the Attackable using the over-hit enabled skill + * @param damage The amount of damage done by the over-hit enabled skill on the Attackable + */ + public void setOverhitValues(Creature attacker, double damage) + { + // Calculate the over-hit damage. + // Ex: mob had 10 HP left, over-hit skill did 50 damage total, over-hit damage is 40. + final double overhitDmg = -(getCurrentHp() - damage); + if (overhitDmg < 0) + { + // We didn't killed the mob with the over-hit strike. (it wasn't really an over-hit strike). + // Let's just clear all the over-hit related values. + overhitEnabled(false); + _overhitDamage = 0; + _overhitAttacker = null; + return; + } + + overhitEnabled(true); + _overhitDamage = overhitDmg; + _overhitAttacker = attacker; + } + + /** + * Return the Creature who hit on the Attackable using an over-hit enabled skill. + * @return Creature attacker + */ + public Creature getOverhitAttacker() + { + return _overhitAttacker; + } + + /** + * Return the amount of damage done on the Attackable using an over-hit enabled skill. + * @return double damage + */ + public double getOverhitDamage() + { + return _overhitDamage; + } + + /** + * @return True if the Attackable was hit by an over-hit enabled skill. + */ + public boolean isOverhit() + { + return _overhit; + } + + /** + * Activate the absorbed soul condition on the Attackable. + */ + public void absorbSoul() + { + _absorbed = true; + } + + /** + * @return True if the Attackable had his soul absorbed. + */ + public boolean isAbsorbed() + { + return _absorbed; + } + + /** + * Adds an attacker that successfully absorbed the soul of this Attackable into the _absorbersList. + * @param attacker + */ + public void addAbsorber(Player attacker) + { + // If we have no _absorbersList initiated, do it + final AbsorberInfo ai = _absorbersList.get(attacker.getObjectId()); + + // If the Creature attacker isn't already in the _absorbersList of this Attackable, add it + if (ai == null) + { + _absorbersList.put(attacker.getObjectId(), new AbsorberInfo(attacker.getObjectId(), getCurrentHp())); + } + else + { + ai.setAbsorbedHp(getCurrentHp()); + } + + // Set this Attackable as absorbed + absorbSoul(); + } + + public void resetAbsorbList() + { + _absorbed = false; + _absorbersList.clear(); + } + + public Map getAbsorbersList() + { + return _absorbersList; + } + + /** + * Calculate the Experience and SP to distribute to attacker (Player, Servitor or Party) of the Attackable. + * @param charLevel The killer level + * @param damage The damages given by the attacker (Player, Servitor or Party) + * @param totalDamage The total damage done + * @return + */ + private double[] calculateExpAndSp(int charLevel, long damage, long totalDamage) + { + final int levelDiff = charLevel - getLevel(); + double xp = 0; + double sp = 0; + + if ((levelDiff < RatesConfig.MONSTER_EXP_MAX_LEVEL_DIFFERENCE) && (levelDiff > -RatesConfig.MONSTER_EXP_MAX_LEVEL_DIFFERENCE)) + { + xp = Math.max(0, (getExpReward(charLevel) * damage) / totalDamage); + sp = Math.max(0, (getSpReward(charLevel) * damage) / totalDamage); + + if ((charLevel > 84) && (levelDiff <= -3)) + { + double mul; + switch (levelDiff) + { + case -3: + { + mul = 0.97; + break; + } + case -4: + { + mul = 0.67; + break; + } + case -5: + { + mul = 0.42; + break; + } + case -6: + { + mul = 0.25; + break; + } + case -7: + { + mul = 0.15; + break; + } + case -8: + { + mul = 0.09; + break; + } + case -9: + { + mul = 0.05; + break; + } + case -10: + { + mul = 0.03; + break; + } + default: + { + mul = 1; + break; + } + } + + xp *= mul; + sp *= mul; + } + } + + return new double[] + { + xp, + sp + }; + } + + public double calculateOverhitExp(double exp) + { + // Get the percentage based on the total of extra (over-hit) damage done relative to the total (maximum) ammount of HP on the Attackable. + double overhitPercentage = ((_overhitDamage * 100) / getMaxHp()); + + // Over-hit damage percentages are limited to 25% max. + if (overhitPercentage > 25) + { + overhitPercentage = 25; + } + + // Get the overhit exp bonus according to the above over-hit damage percentage. + // (1/1 basis - 13% of over-hit damage, 13% of extra exp is given, and so on...). + return (overhitPercentage / 100) * exp; + } + + /** + * Return True. + */ + @Override + public boolean canBeAttacked() + { + return true; + } + + @Override + public void onSpawn() + { + // Keep track of the spawn time. + _spawnTime = System.currentTimeMillis(); + + super.onSpawn(); + + // Clear mob spoil, seed. + setSpoilerObjectId(0); + + // Clear all aggro char from list + clearAggroList(); + + // Clear Harvester reward + _harvestItem.set(null); + + // fake players + if (isFakePlayer()) + { + getFakePlayerDrops().clear(); // Clear existing fake player drops. + setKarma(0); // reset karma + setScriptValue(0); // remove pvp flag + setRunning(); // don't walk + } + else + { + setWalking(); + } + + // Clear mod Seeded stat + _seeded = false; + _seed = null; + _seederObjId = 0; + + // Clear overhit value + overhitEnabled(false); + + _sweepItems.set(null); + resetAbsorbList(); + + setWalking(); + + // Check the region where this mob is, do not activate the AI if region is inactive. + if (hasAI()) + { + // Roll the aggro timeout again, the AI object survives a respawn. + if (getAI() instanceof AttackableAI) + { + ((AttackableAI) getAI()).rollGlobalAggro(); + } + + // Set the intention of the Attackable to ACTIVE. + getAI().setIntentionActive(); + + // Check the region where this mob is, do not activate the AI if region is inactive. + if (!isInActiveRegion()) + { + getAI().stopAITask(); + } + } + } + + /** + * @return the time in milliseconds when this attackable was spawned. + */ + public long getSpawnTime() + { + return _spawnTime; + } + + /** + * Checks if it is spoiled. + * @return {@code true} if it is spoiled, {@code false} otherwise + */ + public boolean isSpoiled() + { + return _spoilerObjectId != 0; + } + + /** + * Gets the spoiler object ID. + * @return the spoiler object ID if it is spoiled, 0 otherwise + */ + public int getSpoilerObjectId() + { + return _spoilerObjectId; + } + + /** + * Sets the spoiler object ID. + * @param spoilerObjectId spoilerObjectId the spoiler object ID + */ + public void setSpoilerObjectId(int spoilerObjectId) + { + _spoilerObjectId = spoilerObjectId; + } + + /** + * Sets state of the mob to seeded. Parameters needed to be set before. + * @param seeder + */ + public void setSeeded(Player seeder) + { + if ((_seed == null) || (_seederObjId != seeder.getObjectId())) + { + return; + } + + _seeded = true; + int count = 1; + for (int skillId : getTemplate().getSkills().keySet()) + { + switch (skillId) + { + case 4303: // Strong type x2 + { + count *= 2; + break; + } + case 4304: // Strong type x3 + { + count *= 3; + break; + } + case 4305: // Strong type x4 + { + count *= 4; + break; + } + case 4306: // Strong type x5 + { + count *= 5; + break; + } + case 4307: // Strong type x6 + { + count *= 6; + break; + } + case 4308: // Strong type x7 + { + count *= 7; + break; + } + case 4309: // Strong type x8 + { + count *= 8; + break; + } + case 4310: // Strong type x9 + { + count *= 9; + break; + } + } + } + + // hi-level mobs bonus + final int diff = getLevel() - _seed.getLevel() - 5; + if (diff > 0) + { + count += diff; + } + + _harvestItem.set(new ItemHolder(_seed.getCropId(), count * RatesConfig.RATE_DROP_MANOR)); + } + + /** + * Sets the seed parameters, but not the seed state + * @param seed - instance {@link Seed} of used seed + * @param seeder - player who sows the seed + */ + public void setSeeded(Seed seed, Player seeder) + { + if (_seeded) + { + return; + } + + _seed = seed; + _seederObjId = seeder.getObjectId(); + } + + public int getSeederId() + { + return _seederObjId; + } + + public Seed getSeed() + { + return _seed; + } + + public boolean isSeeded() + { + return _seeded; + } + + /** + * Set delay for onKill() call, in ms Default: 5000 ms + * @param delay + */ + public void setOnKillDelay(int delay) + { + _onKillDelay = delay; + } + + public int getOnKillDelay() + { + return _onKillDelay; + } + + /** + * Check if the server allows Random Animation. + */ + // This is located here because Monster and FriendlyMob both extend this class. The other non-pc instances extend either Npc or Monster. + @Override + public boolean hasRandomAnimation() + { + return ((GeneralConfig.MAX_MONSTER_ANIMATION > 0) && isRandomAnimationEnabled() && !(this instanceof GrandBoss)); + } + + public void setCommandChannelTimer(CommandChannelTimer commandChannelTimer) + { + _commandChannelTimer = commandChannelTimer; + } + + public CommandChannelTimer getCommandChannelTimer() + { + return _commandChannelTimer; + } + + public CommandChannel getFirstCommandChannelAttacked() + { + return _firstCommandChannelAttacked; + } + + public void setFirstCommandChannelAttacked(CommandChannel firstCommandChannelAttacked) + { + _firstCommandChannelAttacked = firstCommandChannelAttacked; + } + + /** + * @return the _commandChannelLastAttack + */ + public long getCommandChannelLastAttack() + { + return _commandChannelLastAttack; + } + + /** + * @param channelLastAttack the _commandChannelLastAttack to set + */ + public void setCommandChannelLastAttack(long channelLastAttack) + { + _commandChannelLastAttack = channelLastAttack; + } + + public void returnHome() + { + clearAggroList(); + + if (hasAI() && (getSpawn() != null)) + { + getAI().setIntentionMoveTo(getSpawn().getLocation()); + } + } + + /* + * Return vitality points decrease (if positive) or increase (if negative) based on damage. Maximum for damage = maxHp. + */ + public float getVitalityPoints(int level, long damage) + { + // sanity check + if (damage <= 0) + { + return 0; + } + + final long expReward = getExpReward(level); + final float divider = (getLevel() > 0) && (expReward > 0) ? (getTemplate().getBaseHpMax() * 9 * getLevel() * getLevel()) / (100 * expReward) : 0; + if (divider == 0) + { + return 0; + } + + // negative value - vitality will be consumed + return -Math.min(damage, getMaxHp()) / divider; + } + + /* + * True if vitality rate for exp and sp should be applied + */ + public boolean useVitalityRate() + { + return !_champion || ChampionMonstersConfig.CHAMPION_ENABLE_VITALITY; + } + + /** Return True if the Creature is RaidBoss or his minion. */ + @Override + public boolean isRaid() + { + return _isRaid; + } + + /** + * Set this Npc as a Raid instance. + * @param isRaid + */ + public void setIsRaid(boolean isRaid) + { + _isRaid = isRaid; + } + + /** + * Set this Npc as a Minion instance. + * @param value + */ + public void setIsRaidMinion(boolean value) + { + _isRaid = value; + _isRaidMinion = value; + } + + @Override + public boolean isRaidMinion() + { + return _isRaidMinion; + } + + @Override + public boolean isMinion() + { + return getLeader() != null; + } + + /** + * @return leader of this minion or null. + */ + public Attackable getLeader() + { + return null; + } + + public void setChampion(boolean champ) + { + _champion = champ; + if (ChampionMonstersConfig.SHOW_CHAMPION_AURA) + { + setTeam(champ ? Team.RED : Team.NONE); + } + } + + @Override + public boolean isChampion() + { + return _champion; + } + + @Override + public boolean isAttackable() + { + return true; + } + + @Override + public Attackable asAttackable() + { + return this; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/Creature.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/Creature.java new file mode 100644 index 0000000..833f9da --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/Creature.java @@ -0,0 +1,7361 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.StampedLock; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.commons.threads.ThreadPool; +import org.l2jmobius.commons.util.Rnd; +import org.l2jmobius.gameserver.ai.AttackableAI; +import org.l2jmobius.gameserver.ai.CreatureAI; +import org.l2jmobius.gameserver.ai.Intention; +import org.l2jmobius.gameserver.cache.RelationCache; +import org.l2jmobius.gameserver.config.GeoEngineConfig; +import org.l2jmobius.gameserver.config.GrandBossConfig; +import org.l2jmobius.gameserver.config.NpcConfig; +import org.l2jmobius.gameserver.config.PlayerConfig; +import org.l2jmobius.gameserver.config.PvpConfig; +import org.l2jmobius.gameserver.config.custom.BossAnnouncementsConfig; +import org.l2jmobius.gameserver.config.custom.CaptchaConfig; +import org.l2jmobius.gameserver.config.custom.ChampionMonstersConfig; +import org.l2jmobius.gameserver.config.custom.ClassBalanceConfig; +import org.l2jmobius.gameserver.config.custom.FakePlayersConfig; +import org.l2jmobius.gameserver.data.enums.CategoryType; +import org.l2jmobius.gameserver.data.holders.AccessLevel; +import org.l2jmobius.gameserver.data.xml.CategoryData; +import org.l2jmobius.gameserver.data.xml.DoorData; +import org.l2jmobius.gameserver.data.xml.FenceData; +import org.l2jmobius.gameserver.data.xml.MapRegionData; +import org.l2jmobius.gameserver.data.xml.NpcData; +import org.l2jmobius.gameserver.data.xml.SendMessageLocalisationData; +import org.l2jmobius.gameserver.entity.Location; +import org.l2jmobius.gameserver.entity.World; +import org.l2jmobius.gameserver.entity.WorldObject; +import org.l2jmobius.gameserver.entity.WorldRegion; +import org.l2jmobius.gameserver.entity.actor.enums.creature.InstanceType; +import org.l2jmobius.gameserver.entity.actor.enums.creature.Race; +import org.l2jmobius.gameserver.entity.actor.enums.creature.Team; +import org.l2jmobius.gameserver.entity.actor.enums.player.TeleportWhereType; +import org.l2jmobius.gameserver.entity.actor.holders.creature.EffectList; +import org.l2jmobius.gameserver.entity.actor.holders.creature.InvulSkillHolder; +import org.l2jmobius.gameserver.entity.actor.holders.creature.TimeStamp; +import org.l2jmobius.gameserver.entity.actor.instance.GrandBoss; +import org.l2jmobius.gameserver.entity.actor.instance.QuestGuard; +import org.l2jmobius.gameserver.entity.actor.instance.RaidBoss; +import org.l2jmobius.gameserver.entity.actor.stat.CreatureStat; +import org.l2jmobius.gameserver.entity.actor.status.CreatureStatus; +import org.l2jmobius.gameserver.entity.actor.tasks.creature.HitTask; +import org.l2jmobius.gameserver.entity.actor.tasks.creature.MagicUseTask; +import org.l2jmobius.gameserver.entity.actor.tasks.creature.QueuedMagicUseTask; +import org.l2jmobius.gameserver.entity.actor.templates.CreatureTemplate; +import org.l2jmobius.gameserver.entity.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.entity.clan.Clan; +import org.l2jmobius.gameserver.entity.groups.Party; +import org.l2jmobius.gameserver.entity.instancezone.Instance; +import org.l2jmobius.gameserver.entity.item.ItemTemplate; +import org.l2jmobius.gameserver.entity.item.Weapon; +import org.l2jmobius.gameserver.entity.item.enums.ItemProcessType; +import org.l2jmobius.gameserver.entity.item.enums.ShotType; +import org.l2jmobius.gameserver.entity.item.instance.Item; +import org.l2jmobius.gameserver.entity.item.type.WeaponType; +import org.l2jmobius.gameserver.entity.itemcontainer.Inventory; +import org.l2jmobius.gameserver.entity.spawns.Spawn; +import org.l2jmobius.gameserver.entity.zone.ZoneId; +import org.l2jmobius.gameserver.entity.zone.ZoneRegion; +import org.l2jmobius.gameserver.entity.zone.type.WaterZone; +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.GeoLocation; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; +import org.l2jmobius.gameserver.interfaces.ILocational; +import org.l2jmobius.gameserver.managers.CaptchaManager; +import org.l2jmobius.gameserver.managers.IdManager; +import org.l2jmobius.gameserver.managers.InstanceManager; +import org.l2jmobius.gameserver.managers.ScriptManager; +import org.l2jmobius.gameserver.managers.ZoneManager; +import org.l2jmobius.gameserver.mechanics.effects.EffectFlag; +import org.l2jmobius.gameserver.mechanics.effects.EffectType; +import org.l2jmobius.gameserver.mechanics.events.Containers; +import org.l2jmobius.gameserver.mechanics.events.EventDispatcher; +import org.l2jmobius.gameserver.mechanics.events.EventType; +import org.l2jmobius.gameserver.mechanics.events.holders.actor.creature.OnCreatureAttack; +import org.l2jmobius.gameserver.mechanics.events.holders.actor.creature.OnCreatureAttackAvoid; +import org.l2jmobius.gameserver.mechanics.events.holders.actor.creature.OnCreatureAttacked; +import org.l2jmobius.gameserver.mechanics.events.holders.actor.creature.OnCreatureDamageDealt; +import org.l2jmobius.gameserver.mechanics.events.holders.actor.creature.OnCreatureDamageReceived; +import org.l2jmobius.gameserver.mechanics.events.holders.actor.creature.OnCreatureDeath; +import org.l2jmobius.gameserver.mechanics.events.holders.actor.creature.OnCreatureKilled; +import org.l2jmobius.gameserver.mechanics.events.holders.actor.creature.OnCreatureSee; +import org.l2jmobius.gameserver.mechanics.events.holders.actor.creature.OnCreatureSkillUse; +import org.l2jmobius.gameserver.mechanics.events.holders.actor.creature.OnCreatureTeleported; +import org.l2jmobius.gameserver.mechanics.events.holders.actor.npc.OnNpcSkillSee; +import org.l2jmobius.gameserver.mechanics.events.holders.actor.npc.attackable.OnAttackableFactionCall; +import org.l2jmobius.gameserver.mechanics.events.listeners.AbstractEventListener; +import org.l2jmobius.gameserver.mechanics.events.returns.TerminateReturn; +import org.l2jmobius.gameserver.mechanics.options.OptionSkillHolder; +import org.l2jmobius.gameserver.mechanics.options.OptionSkillType; +import org.l2jmobius.gameserver.mechanics.skill.AbnormalType; +import org.l2jmobius.gameserver.mechanics.skill.AbnormalVisualEffect; +import org.l2jmobius.gameserver.mechanics.skill.BuffFinishTask; +import org.l2jmobius.gameserver.mechanics.skill.BuffInfo; +import org.l2jmobius.gameserver.mechanics.skill.CommonSkill; +import org.l2jmobius.gameserver.mechanics.skill.EffectScope; +import org.l2jmobius.gameserver.mechanics.skill.Skill; +import org.l2jmobius.gameserver.mechanics.skill.SkillChannelized; +import org.l2jmobius.gameserver.mechanics.skill.SkillChannelizer; +import org.l2jmobius.gameserver.mechanics.skill.enums.SkillFinishType; +import org.l2jmobius.gameserver.mechanics.skill.holders.SkillHolder; +import org.l2jmobius.gameserver.mechanics.skill.holders.SkillUseHolder; +import org.l2jmobius.gameserver.mechanics.skill.targets.TargetType; +import org.l2jmobius.gameserver.mechanics.stats.BaseStat; +import org.l2jmobius.gameserver.mechanics.stats.Calculator; +import org.l2jmobius.gameserver.mechanics.stats.Formulas; +import org.l2jmobius.gameserver.mechanics.stats.Stat; +import org.l2jmobius.gameserver.mechanics.stats.functions.AbstractFunction; +import org.l2jmobius.gameserver.network.Disconnection; +import org.l2jmobius.gameserver.network.GameClient; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.serverpackets.AbstractNpcInfo; +import org.l2jmobius.gameserver.network.serverpackets.ActionFailed; +import org.l2jmobius.gameserver.network.serverpackets.Attack; +import org.l2jmobius.gameserver.network.serverpackets.ChangeMoveType; +import org.l2jmobius.gameserver.network.serverpackets.ChangeWaitType; +import org.l2jmobius.gameserver.network.serverpackets.FakePlayerInfo; +import org.l2jmobius.gameserver.network.serverpackets.MagicSkillCanceled; +import org.l2jmobius.gameserver.network.serverpackets.MagicSkillLaunched; +import org.l2jmobius.gameserver.network.serverpackets.MagicSkillUse; +import org.l2jmobius.gameserver.network.serverpackets.MoveToLocation; +import org.l2jmobius.gameserver.network.serverpackets.MoveToPawn; +import org.l2jmobius.gameserver.network.serverpackets.Revive; +import org.l2jmobius.gameserver.network.serverpackets.ServerClose; +import org.l2jmobius.gameserver.network.serverpackets.ServerObjectInfo; +import org.l2jmobius.gameserver.network.serverpackets.ServerPacket; +import org.l2jmobius.gameserver.network.serverpackets.SetupGauge; +import org.l2jmobius.gameserver.network.serverpackets.SocialAction; +import org.l2jmobius.gameserver.network.serverpackets.StatusUpdate; +import org.l2jmobius.gameserver.network.serverpackets.StopMove; +import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; +import org.l2jmobius.gameserver.network.serverpackets.TeleportToLocation; +import org.l2jmobius.gameserver.taskmanagers.AttackStanceTaskManager; +import org.l2jmobius.gameserver.taskmanagers.CreatureSeeTaskManager; +import org.l2jmobius.gameserver.taskmanagers.GameTimeTaskManager; +import org.l2jmobius.gameserver.taskmanagers.MovementTaskManager; +import org.l2jmobius.gameserver.util.LocationUtil; + +/** + * Mother class of all character objects of the world (PC, NPC...)
    + * Creature:
    + *
      + *
    • Door
    • + *
    • Playable
    • + *
    • Npc
    • + *
    • StaticObject
    • + *
    • Trap
    • + *
    • Vehicle
    • + *
    + * Concept of CreatureTemplate:
    + * Each Creature owns generic and static properties (ex : all Keltir have the same number of HP...).
    + * All of those properties are stored in a different template for each type of Creature.
    + * Each template is loaded once in the server cache memory (reduce memory use).
    + * When a new instance of Creature is spawned, server just create a link between the instance and the template.
    + * This link is stored in {@link #_template} + * @version $Revision: 1.53.2.45.2.34 $ $Date: 2005/04/11 10:06:08 $ + */ +public abstract class Creature extends WorldObject +{ + public static final Logger LOGGER = Logger.getLogger(Creature.class.getName()); + + private final Set _attackByList = ConcurrentHashMap.newKeySet(1); + private volatile boolean _isCastingNow = false; + private volatile boolean _isCastingSimultaneouslyNow = false; + private Skill _lastSkillCast; + private Skill _lastSimultaneousSkillCast; + + private boolean _isDead = false; + private boolean _isImmobilized = false; + private boolean _isOverloaded = false; // The char is carrying too much. + private boolean _isParalyzed = false; + private boolean _isPendingRevive = false; + private boolean _isRunning = false; + protected boolean _showSummonAnimation = false; + protected boolean _isTeleporting = false; + private boolean _isInvul = false; + private boolean _isMortal = true; // Char will die when HP decreased to 0. + private boolean _isFlying = false; + + private CreatureStat _stat; + private CreatureStatus _status; + private CreatureTemplate _template; // The link on the CreatureTemplate object containing generic and static properties of this Creature type (ex : Max HP, Speed...) + private String _title; + + public static final double MAX_HP_BAR_PX = 352.0; + + private double _hpUpdateIncCheck = .0; + private double _hpUpdateDecCheck = .0; + private double _hpUpdateInterval = .0; + + private int _karma = 0; + + /** Table of Calculators containing all used calculator */ + private Calculator[] _calculators; + /** Map containing all skills of this character. */ + private final Map _skills = new ConcurrentHashMap<>(); + /** Map containing the skill reuse time stamps. */ + private final Map _reuseTimeStampsSkills = new ConcurrentHashMap<>(); + /** Map containing the item reuse time stamps. */ + private final Map _reuseTimeStampsItems = new ConcurrentHashMap<>(); + /** Map containing all the disabled skills. */ + private final Map _disabledSkills = new ConcurrentHashMap<>(); + private boolean _allSkillsDisabled; + + private final byte[] _zones = new byte[ZoneId.getZoneCount()]; + protected final Location _lastZoneValidateLocation = new Location(getX(), getY(), getZ()); + + private final StampedLock _attackLock = new StampedLock(); + + private Team _team = Team.NONE; + + private boolean _lethalable = true; + + private final Map _triggerSkills = new ConcurrentHashMap<>(1); + + private final Map _invulAgainst = new ConcurrentHashMap<>(1); + /** Creatures effect list. */ + private final EffectList _effectList = new EffectList(this); + /** The creature that summons this character. */ + private Creature _summoner = null; + + private SkillChannelizer _channelizer = null; + + private SkillChannelized _channelized = null; + + private final BuffFinishTask _buffFinishTask = new BuffFinishTask(); + + /** Map 32 bits, containing all abnormal visual effects in progress. */ + private int _abnormalVisualEffects; + /** Map 32 bits, containing all special abnormal visual effects in progress. */ + private int _abnormalVisualEffectsSpecial; + /** Map 32 bits, containing all event abnormal visual effects in progress. */ + private int _abnormalVisualEffectsEvent; + + /** Movement data of this Creature */ + protected MoveData _move; + private boolean _cursorKeyMovement = false; + + /** This creature's target. */ + private WorldObject _target; + /** Represents the time where the attack should end, in nanoseconds. */ + private volatile long _attackEndTime; + private int _disableBowAttackEndTime; + + private int _castInterruptTime; + + /** Table of calculators containing all standard NPC calculator (ex : ACCURACY_COMBAT, EVASION_RATE) */ + private static final Calculator[] NPC_STD_CALCULATOR = Formulas.getStdNPCCalculators(); + + private volatile CreatureAI _ai = null; + + /** Future Skill Cast */ + protected Future _skillCast; + protected Future _skillCast2; + + private final Map _knownRelations = new ConcurrentHashMap<>(); + + private Set _seenCreatures = null; + private int _seenCreatureRange = PlayerConfig.ALT_PARTY_RANGE; + + /** A list containing the dropped items of this fake player. */ + private final List _fakePlayerDrops = new CopyOnWriteArrayList<>(); + + private OnCreatureAttack _onCreatureAttack = null; + private OnCreatureAttacked _onCreatureAttacked = null; + private OnCreatureDamageDealt _onCreatureDamageDealt = null; + private OnCreatureDamageReceived _onCreatureDamageReceived = null; + private OnCreatureAttackAvoid _onCreatureAttackAvoid = null; + private OnCreatureSkillUse _onCreatureSkillUse = null; + + /** + * Creates a creature. + * @param template the creature template + */ + public Creature(CreatureTemplate template) + { + this(IdManager.getInstance().getNextId(), template); + } + + /** + * Constructor of Creature.
    + *
    + * Concept:
    + *
    + * Each Creature owns generic and static properties (ex : all Keltir have the same number of HP...).
    + * All of those properties are stored in a different template for each type of Creature. Each template is loaded once in the server cache memory (reduce memory use).
    + * When a new instance of Creature is spawned, server just create a link between the instance and the template This link is stored in _template
    + *
    + * Actions: + *
      + *
    • Set the _template of the Creature
    • + *
    • Set _overloaded to false (the character can take more items)
    • + *
    • If Creature is a Npc, copy skills from template to object
    • + *
    • If Creature is a Npc, link _calculators to NPC_STD_CALCULATOR
    • + *
    • If Creature is NOT a Npc, create an empty _skills slot
    • + *
    • If Creature is a Player or Summon, copy basic Calculator set to object
    • + *
    + * @param objectId Identifier of the object to initialized + * @param template The CreatureTemplate to apply to the object + */ + public Creature(int objectId, CreatureTemplate template) + { + super(objectId); + if (template == null) + { + throw new NullPointerException("Template is null!"); + } + + setInstanceType(InstanceType.Creature); + initCharStat(); + initCharStatus(); + + // Set its template to the new Creature. + _template = template; + if (isDoor()) + { + _calculators = Formulas.getStdDoorCalculators(); + } + else if (isNpc()) + { + // Copy the Standard Calculators of the Npc in _calculators. + _calculators = NPC_STD_CALCULATOR; + + // Copy the skills of the Npc from its template to the Creature Instance. + // The skills list can be affected by spell effects so it's necessary to make a copy. + // to avoid that a spell affecting a Npc, affects others Npc of the same type too. + for (Skill skill : template.getSkills().values()) + { + addSkill(skill); + } + } + else + { + // If Creature is a Player or a Summon, create the basic calculator set. + _calculators = new Calculator[Stat.NUM_STATS]; + if (isSummon()) + { + // Copy the skills of the Summon from its template to the Creature Instance. + // The skills list can be affected by spell effects so it's necessary to make a copy. + // to avoid that a spell affecting a Summon, affects others Summon of the same type too. + for (Skill skill : template.getSkills().values()) + { + addSkill(skill); + } + } + + Formulas.addFuncsToNewCharacter(this); + } + + setInvul(true); + } + + public EffectList getEffectList() + { + return _effectList; + } + + /** + * @return character inventory, default null, overridden in Playable types and in Npc + */ + public Inventory getInventory() + { + return null; + } + + public boolean destroyItemByItemId(ItemProcessType process, int itemId, int count, WorldObject reference, boolean sendMessage) + { + // Default: NPCs consume virtual items for their skills. + // TODO: should be logged if even happens.. should be false + return true; + } + + public boolean destroyItem(ItemProcessType process, int objectId, int count, WorldObject reference, boolean sendMessage) + { + // Default: NPCs consume virtual items for their skills. + // TODO: should be logged if even happens.. should be false + return true; + } + + /** + * Check if the character is in the given zone Id. + * @param zone the zone Id to check + * @return {code true} if the character is in that zone + */ + @Override + public boolean isInsideZone(ZoneId zone) + { + final Instance instance = InstanceManager.getInstance().getInstance(getInstanceId()); + switch (zone) + { + case PVP: + { + if ((instance != null) && instance.isPvP()) + { + return true; + } + + return (_zones[ZoneId.PVP.ordinal()] > 0) && (_zones[ZoneId.PEACE.ordinal()] == 0) && (_zones[ZoneId.NO_PVP.ordinal()] == 0); + } + case PEACE: + { + if ((instance != null) && instance.isPvP()) + { + return false; + } + } + } + + return _zones[zone.ordinal()] > 0; + } + + /** + * @param zone + * @param state + */ + public void setInsideZone(ZoneId zone, boolean state) + { + synchronized (_zones) + { + if (state) + { + _zones[zone.ordinal()]++; + } + else if (_zones[zone.ordinal()] > 0) + { + _zones[zone.ordinal()]--; + } + } + } + + /** + * This will return true if the player is GM,
    + * but if the player is not GM it will return false. + * @return GM status + */ + public boolean isGM() + { + return false; + } + + /** + * Overridden in Player. + * @return the access level. + */ + public AccessLevel getAccessLevel() + { + return null; + } + + protected void initCharStatusUpdateValues() + { + _hpUpdateIncCheck = _stat.getMaxHp(); + _hpUpdateInterval = _hpUpdateIncCheck / MAX_HP_BAR_PX; + _hpUpdateDecCheck = _hpUpdateIncCheck - _hpUpdateInterval; + } + + /** + * Remove the Creature from the world when the decay task is launched.
    + * Caution: This method DOESN'T REMOVE the object from _allObjects of World
    + * Caution: This method DOESN'T SEND Server->Client packets to players + */ + public void onDecay() + { + if (PlayerConfig.DISCONNECT_AFTER_DEATH && isPlayer()) + { + final Player player = asPlayer(); + if (player.isOnline()) + { + player.sendPacket(new SystemMessage(SendMessageLocalisationData.getLocalisation(player, "60 min. have passed after the death of your character, so you were disconnected from the game."))); + Disconnection.of(player).storeAndDeleteWith(ServerClose.STATIC_PACKET); + } + } + else + { + decayMe(); + final ZoneRegion region = ZoneManager.getInstance().getRegion(this); + if (region != null) + { + region.removeFromZones(this); + } + } + } + + @Override + public void onSpawn() + { + super.onSpawn(); + + _buffFinishTask.start(); + + revalidateZone(true); + + // Custom boss announcements configuration. + if (this instanceof GrandBoss) + { + if (BossAnnouncementsConfig.GRANDBOSS_SPAWN_ANNOUNCEMENTS && (!isInInstance() || BossAnnouncementsConfig.GRANDBOSS_INSTANCE_ANNOUNCEMENTS) && !isMinion() && !isRaidMinion()) + { + final String name = NpcData.getInstance().getTemplate(getId()).getName(); + if ((name != null) && !BossAnnouncementsConfig.RAIDBOSSES_EXCLUDED_FROM_SPAWN_ANNOUNCEMENTS.contains(getId())) + { + World.broadcastToAllOnlinePlayers(name + " has spawned!"); + World.broadcastToAllOnlinePlayersOnScreen(name + " has spawned!"); + } + } + } + else if (isRaid() && BossAnnouncementsConfig.RAIDBOSS_SPAWN_ANNOUNCEMENTS && (!isInInstance() || BossAnnouncementsConfig.RAIDBOSS_INSTANCE_ANNOUNCEMENTS) && !isMinion() && !isRaidMinion()) + { + final String name = NpcData.getInstance().getTemplate(getId()).getName(); + if ((name != null) && !BossAnnouncementsConfig.RAIDBOSSES_EXCLUDED_FROM_SPAWN_ANNOUNCEMENTS.contains(getId())) + { + World.broadcastToAllOnlinePlayers(name + " has spawned!"); + World.broadcastToAllOnlinePlayersOnScreen(name + " has spawned!"); + } + } + } + + public synchronized void onTeleported() + { + if (!_isTeleporting) + { + return; + } + + spawnMe(getX(), getY(), getZ()); + setTeleporting(false); + + if (EventDispatcher.getInstance().hasListener(EventType.ON_CREATURE_TELEPORTED, this)) + { + EventDispatcher.getInstance().notifyEventAsync(new OnCreatureTeleported(this), this); + } + } + + /** + * Add Creature instance that is attacking to the attacker list. + * @param creature The Creature that attacks this one + */ + public void addAttackerToAttackByList(Creature creature) + { + // DS: moved to Attackable. + } + + /** + * Send a packet to the Creature AND to all Player in the _KnownPlayers of the Creature.
    + *
    + * Concept:
    + *
    + * Player in the detection area of the Creature are identified in _knownPlayers.
    + * In order to inform other players of state modification on the Creature, server just need to go through known players to send Server->Client Packet + * @param packet + */ + public void broadcastPacket(ServerPacket packet) + { + packet.sendInBroadcast(); + + World.forEachVisibleObject(this, Player.class, player -> + { + if (isVisibleFor(player)) + { + player.sendPacket(packet); + } + }); + } + + // TODO: Add target logic. + public void broadcastSkillPacket(ServerPacket packet, WorldObject target) + { + broadcastPacket(packet); + } + + // TODO: Add targets logic. + public void broadcastSkillPacket(ServerPacket packet, Collection targets) + { + broadcastPacket(packet); + } + + public void broadcastMoveToLocation() + { + broadcastMoveToLocation(false); + } + + public void broadcastMoveToLocation(boolean force) + { + final MoveData move = _move; + if (move == null) + { + return; + } + + // Broadcast MoveToLocation when forced or once per second. + final int gameTicks = GameTimeTaskManager.getInstance().getGameTicks(); + if (!force && !_cursorKeyMovement && (move.moveTimestamp > 0) && ((gameTicks - move.lastBroadcastTime) < 10)) + { + return; + } + + move.lastBroadcastTime = gameTicks; + + if (isPlayable()) + { + broadcastPacket(new MoveToLocation(this)); + } + else + { + final CreatureAI ai = hasAI() ? getAI() : null; + final Intention intention = ai != null ? ai.getIntention() : null; + final WorldObject target = ((intention == Intention.ATTACK) || (intention == Intention.FOLLOW)) ? _target : null; + if (target != null) + { + if ((target != this) && !isOnGeodataPath(move)) + { + broadcastPacket(new MoveToPawn(this, target, getAI().getClientMovingToPawnOffset())); + } + else + { + broadcastPacket(new MoveToLocation(this)); + } + } + else + { + final WorldRegion region = getWorldRegion(); + if (((region != null) && region.areNeighborsActive())) + { + broadcastPacket(new MoveToLocation(this)); + } + } + } + } + + public void broadcastSocialAction(int id) + { + if (isPlayable()) + { + broadcastPacket(new SocialAction(getObjectId(), id)); + } + else + { + final WorldRegion region = getWorldRegion(); + if ((region != null) && region.areNeighborsActive()) + { + broadcastPacket(new SocialAction(getObjectId(), id)); + } + } + } + + /** + * @return true if hp update should be done, false if not. + */ + protected boolean needHpUpdate() + { + final double currentHp = _status.getCurrentHp(); + final double maxHp = _stat.getMaxHp(); + if ((currentHp <= 1.0) || (maxHp < MAX_HP_BAR_PX)) + { + return true; + } + + if ((currentHp <= _hpUpdateDecCheck) || (currentHp >= _hpUpdateIncCheck)) + { + if (currentHp == maxHp) + { + _hpUpdateIncCheck = currentHp + 1; + _hpUpdateDecCheck = currentHp - _hpUpdateInterval; + } + else + { + final double doubleMulti = currentHp / _hpUpdateInterval; + int intMulti = (int) doubleMulti; + _hpUpdateDecCheck = _hpUpdateInterval * (doubleMulti < intMulti ? intMulti - 1 : intMulti); + _hpUpdateIncCheck = _hpUpdateDecCheck + _hpUpdateInterval; + } + + return true; + } + + return false; + } + + /** + * Send the Server->Client packet StatusUpdate with current HP and MP to all other Player to inform.
    + *
    + * Actions: + *
      + *
    • Create the Server->Client packet StatusUpdate with current HP and MP
    • + *
    • Send the Server->Client packet StatusUpdate with current HP and MP to all Creature called _statusListener that must be informed of HP/MP updates of this Creature
    • + *
    + * Caution: This method DOESN'T SEND CP information + */ + public void broadcastStatusUpdate() + { + if (_status.getStatusListener().isEmpty() || !needHpUpdate()) + { + return; + } + + // Create the Server->Client packet StatusUpdate with current HP. + final StatusUpdate su = new StatusUpdate(this); + su.addAttribute(StatusUpdate.MAX_HP, _stat.getMaxHp()); + su.addAttribute(StatusUpdate.CUR_HP, (int) _status.getCurrentHp()); + + // Go through the StatusListener. + // Send the Server->Client packet StatusUpdate with current HP and MP. + for (Creature temp : _status.getStatusListener()) + { + if (temp != null) + { + temp.sendPacket(su); + } + } + } + + /** + * @param text + */ + public void sendMessage(String text) + { + // default implementation + } + + /** + * Teleport a Creature and its pet if necessary.
    + *
    + * Actions: + *
      + *
    • Stop the movement of the Creature
    • + *
    • Set the x,y,z position of the WorldObject and if necessary modify its _worldRegion
    • + *
    • Send a Server->Client packet TeleportToLocationt to the Creature AND to all Player in its _KnownPlayers
    • + *
    • Modify the position of the pet if necessary
    • + *
    + * @param xValue + * @param yValue + * @param zValue + * @param headingValue + * @param instanceId + * @param randomOffset + */ + public void teleToLocation(int xValue, int yValue, int zValue, int headingValue, int instanceId, int randomOffset) + { + // Prevent teleporting for players that disconnected unexpectedly. + if (isPlayer() && !asPlayer().isOnline()) + { + return; + } + + int x = xValue; + int y = yValue; + int z = _isFlying ? zValue : GeoEngine.getInstance().getHeight(x, y, zValue); + int heading = headingValue; + + // Prepare creature for teleport. + if (_isPendingRevive) + { + doRevive(); + } + + // Abort any client actions, casting and remove target. + stopMove(null); + abortAttack(); + abortCast(); + setTarget(null); + + setTeleporting(true); + + getAI().setIntentionActive(); + + // Remove the object from its old location. + decayMe(); + + // Adjust position a bit. + if (PlayerConfig.OFFSET_ON_TELEPORT_ENABLED || (randomOffset > 0)) + { + x = xValue + Rnd.get(-randomOffset, randomOffset); + y = yValue + Rnd.get(-randomOffset, randomOffset); + + int count = 0; + final float collision = Math.min(getTemplate().getCollisionRadius() * 2, randomOffset); + while ((count++ < 100) && ((LocationUtil.calculateDistance(xValue, yValue, zValue, x, y, z, true, false) < collision) || !GeoEngine.getInstance().canSeeTarget(xValue, yValue, zValue, x, y, z, instanceId) || !GeoEngine.getInstance().canMoveToTarget(xValue, yValue, zValue, x, y, z, instanceId))) + { + x = xValue + Rnd.get(-randomOffset, randomOffset); + y = yValue + Rnd.get(-randomOffset, randomOffset); + } + + if (!_isFlying) + { + final Location validLocation = GeoEngine.getInstance().getValidLocation(xValue, yValue, z, x, y, z, instanceId); + x = validLocation.getX(); + y = validLocation.getY(); + z = validLocation.getZ(); + } + } + + z += 5; + + // Send teleport packet where needed. + broadcastPacket(new TeleportToLocation(this, x, y, z, heading)); + + // Change instance id. + setInstanceId(instanceId); + + // Set the x,y,z position of the WorldObject and if necessary modify its _worldRegion. + setXYZ(x, y, z); + + // Also adjust heading. + if (heading != 0) + { + setHeading(heading); + } + + // Allow recall of the detached characters. + if (isPlayer()) + { + final Player player = asPlayer(); + final GameClient client = player.getClient(); + if ((client != null) && client.isDetached()) + { + onTeleported(); + } + } + else + { + onTeleported(); + } + + revalidateZone(true); + } + + public void teleToLocation(int x, int y, int z, int heading, int instanceId, boolean randomOffset) + { + teleToLocation(x, y, z, heading, instanceId, (randomOffset) ? PlayerConfig.MAX_OFFSET_ON_TELEPORT : 0); + } + + public void teleToLocation(int x, int y, int z, int heading, int instanceId) + { + teleToLocation(x, y, z, heading, instanceId, 0); + } + + public void teleToLocation(int x, int y, int z, int heading, boolean randomOffset) + { + teleToLocation(x, y, z, heading, -1, (randomOffset) ? PlayerConfig.MAX_OFFSET_ON_TELEPORT : 0); + } + + public void teleToLocation(int x, int y, int z, int heading) + { + teleToLocation(x, y, z, heading, -1, 0); + } + + public void teleToLocation(int x, int y, int z, boolean randomOffset) + { + teleToLocation(x, y, z, 0, -1, (randomOffset) ? PlayerConfig.MAX_OFFSET_ON_TELEPORT : 0); + } + + public void teleToLocation(int x, int y, int z) + { + teleToLocation(x, y, z, 0, -1, 0); + } + + public void teleToLocation(ILocational loc, int randomOffset) + { + teleToLocation(loc.getX(), loc.getY(), loc.getZ(), loc.getHeading(), loc.getInstanceId(), randomOffset); + } + + public void teleToLocation(ILocational loc, int instanceId, int randomOffset) + { + teleToLocation(loc.getX(), loc.getY(), loc.getZ(), loc.getHeading(), instanceId, randomOffset); + } + + public void teleToLocation(ILocational loc, boolean randomOffset) + { + teleToLocation(loc.getX(), loc.getY(), loc.getZ(), loc.getHeading(), loc.getInstanceId(), (randomOffset) ? PlayerConfig.MAX_OFFSET_ON_TELEPORT : 0); + } + + public void teleToLocation(ILocational loc) + { + teleToLocation(loc.getX(), loc.getY(), loc.getZ(), loc.getHeading(), loc.getInstanceId(), 0); + } + + public void teleToLocation(TeleportWhereType teleportWhere) + { + teleToLocation(MapRegionData.getInstance().getTeleToLocation(this, teleportWhere), true); + } + + private boolean canUseRangeWeapon() + { + // Check for arrows and MP. + if (isPlayer()) + { + final Weapon weaponItem = getActiveWeaponItem(); + if ((weaponItem == null) || !weaponItem.isBow()) + { + return false; + } + + // Equip arrows needed in left hand and send a Server->Client packet ItemList to the Player then return True. + if (!checkAndEquipArrows()) + { + // Cancel the action because the Player have no arrow. + getAI().setIntentionIdle(); + sendPacket(ActionFailed.STATIC_PACKET); + sendPacket(SystemMessageId.YOU_HAVE_RUN_OUT_OF_ARROWS); + return false; + } + + // Verify if the bow can be use. + if (_disableBowAttackEndTime <= GameTimeTaskManager.getInstance().getGameTicks()) + { + // Verify if Player owns enough MP. + int mpConsume = weaponItem.getMpConsume(); + if ((weaponItem.getReducedMpConsume() > 0) && (Rnd.get(100) < weaponItem.getReducedMpConsumeChance())) + { + mpConsume = weaponItem.getReducedMpConsume(); + } + + mpConsume = (int) calcStat(Stat.BOW_MP_CONSUME_RATE, mpConsume, null, null); + if (_status.getCurrentMp() < mpConsume) + { + // If Player doesn't have enough MP, stop the attack. + ThreadPool.schedule(() -> getAI().notifyActionReadyToAct(), 1000); + sendPacket(SystemMessageId.NOT_ENOUGH_MP); + sendPacket(ActionFailed.STATIC_PACKET); + return false; + } + + // If Player have enough MP, the bow consumes it. + if (mpConsume > 0) + { + _status.reduceMp(mpConsume); + } + + // Set the period of bow no re-use. + _disableBowAttackEndTime = (5 * GameTimeTaskManager.TICKS_PER_SECOND) + GameTimeTaskManager.getInstance().getGameTicks(); + } + else + { + // Cancel the action because the bow can't be re-use at this moment. + ThreadPool.schedule(() -> getAI().notifyActionReadyToAct(), 1000); + sendPacket(ActionFailed.STATIC_PACKET); + return false; + } + } + else if (isNpc()) + { + if (_disableBowAttackEndTime > GameTimeTaskManager.getInstance().getGameTicks()) + { + return false; + } + } + + return true; + } + + /** + * Launch a physical attack against a target (Simple, Bow, Pole or Dual).
    + *
    + * Actions: + *
      + *
    • Get the active weapon (always equipped in the right hand)
    • + *
    • If weapon is a bow, check for arrows, MP and bow re-use delay (if necessary, equip the Player with arrows in left hand)
    • + *
    • If weapon is a bow, consume MP and set the new period of bow non re-use
    • + *
    • Get the Attack Speed of the Creature (delay (in milliseconds) before next attack)
    • + *
    • Select the type of attack to start (Simple, Bow, Pole or Dual) and verify if SoulShot are charged then start calculation
    • + *
    • If the Server->Client packet Attack contains at least 1 hit, send the Server->Client packet Attack to the Creature AND to all Player in the _KnownPlayers of the Creature
    • + *
    • Notify AI with READY_TO_ACT
    • + *
    + * @param target The Creature targeted + */ + public void doAttack(Creature target) + { + final long stamp = _attackLock.tryWriteLock(); + if (stamp == 0) + { + return; + } + + try + { + if ((target == null) || isAttackDisabled() || !target.isTargetable()) + { + return; + } + + // Notify to scripts + if (EventDispatcher.getInstance().hasListener(EventType.ON_CREATURE_ATTACK, this)) + { + if (_onCreatureAttack == null) + { + _onCreatureAttack = new OnCreatureAttack(); + } + + _onCreatureAttack.setAttacker(this); + _onCreatureAttack.setTarget(target); + final TerminateReturn attackReturn = EventDispatcher.getInstance().notifyEvent(_onCreatureAttack, this, TerminateReturn.class); + if ((attackReturn != null) && attackReturn.terminate()) + { + getAI().setIntentionActive(); + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + } + + if (EventDispatcher.getInstance().hasListener(EventType.ON_CREATURE_ATTACKED, target)) + { + if (_onCreatureAttacked == null) + { + _onCreatureAttacked = new OnCreatureAttacked(); + } + + _onCreatureAttacked.setAttacker(this); + _onCreatureAttacked.setTarget(target); + final TerminateReturn attackedReturn = EventDispatcher.getInstance().notifyEvent(_onCreatureAttacked, target, TerminateReturn.class); + if ((attackedReturn != null) && attackedReturn.terminate()) + { + getAI().setIntentionActive(); + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + } + + if (!isAlikeDead()) + { + if ((isNpc() && target.isAlikeDead()) || !isInSurroundingRegion(target)) + { + getAI().setIntentionActive(); + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + else if (isPlayer()) + { + if (target.isDead()) + { + getAI().setIntentionActive(); + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + } + } + + // Check if attacker's weapon can attack. + if (getActiveWeaponItem() != null) + { + final Weapon wpn = getActiveWeaponItem(); + if (!wpn.isAttackWeapon() && !isGM()) + { + if (wpn.getItemType() == WeaponType.FISHINGROD) + { + sendPacket(SystemMessageId.YOU_LOOK_ODDLY_AT_THE_FISHING_POLE_IN_DISBELIEF_AND_REALIZE_THAT_YOU_CAN_T_ATTACK_ANYTHING_WITH_THIS); + } + else + { + sendMessage("That weapon cannot perform any attacks."); + } + + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + } + + final Player player = asPlayer(); + if (player != null) + { + if (player.inObserverMode()) + { + sendPacket(SystemMessageId.OBSERVERS_CANNOT_PARTICIPATE); + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + else if (player.isSiegeFriend(target)) + { + sendMessage("Force attack is impossible against a temporary allied member during a siege."); + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + // Checking if target has moved to peace zone. + else if (target.isInsidePeaceZone(player)) + { + getAI().setIntentionActive(); + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + } + else if (isInsidePeaceZone(this, target)) + { + getAI().setIntentionActive(); + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + + stopEffectsOnAction(); + + // GeoData Los Check here (or dz > 1000). + if (!GeoEngine.getInstance().canSeeTarget(this, target)) + { + sendPacket(SystemMessageId.CANNOT_SEE_TARGET); + getAI().setIntentionActive(); + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + + // Mobius: Do not move when attack is launched. + if (isMoving()) + { + stopMove(getLocation()); + } + + final Weapon weaponItem = getActiveWeaponItem(); + final int timeAtk = calculateTimeBetweenAttacks(); + final int timeToHit = timeAtk / 2; + final Attack attack = new Attack(this, target, isChargedShot(ShotType.SOULSHOTS), (weaponItem != null) ? weaponItem.getCrystalTypePlus().getLevel() : 0); + setHeading(LocationUtil.calculateHeadingFrom(this, target)); + final int reuse = calculateReuseTime(weaponItem); + final long currentTime = System.nanoTime(); + boolean hitted = false; + switch (getAttackType()) + { + case BOW: + { + if (!canUseRangeWeapon()) + { + return; + } + + _attackEndTime = currentTime + TimeUnit.MILLISECONDS.toNanos(timeToHit + (reuse / 2)); + hitted = doAttackHitByBow(attack, target, timeAtk, reuse); + break; + } + case POLE: + { + _attackEndTime = currentTime + TimeUnit.MILLISECONDS.toNanos(timeAtk); + hitted = doAttackHitByPole(attack, target, timeToHit); + break; + } + case FIST: + { + if (!isPlayer()) + { + _attackEndTime = currentTime + TimeUnit.MILLISECONDS.toNanos(timeAtk); + hitted = doAttackHitSimple(attack, target, timeToHit); + break; + } + // Fallthrough. + } + case DUAL: + case DUALFIST: + case DUALDAGGER: + { + _attackEndTime = currentTime + TimeUnit.MILLISECONDS.toNanos(timeAtk); + hitted = doAttackHitByDual(attack, target, timeToHit); + break; + } + default: + { + _attackEndTime = currentTime + TimeUnit.MILLISECONDS.toNanos(timeAtk); + hitted = doAttackHitSimple(attack, target, timeToHit); + break; + } + } + + // Precaution. It has happened in the past. Probably impossible to happen now, but will not risk it. + if (_attackEndTime < currentTime) + { + _attackEndTime = currentTime + TimeUnit.MILLISECONDS.toNanos(Integer.MAX_VALUE); + } + + if (isFakePlayer() && !FakePlayersConfig.FAKE_PLAYER_AUTO_ATTACKABLE && (target.isPlayable() || target.isFakePlayer())) + { + final Npc npc = asNpc(); + if (!npc.isScriptValue(1)) + { + npc.setScriptValue(1); // in combat + broadcastInfo(); // update flag status + ScriptManager.getInstance().getScript("PvpFlaggingStopTask").notifyEvent("FLAG_CHECK", npc, null); + } + } + + // Flag the attacker if it's a Player outside a PvP area. + if ((player != null) && !player.isInsideZone(ZoneId.PVP) && (player != target)) // Prevent players from flagging in PvP Zones. + { + AttackStanceTaskManager.getInstance().addAttackStanceTask(player); + if (player.getSummon() != target) + { + if (((target.isRaid() && PvpConfig.FLAG_PLAYER_ON_RAID_ATTACK) || (target.isChampion() && PvpConfig.FLAG_PLAYER_ON_CHAMPION_ATTACK))) + { + player.updatePvPStatus(); + } + else + { + player.updatePvPStatus(target); + } + } + } + else if (PvpConfig.FLAG_PLAYER_ON_SUMMON_PET_RAID_CHAMPION_ATTACK && (isSummon() || isPet())) + { + final Player owner = isSummon() ? asSummon().getOwner() : asPet().getOwner(); + if ((owner != null) && !owner.isInsideZone(ZoneId.PVP) && (owner != target) && (owner.getSummon() != target)) + { + if (((target.isRaid() && PvpConfig.FLAG_PLAYER_ON_RAID_ATTACK) || (target.isChampion() && PvpConfig.FLAG_PLAYER_ON_CHAMPION_ATTACK))) + { + AttackStanceTaskManager.getInstance().addAttackStanceTask(owner); + owner.updatePvPStatus(); + } + } + } + + // Check if hit isn't missed. + if (!hitted) + { + abortAttack(); // Abort the attack of the Creature and send Server->Client ActionFailed packet. + } + else + { + // If we didn't miss the hit, discharge the shoulshots, if any. + setChargedShot(ShotType.SOULSHOTS, false); + if (player != null) + { + if (player.isCursedWeaponEquipped()) + { + // If hit by a cursed weapon, CP is reduced to 0. + if (!target.isInvul()) + { + target.setCurrentCp(0); + } + } + // If a cursed weapon is hit by a Hero, CP is reduced to 0. + else if (player.isHero() && target.isPlayer() && target.asPlayer().isCursedWeaponEquipped()) + { + target.setCurrentCp(0); + } + } + } + + // If the Server->Client packet Attack contains at least 1 hit, send the Server->Client packet Attack. + // to the Creature AND to all Player in the _KnownPlayers of the Creature. + if (attack.hasHits()) + { + broadcastPacket(attack); + } + + // Notify AI with READY_TO_ACT + ThreadPool.schedule(() -> getAI().notifyActionReadyToAct(), timeAtk + reuse); + } + finally + { + _attackLock.unlockWrite(stamp); + } + } + + /** + * Launch a Bow attack.
    + *
    + * Actions: + *
      + *
    • Calculate if hit is missed or not
    • + *
    • Consume arrows
    • + *
    • If hit isn't missed, calculate if shield defense is efficient
    • + *
    • If hit isn't missed, calculate if hit is critical
    • + *
    • If hit isn't missed, calculate physical damages
    • + *
    • If the Creature is a Player, Send a Server->Client packet SetupGauge
    • + *
    • Create a new hit task with Medium priority
    • + *
    • Calculate and set the disable delay of the bow in function of the Attack Speed
    • + *
    • Add this hit to the Server-Client packet Attack
    • + *
    + * @param attack Server->Client packet Attack in which the hit will be added + * @param target The Creature targeted + * @param sAtk The Attack Speed of the attacker + * @param reuse + * @return True if the hit isn't missed + */ + private boolean doAttackHitByBow(Attack attack, Creature target, int sAtk, int reuse) + { + int damage1 = 0; + byte shld1 = 0; + boolean crit1 = false; + + // Calculate if hit is missed or not. + final boolean miss1 = Formulas.calcHitMiss(this, target); + + // Consume arrows. + reduceArrowCount(); + + _move = null; + + // Check if hit isn't missed. + if (!miss1) + { + // Calculate if shield defense is efficient. + shld1 = Formulas.calcShldUse(this, target); + + // Calculate if hit is critical. + crit1 = Formulas.calcCrit(this, target); + + // Calculate physical damages. + damage1 = (int) Formulas.calcPhysDam(this, target, null, shld1, crit1, attack.hasSoulshot()); + + // Bows Ranged Damage Formula (Damage gradually decreases when 60% or lower than full hit range, and increases when 60% or higher). + // Full hit range is 500 which is the base bow range, and the 60% of this is 800. + if (PlayerConfig.CALCULATE_DISTANCE_BOW_DAMAGE) + { + damage1 *= (calculateDistance3D(target) / 4000) + 0.8; + } + } + + // Check if the Creature is a Player. + if (isPlayer()) + { + sendPacket(SystemMessageId.YOU_CAREFULLY_NOCK_AN_ARROW); + sendPacket(new SetupGauge(getObjectId(), SetupGauge.RED, sAtk + reuse)); + } + + // Create a new hit task with Medium priority. + ThreadPool.schedule(new HitTask(this, target, damage1, crit1, miss1, shld1, attack.hasSoulshot(), true), sAtk); + + // Calculate and set the disable delay of the bow in function of the Attack Speed. + final int gameTime = GameTimeTaskManager.getInstance().getGameTicks(); + _disableBowAttackEndTime = gameTime + ((sAtk + reuse) / GameTimeTaskManager.MILLIS_IN_TICK); + + // Precaution. It happened in the past for _attackEndTime. Will not risk it. + if (_disableBowAttackEndTime < gameTime) + { + _disableBowAttackEndTime = Integer.MAX_VALUE; + } + + // Add this hit to the Server-Client packet Attack. + attack.addHit(target, damage1, miss1, crit1, shld1); + + // Return true if hit isn't missed. + return !miss1; + } + + /** + * Launch a Dual attack.
    + *
    + * Actions: + *
      + *
    • Calculate if hits are missed or not
    • + *
    • If hits aren't missed, calculate if shield defense is efficient
    • + *
    • If hits aren't missed, calculate if hit is critical
    • + *
    • If hits aren't missed, calculate physical damages
    • + *
    • Create 2 new hit tasks with Medium priority
    • + *
    • Add those hits to the Server-Client packet Attack
    • + *
    + * @param attack Server->Client packet Attack in which the hit will be added + * @param target The Creature targeted + * @param sAtk + * @return True if hit 1 or hit 2 isn't missed + */ + private boolean doAttackHitByDual(Attack attack, Creature target, int sAtk) + { + int damage1 = 0; + int damage2 = 0; + byte shld1 = 0; + byte shld2 = 0; + boolean crit1 = false; + boolean crit2 = false; + + // Calculate if hits are missed or not. + final boolean miss1 = Formulas.calcHitMiss(this, target); + final boolean miss2 = Formulas.calcHitMiss(this, target); + + // Check if hit 1 isn't missed. + if (!miss1) + { + // Calculate if shield defense is efficient against hit 1. + shld1 = Formulas.calcShldUse(this, target); + + // Calculate if hit 1 is critical. + crit1 = Formulas.calcCrit(this, target); + + // Calculate physical damages of hit 1. + damage1 = (int) Formulas.calcPhysDam(this, target, null, shld1, crit1, attack.hasSoulshot()); + damage1 /= 2; + } + + // Check if hit 2 isn't missed. + if (!miss2) + { + // Calculate if shield defense is efficient against hit 2. + shld2 = Formulas.calcShldUse(this, target); + + // Calculate if hit 2 is critical. + crit2 = Formulas.calcCrit(this, target); + + // Calculate physical damages of hit 2. + damage2 = (int) Formulas.calcPhysDam(this, target, null, shld2, crit2, attack.hasSoulshot()); + damage2 /= 2; + } + + // Create a new hit task with Medium priority for hit 1. + ThreadPool.schedule(new HitTask(this, target, damage1, crit1, miss1, shld1, attack.hasSoulshot(), true), sAtk / 2); + + // Create a new hit task with Medium priority for hit 2 with a higher delay. + ThreadPool.schedule(new HitTask(this, target, damage2, crit2, miss2, shld2, attack.hasSoulshot(), false), sAtk); + + // Add those hits to the Server-Client packet Attack. + attack.addHit(target, damage1, miss1, crit1, shld1); + attack.addHit(target, damage2, miss2, crit2, shld2); + + // Return true if hit 1 or hit 2 isn't missed. + return !miss1 || !miss2; + } + + /** + * Launch a Pole attack.
    + *
    + * Actions: + *
      + *
    • Get all visible objects in a spherical area near the Creature to obtain possible targets
    • + *
    • If possible target is the Creature targeted, launch a simple attack against it
    • + *
    • If possible target isn't the Creature targeted but is attackable, launch a simple attack against it
    • + *
    + * @param attack Server->Client packet Attack in which the hit will be added + * @param target + * @param sAtk + * @return True if one hit isn't missed + */ + private boolean doAttackHitByPole(Attack attack, Creature target, int sAtk) + { + // Perform the main target hit. + boolean hitted = doAttackHitSimple(attack, target, 100, sAtk, true); + + if (!isAffected(EffectFlag.POLEARM_SINGLE_TARGET)) + { + // Without Polearm Mastery (skill 216) max simultaneous attacks is 3 (1 by default + 2 in skill 3599). + int attackCountMax = (int) _stat.calcStat(Stat.ATTACK_COUNT_MAX, 1, null, null); + if (attackCountMax > 1) + { + final double headingAngle = LocationUtil.convertHeadingToDegree(getHeading()); + final int maxRadius = _stat.getPhysicalAttackRange(); + final int physicalAttackAngle = _stat.getPhysicalAttackAngle(); + double attackpercent = 85; + for (Creature obj : World.getVisibleObjectsInRange(this, Creature.class, maxRadius)) + { + // Skip main target. + if (obj == target) + { + continue; + } + + // Skip dead or fake dead target. + if (obj.isAlikeDead()) + { + continue; + } + + // Check if target is auto attackable. + if (!obj.isAutoAttackable(this)) + { + continue; + } + + // Check if target is within attack angle. + if (Math.abs(calculateDirectionTo(obj) - headingAngle) > physicalAttackAngle) + { + continue; + } + + if (obj.isPet() && isPlayer() && (obj.asPet().getOwner() == asPlayer())) + { + continue; + } + + if (isAttackable() && obj.isPlayer() && _target.isAttackable()) + { + continue; + } + + if (isAttackable() && obj.isAttackable() && !asAttackable().isChaos()) + { + continue; + } + + // Launch a simple attack against the additional target. + hitted |= doAttackHitSimple(attack, obj, attackpercent, sAtk, false); + attackpercent /= 1.15; + if (--attackCountMax <= 0) + { + break; + } + } + } + } + + // Return true if one hit isn't missed. + return hitted; + } + + /** + * Launch a simple attack.
    + *
    + * Actions: + *
      + *
    • Calculate if hit is missed or not
    • + *
    • If hit isn't missed, calculate if shield defense is efficient
    • + *
    • If hit isn't missed, calculate if hit is critical
    • + *
    • If hit isn't missed, calculate physical damages
    • + *
    • Create a new hit task with Medium priority
    • + *
    • Add this hit to the Server-Client packet Attack
    • + *
    + * @param attack Server->Client packet Attack in which the hit will be added + * @param target The Creature targeted + * @param sAtk + * @return True if the hit isn't missed + */ + private boolean doAttackHitSimple(Attack attack, Creature target, int sAtk) + { + return doAttackHitSimple(attack, target, 100, sAtk, true); + } + + private boolean doAttackHitSimple(Attack attack, Creature target, double attackpercent, int sAtk, boolean rechargeShots) + { + int damage1 = 0; + byte shld1 = 0; + boolean crit1 = false; + + // Calculate if hit is missed or not. + final boolean miss1 = Formulas.calcHitMiss(this, target); + + // Check if hit isn't missed. + if (!miss1) + { + // Calculate if shield defense is efficient. + shld1 = Formulas.calcShldUse(this, target); + + // Calculate if hit is critical. + crit1 = Formulas.calcCrit(this, target); + + // Calculate physical damages + damage1 = (int) Formulas.calcPhysDam(this, target, null, shld1, crit1, attack.hasSoulshot()); + if (attackpercent != 100) + { + damage1 = (int) ((damage1 * attackpercent) / 100); + } + } + + // Create a new hit task with Medium priority. + ThreadPool.schedule(new HitTask(this, target, damage1, crit1, miss1, shld1, attack.hasSoulshot(), rechargeShots), sAtk); + + // Add this hit to the Server-Client packet Attack. + attack.addHit(target, damage1, miss1, crit1, shld1); + + // Return true if hit isn't missed. + return !miss1; + } + + /** + * Manage the casting task (casting and interrupt time, re-use delay...) and display the casting bar and animation on client.
    + *
    + * Actions: + *
      + *
    • Verify the possibility of the the cast : skill is a spell, caster isn't muted...
    • + *
    • Get the list of all targets (ex : area effects) and define the Creature targeted (its stats will be used in calculation)
    • + *
    • Calculate the casting time (base + modifier of MAtkSpd), interrupt time and re-use delay
    • + *
    • Send a Server->Client packet MagicSkillUser (to display casting animation), a packet SetupGauge (to display casting bar) and a system message
    • + *
    • Disable all skills during the casting time (create a task EnableAllSkills)
    • + *
    • Disable the skill during the re-use delay (create a task EnableSkill)
    • + *
    • Create a task MagicUseTask (that will call method onMagicUseTimer) to launch the Magic Skill at the end of the casting time
    • + *
    + * @param skill The Skill to use + */ + public void doCast(Skill skill) + { + beginCast(skill, false); + } + + public void doSimultaneousCast(Skill skill) + { + beginCast(skill, true); + } + + public void doCast(Skill skill, Creature target, List targets) + { + if (!checkDoCastConditions(skill)) + { + setCastingNow(false); + return; + } + + // Override casting type + if (skill.isSimultaneousCast()) + { + doSimultaneousCast(skill, target, targets); + return; + } + + stopEffectsOnAction(); + + // Recharge AutoSoulShot + // this method should not used with Playable + beginCast(skill, false, target, targets); + } + + public void doSimultaneousCast(Skill skill, Creature target, List targets) + { + if (!checkDoCastConditions(skill)) + { + setCastingSimultaneouslyNow(false); + return; + } + + stopEffectsOnAction(); + + beginCast(skill, true, target, targets); + } + + private void beginCast(Skill skill, boolean isSimultaneous) + { + // Attackables cannot cast while moving. + if (isAttackable() && isMoving()) + { + return; + } + + if (!checkDoCastConditions(skill)) + { + if (isSimultaneous) + { + setCastingSimultaneouslyNow(false); + } + else + { + setCastingNow(false); + } + + if (isPlayer()) + { + getAI().setIntentionActive(); + } + return; + } + + // Override casting type + boolean simultaneously = isSimultaneous; + if (skill.isSimultaneousCast() && !simultaneously) + { + simultaneously = true; + } + + stopEffectsOnAction(); + + // Set the target of the skill in function of Skill Type and Target Type. + Creature target = null; + + // Get all possible targets of the skill in a table in function of the skill target type. + final List targets = skill.getTargetList(this); + boolean doit = false; + + // AURA skills should always be using caster as target. + switch (skill.getTargetType()) + { + case AREA_SUMMON: // We need it to correct facing + { + target = isPlayer() ? asPlayer().getSummon() : null; + break; + } + case AURA: + case AURA_CORPSE_MOB: + case FRONT_AURA: + case BEHIND_AURA: + case GROUND: + case AURA_FRIENDLY: + { + target = this; + break; + } + case SELF: + case PET: + case SERVITOR: + case SUMMON: + case OWNER_PET: + case PARTY: + case CLAN: + case PARTY_CLAN: + case COMMAND_CHANNEL: + { + doit = true; + // Fallthrough. + } + default: + { + if (targets.isEmpty()) + { + if (simultaneously) + { + setCastingSimultaneouslyNow(false); + } + else + { + setCastingNow(false); + } + + // Send a Server->Client packet ActionFailed to the Player. + if (isPlayer()) + { + sendPacket(ActionFailed.STATIC_PACKET); + getAI().setIntentionActive(); + } + return; + } + + if ((skill.isContinuous() && !skill.isDebuff()) || skill.hasEffectType(EffectType.CPHEAL, EffectType.HEAL)) + { + doit = true; + } + + if (doit) + { + target = targets.get(0).asCreature(); + } + else + { + target = _target != null ? _target.asCreature() : null; + } + } + } + + beginCast(skill, simultaneously, target, targets); + } + + private void beginCast(Skill skill, boolean simultaneously, Creature target, List targets) + { + if (target == null) + { + if (simultaneously) + { + setCastingSimultaneouslyNow(false); + } + else + { + setCastingNow(false); + } + + if (isPlayer()) + { + sendPacket(ActionFailed.STATIC_PACKET); + getAI().setIntentionActive(); + } + return; + } + + if (EventDispatcher.getInstance().hasListener(EventType.ON_CREATURE_SKILL_USE, this)) + { + if (_onCreatureSkillUse == null) + { + _onCreatureSkillUse = new OnCreatureSkillUse(); + } + + _onCreatureSkillUse.setCaster(this); + _onCreatureSkillUse.setSkill(skill); + _onCreatureSkillUse.setSimultaneously(simultaneously); + _onCreatureSkillUse.setTarget(target); + _onCreatureSkillUse.setTargets(targets); + final TerminateReturn term = EventDispatcher.getInstance().notifyEvent(_onCreatureSkillUse, this, TerminateReturn.class); + if ((term != null) && term.terminate()) + { + if (simultaneously) + { + setCastingSimultaneouslyNow(false); + } + else + { + setCastingNow(false); + } + + if (isPlayer()) + { + sendPacket(ActionFailed.STATIC_PACKET); + getAI().setIntentionActive(); + } + return; + } + } + + // TODO: Unhardcode using event listeners! + if (skill.hasEffectType(EffectType.RESURRECTION) && (isResurrectionBlocked() || target.isResurrectionBlocked())) + { + sendPacket(SystemMessageId.REJECT_RESURRECTION); // Reject resurrection + target.sendPacket(SystemMessageId.REJECT_RESURRECTION); // Reject resurrection + if (simultaneously) + { + setCastingSimultaneouslyNow(false); + } + else + { + setCastingNow(false); + } + + if (isPlayer()) + { + getAI().setIntentionActive(); + sendPacket(ActionFailed.STATIC_PACKET); + } + return; + } + + // Get the Base Casting Time of the Skills. + int skillTime = skill.getHitTime() + skill.getCoolTime(); + if (!skill.isChanneling() || (skill.getChannelingSkillId() == 0)) + { + // Calculate the Casting Time of the "Non-Static" Skills (with caster PAtk/MAtkSpd). + if (!skill.isStatic()) + { + skillTime = Formulas.calcAtkSpd(this, skill, skillTime); + } + + // Calculate the Casting Time of Magic Skills (reduced in 40% if using SPS/BSPS). + if (skill.isMagic() && (isChargedShot(ShotType.SPIRITSHOTS) || isChargedShot(ShotType.BLESSED_SPIRITSHOTS))) + { + skillTime = (int) (0.7 * skillTime); + } + } + + // Avoid broken Casting Animation. + // Client can't handle less than 550ms Casting Animation in Magic Skills with more than 550ms base. + if (skill.isMagic() && ((skill.getHitTime() + skill.getCoolTime()) > 550) && (skillTime < 550)) + { + skillTime = 550; + } + // Client can't handle less than 500ms Casting Animation in Physical Skills with 500ms base or more. + else if (!skill.isStatic() && ((skill.getHitTime() + skill.getCoolTime()) >= 500) && (skillTime < 500)) + { + skillTime = 500; + } + + // Queue herbs and potions. + if (_isCastingSimultaneouslyNow && simultaneously) + { + ThreadPool.schedule(() -> beginCast(skill, simultaneously, target, targets), 100); + return; + } + + // Set the _castInterruptTime and casting status (Player already has this true). + if (simultaneously) + { + setCastingSimultaneouslyNow(true); + } + else + { + setCastingNow(true); + } + + if (!simultaneously) + { + _castInterruptTime = -2 + GameTimeTaskManager.getInstance().getGameTicks() + (skillTime / GameTimeTaskManager.MILLIS_IN_TICK); + setLastSkillCast(skill); + } + else + { + setLastSimultaneousSkillCast(skill); + } + + // Calculate the Reuse Time of the Skill. + int reuseDelay; + if (skill.isStaticReuse() || skill.isStatic()) + { + reuseDelay = skill.getReuseDelay(); + } + else if (skill.isMagic()) + { + reuseDelay = (int) (skill.getReuseDelay() * calcStat(Stat.MAGIC_REUSE_RATE, 1, null, null)); + reuseDelay *= 333f / getMAtkSpd(); // Interlude adjustment. + } + else if (skill.isPhysical()) + { + reuseDelay = (int) (skill.getReuseDelay() * calcStat(Stat.P_REUSE, 1, null, null)); + reuseDelay *= 333f / getPAtkSpd(); // Interlude adjustment. + } + else + { + reuseDelay = (int) (skill.getReuseDelay() * calcStat(Stat.DANCE_REUSE, 1, null, null)); + reuseDelay *= 333f / getPAtkSpd(); // Interlude adjustment. + } + + if (isPlayable()) + { + reuseDelay = (int) (reuseDelay * ClassBalanceConfig.SKILL_REUSE_MULTIPLIERS[asPlayer().getPlayerClass().getId()]); + } + + // Check if this skill consume mp on start casting. + final int initmpcons = _stat.getMpInitialConsume(skill); + if (initmpcons > 0) + { + _status.reduceMp(initmpcons); + final StatusUpdate su = new StatusUpdate(this); + su.addAttribute(StatusUpdate.CUR_MP, (int) _status.getCurrentMp()); + sendPacket(su); + } + + // Disable the skill during the re-use delay and create a task EnableSkill with Medium priority to enable it at the end of the re-use delay. + if (reuseDelay > 10) + { + if (Formulas.calcSkillMastery(this, skill)) + { + reuseDelay = 100; + if (isPlayable()) + { + sendPacket(SystemMessageId.A_SKILL_IS_READY_TO_BE_USED_AGAIN); + } + } + + if (reuseDelay > 1000) + { + addTimeStamp(skill, reuseDelay); + } + else + { + disableSkill(skill, reuseDelay); + } + } + + // Make sure that char is facing selected target. + if (target != this) + { + setHeading(LocationUtil.calculateHeadingFrom(this, target)); + } + + if (isPlayable()) + { + if ((skill.getItemConsumeId() > 0) && !destroyItemByItemId(null, skill.getItemConsumeId(), skill.getItemConsumeCount(), null, true)) + { + sendPacket(SystemMessageId.INCORRECT_ITEM_COUNT_2); + abortCast(); + return; + } + } + + if (!skill.isToggle()) + { + // Send a Server->Client packet MagicSkillUser with target, displayId, level, skillTime, reuseDelay. + // to the Creature AND to all Player in the _KnownPlayers of the Creature. + broadcastSkillPacket(new MagicSkillUse(this, target, skill.getDisplayId(), skill.getDisplayLevel(), skillTime, reuseDelay), target); + broadcastSkillPacket(new MagicSkillLaunched(this, skill.getDisplayId(), skill.getDisplayLevel(), targets), targets); + } + + // Send a system message to the player. + if (isPlayer() && !skill.isAbnormalInstant()) + { + SystemMessage sm = null; + switch (skill.getId()) + { + case 1312: // Fishing + { + // Done in Player.startFishing() + break; + } + case 2046: // Wolf Collar + { + sm = new SystemMessage(SystemMessageId.SUMMONING_YOUR_PET); + break; + } + default: + { + sm = new SystemMessage(SystemMessageId.USE_S1); + sm.addSkillName(skill); + break; + } + } + + if (sm != null) + { + sendPacket(sm); + } + } + + if (skill.hasEffects(EffectScope.START)) + { + skill.applyEffectScope(EffectScope.START, new BuffInfo(this, target, skill), true, false); + } + + final MagicUseTask mut = new MagicUseTask(this, targets, skill, skillTime, simultaneously); + + // launch the magic in skillTime milliseconds + if (skillTime > 0) + { + // Send a Server->Client packet SetupGauge with the color of the gauge and the casting time. + if (isPlayer() && !simultaneously) + { + sendPacket(new SetupGauge(getObjectId(), SetupGauge.BLUE, skillTime)); + } + + if (skill.isChanneling() && (skill.getChannelingSkillId() > 0)) + { + getSkillChannelizer().startChanneling(skill); + } + + if (simultaneously) + { + final Future future = _skillCast2; + if (future != null) + { + future.cancel(true); + _skillCast2 = null; + } + + // Create a task MagicUseTask to launch the MagicSkill at the end of the casting time (skillTime). + // For client animation reasons (party buffs especially) 400 ms before! + _skillCast2 = ThreadPool.schedule(mut, Math.max(0, skillTime - 400)); + } + else + { + final Future future = _skillCast; + if (future != null) + { + future.cancel(true); + _skillCast = null; + } + + // Create a task MagicUseTask to launch the MagicSkill at the end of the casting time (skillTime). + // For client animation reasons (party buffs especially) 400 ms before! + _skillCast = ThreadPool.schedule(mut, Math.max(0, skillTime - 400)); + } + } + else + { + mut.setSkillTime(0); + onMagicLaunchedTimer(mut); + } + } + + /** + * Check if casting of skill is possible + * @param skill + * @return True if casting is possible + */ + public boolean checkDoCastConditions(Skill skill) + { + if ((skill == null) || isSkillDisabled(skill)) + { + // Send a Server->Client packet ActionFailed to the Player. + sendPacket(ActionFailed.STATIC_PACKET); + return false; + } + + // Check if the caster has enough MP. + if (_status.getCurrentMp() < (_stat.getMpConsume(skill) + _stat.getMpInitialConsume(skill))) + { + // Send a System Message to the caster. + sendPacket(SystemMessageId.NOT_ENOUGH_MP); + + // Send a Server->Client packet ActionFailed to the Player. + sendPacket(ActionFailed.STATIC_PACKET); + return false; + } + + // Check if the caster has enough HP. + if (_status.getCurrentHp() <= skill.getHpConsume()) + { + // Send a System Message to the caster. + sendPacket(SystemMessageId.NOT_ENOUGH_HP); + + // Send a Server->Client packet ActionFailed to the Player. + sendPacket(ActionFailed.STATIC_PACKET); + return false; + } + + // Skill mute checks. + if (!skill.isStatic()) + { + // Check if the skill is a magic spell and if the Creature is not muted. + if (skill.isMagic()) + { + if (isMuted()) + { + // Send a Server->Client packet ActionFailed to the Player. + sendPacket(ActionFailed.STATIC_PACKET); + return false; + } + } + else + { + // Check if the skill is physical and if the Creature is not physical_muted. + if (isPhysicalMuted()) + { + // Send a Server->Client packet ActionFailed to the Player. + sendPacket(ActionFailed.STATIC_PACKET); + return false; + } + } + } + + // prevent casting signets to peace zone + if (skill.isChanneling() && (skill.getChannelingSkillId() > 0) && !isInInstance()) + { + final ZoneRegion zoneRegion = ZoneManager.getInstance().getRegion(this); + boolean canCast = true; + if ((skill.getTargetType() == TargetType.GROUND) && isPlayer()) + { + final Location wp = asPlayer().getCurrentSkillWorldPosition(); + if (!zoneRegion.checkEffectRangeInsidePeaceZone(skill, wp.getX(), wp.getY(), wp.getZ())) + { + canCast = false; + } + } + else if (!zoneRegion.checkEffectRangeInsidePeaceZone(skill, getX(), getY(), getZ())) + { + canCast = false; + } + + if (!canCast) + { + final SystemMessage sm = new SystemMessage(SystemMessageId.S1_CANNOT_BE_USED_DUE_TO_UNSUITABLE_TERMS); + sm.addSkillName(skill); + sendPacket(sm); + return false; + } + } + + // Check if the caster's weapon is limited to use only its own skills. + if (getActiveWeaponItem() != null) + { + final Weapon wep = getActiveWeaponItem(); + if (wep.useWeaponSkillsOnly() && !isGM() && wep.hasSkills()) + { + boolean found = false; + for (SkillHolder sh : wep.getSkills()) + { + if (sh.getSkillId() == skill.getId()) + { + found = true; + } + } + + if (!found) + { + if (asPlayer() != null) + { + sendMessage("That weapon cannot use any other skill except the weapon's skill."); + } + + return false; + } + } + } + + // Check if the spell consumes an Item. + // TODO: combine check and consume + if ((skill.getItemConsumeId() > 0) && (getInventory() != null)) + { + // Get the Item consumed by the spell. + final Item requiredItems = getInventory().getItemByItemId(skill.getItemConsumeId()); + + // Check if the caster owns enough consumed Item to cast. + if ((requiredItems == null) || (requiredItems.getCount() < skill.getItemConsumeCount())) + { + // Checked: when a summon skill failed, server show required consume item count. + if (skill.hasEffectType(EffectType.SUMMON)) + { + final SystemMessage sm = new SystemMessage(SystemMessageId.SUMMONING_A_SERVITOR_COSTS_S2_S1); + sm.addItemName(skill.getItemConsumeId()); + sm.addInt(skill.getItemConsumeCount()); + sendPacket(sm); + } + else + { + // Send a System Message to the caster. + sendMessage("There are not enough necessary items to use the skill."); + } + + return false; + } + } + + return true; + } + + /** + * Gets the item reuse time stamps map. + * @return the item reuse time stamps map + */ + public Map getItemReuseTimeStamps() + { + return _reuseTimeStampsItems; + } + + /** + * Adds a item reuse time stamp. + * @param item the item + * @param reuse the reuse + */ + public void addTimeStampItem(Item item, long reuse) + { + addTimeStampItem(item, reuse, -1); + } + + /** + * Adds a item reuse time stamp.
    + * Used for restoring purposes. + * @param item the item + * @param reuse the reuse + * @param systime the system time + */ + public void addTimeStampItem(Item item, long reuse, long systime) + { + _reuseTimeStampsItems.put(item.getObjectId(), new TimeStamp(item, reuse, systime)); + } + + /** + * Gets the item remaining reuse time for a given item object ID. + * @param itemObjId the item object ID + * @return if the item has a reuse time stamp, the remaining time, otherwise -1 + */ + public long getItemRemainingReuseTime(int itemObjId) + { + final TimeStamp reuseStamp = _reuseTimeStampsItems.get(itemObjId); + return reuseStamp != null ? reuseStamp.getRemaining() : -1; + } + + /** + * Gets the item remaining reuse time for a given shared reuse item group. + * @param group the shared reuse item group + * @return if the shared reuse item group has a reuse time stamp, the remaining time, otherwise -1 + */ + public long getReuseDelayOnGroup(int group) + { + if ((group > 0) && !_reuseTimeStampsItems.isEmpty()) + { + final long currentTime = System.currentTimeMillis(); + for (TimeStamp ts : _reuseTimeStampsItems.values()) + { + if (ts.getSharedReuseGroup() == group) + { + final long stamp = ts.getStamp(); + if (currentTime < stamp) + { + return Math.max(stamp - currentTime, 0); + } + } + } + } + + return -1; + } + + /** + * Gets the skill reuse time stamps map. + * @return the skill reuse time stamps map + */ + public Map getSkillReuseTimeStamps() + { + return _reuseTimeStampsSkills; + } + + /** + * Adds the skill reuse time stamp. + * @param skill the skill + * @param reuse the delay + */ + public void addTimeStamp(Skill skill, long reuse) + { + addTimeStamp(skill, reuse, -1); + } + + /** + * Adds the skill reuse time stamp.
    + * Used for restoring purposes. + * @param skill the skill + * @param reuse the reuse + * @param systime the system time + */ + public void addTimeStamp(Skill skill, long reuse, long systime) + { + _reuseTimeStampsSkills.put(skill.getReuseHashCode(), new TimeStamp(skill, reuse, systime)); + } + + /** + * Removes a skill reuse time stamp. + * @param skill the skill to remove + */ + public void removeTimeStamp(Skill skill) + { + _reuseTimeStampsSkills.remove(skill.getReuseHashCode()); + } + + /** + * Removes all skill reuse time stamps. + */ + public void resetTimeStamps() + { + _reuseTimeStampsSkills.clear(); + } + + /** + * Gets the skill remaining reuse time for a given skill hash code. + * @param hashCode the skill hash code + * @return if the skill has a reuse time stamp, the remaining time, otherwise -1 + */ + public long getSkillRemainingReuseTime(int hashCode) + { + final TimeStamp reuseStamp = _reuseTimeStampsSkills.get(hashCode); + return reuseStamp != null ? reuseStamp.getRemaining() : -1; + } + + /** + * Verifies if the skill is under reuse time. + * @param hashCode the skill hash code + * @return {@code true} if the skill is under reuse time, {@code false} otherwise + */ + public boolean hasSkillReuse(int hashCode) + { + final TimeStamp reuseStamp = _reuseTimeStampsSkills.get(hashCode); + return (reuseStamp != null) && reuseStamp.hasNotPassed(); + } + + /** + * Gets the skill reuse time stamp. + * @param hashCode the skill hash code + * @return if the skill has a reuse time stamp, the skill reuse time stamp, otherwise {@code null} + */ + public TimeStamp getSkillReuseTimeStamp(int hashCode) + { + return _reuseTimeStampsSkills.get(hashCode); + } + + /** + * Gets the disabled skills map. + * @return the disabled skills map + */ + public Map getDisabledSkills() + { + return _disabledSkills; + } + + /** + * Enables a skill. + * @param skill the skill to enable + */ + public void enableSkill(Skill skill) + { + if (skill == null) + { + return; + } + + _disabledSkills.remove(skill.getReuseHashCode()); + } + + /** + * Disables a skill for a given time.
    + * If delay is lesser or equal than zero, skill will be disabled "forever". + * @param skill the skill to disable + * @param delay delay in milliseconds + */ + public void disableSkill(Skill skill, long delay) + { + if (skill == null) + { + return; + } + + _disabledSkills.put(skill.getReuseHashCode(), delay > 0 ? System.currentTimeMillis() + delay : Long.MAX_VALUE); + } + + /** + * Removes all the disabled skills. + */ + public void resetDisabledSkills() + { + _disabledSkills.clear(); + } + + /** + * Verifies if the skill is disabled. + * @param skill the skill + * @return {@code true} if the skill is disabled, {@code false} otherwise + */ + public boolean isSkillDisabled(Skill skill) + { + if (skill == null) + { + return false; + } + + if (isAllSkillsDisabled()) + { + return true; + } + + final int hashCode = skill.getReuseHashCode(); + if (hasSkillReuse(hashCode)) + { + return true; + } + + if (_disabledSkills.isEmpty()) + { + return false; + } + + final Long stamp = _disabledSkills.get(hashCode); + if (stamp == null) + { + return false; + } + + if (stamp < System.currentTimeMillis()) + { + _disabledSkills.remove(hashCode); + return false; + } + + return true; + } + + /** + * Disables all skills. + */ + public void disableAllSkills() + { + _allSkillsDisabled = true; + } + + /** + * Enables all skills, except those under reuse time or previously disabled. + */ + public void enableAllSkills() + { + _allSkillsDisabled = false; + } + + /** + * Kill the Creature.
    + *
    + * Actions: + *
      + *
    • Set target to null and cancel Attack or Cast
    • + *
    • Stop movement
    • + *
    • Stop HP/MP/CP Regeneration task
    • + *
    • Stop all active skills effects in progress on the Creature
    • + *
    • Send the Server->Client packet StatusUpdate with current HP and MP to all other Player to inform
    • + *
    • Notify Creature AI
    • + *
    + * @param killer The Creature who killed it + * @return false if the player is already dead. + */ + public boolean doDie(Creature killer) + { + if (CaptchaConfig.ENABLE_CAPTCHA) + { + CaptchaManager.getInstance().updateCounter(killer, this); + } + + // Killing is only possible one time. + synchronized (this) + { + if (_isDead) + { + return false; + } + + // Now reset currentHp to zero. + setCurrentHp(0); + setDead(true); + } + + if (EventDispatcher.getInstance().hasListener(EventType.ON_CREATURE_DEATH, this)) + { + EventDispatcher.getInstance().notifyEvent(new OnCreatureDeath(killer, this), this); + } + + if (EventDispatcher.getInstance().hasListener(EventType.ON_CREATURE_KILLED, this)) + { + final TerminateReturn returnBack = EventDispatcher.getInstance().notifyEvent(new OnCreatureKilled(killer, this), this, TerminateReturn.class); + if ((returnBack != null) && returnBack.terminate()) + { + return false; + } + } + + // Calculate rewards for main damage dealer. + final Creature mainDamageDealer = isMonster() ? asMonster().getMainDamageDealer() : null; + calculateRewards(mainDamageDealer != null ? mainDamageDealer : killer); + + // Set target to null and cancel Attack or Cast. + setTarget(null); + + // Stop movement + stopMove(null); + + // Stop HP/MP/CP Regeneration task. + _status.stopHpMpRegeneration(); + + if (isAttackable()) + { + final Spawn spawn = asNpc().getSpawn(); + if ((spawn != null) && spawn.isRespawnEnabled()) + { + stopAllEffects(); + } + else + { + _effectList.stopAllEffectsWithoutExclusions(true, true); + } + + // Clan help range aggro on kill. + if ((killer != null) && killer.isPlayable() && !killer.asPlayer().isGM()) + { + final NpcTemplate template = asAttackable().getTemplate(); + final Set clans = template.getClans(); + if ((clans != null) && !clans.isEmpty()) + { + World.forEachVisibleObjectInRange(this, Attackable.class, template.getClanHelpRange(), called -> + { + // Don't call dead npcs, npcs without ai or npcs which are too far away. + if (called.isDead() || !called.hasAI() || (Math.abs(killer.getZ() - called.getZ()) > 600)) + { + return; + } + + // Don't call npcs spawned within the last 7 seconds. + if ((System.currentTimeMillis() - called.getSpawnTime()) < 7000) + { + return; + } + + // Don't call npcs who are already doing some action (e.g. attacking, casting). + if ((called.getAI().getIntention() != Intention.IDLE) && (called.getAI().getIntention() != Intention.ACTIVE)) + { + return; + } + + // Don't call npcs who aren't in the same clan. + if (!template.isClan(called.getTemplate().getClans())) + { + return; + } + + // By default, when a faction member calls for help, attack the caller's attacker. + called.getAI().notifyActionAggression(killer, 1); + + if (EventDispatcher.getInstance().hasListener(EventType.ON_ATTACKABLE_FACTION_CALL, called)) + { + EventDispatcher.getInstance().notifyEventAsync(new OnAttackableFactionCall(called, asAttackable(), killer.asPlayer(), killer.isSummon()), called); + } + }); + } + } + } + else + { + stopAllEffectsExceptThoseThatLastThroughDeath(); + } + + // Send the Server->Client packet StatusUpdate with current HP and MP to all other Player to inform. + broadcastStatusUpdate(); + + // Notify Creature AI + if (hasAI()) + { + getAI().notifyActionDeath(); + } + + ZoneManager.getInstance().getRegion(this).onDeath(this); + + getAttackByList().clear(); + + if (isChannelized()) + { + getSkillChannelized().abortChannelization(); + } + + // Custom boss announcements configuration. + if (this instanceof GrandBoss) + { + if (BossAnnouncementsConfig.GRANDBOSS_DEFEAT_ANNOUNCEMENTS && (!isInInstance() || BossAnnouncementsConfig.GRANDBOSS_INSTANCE_ANNOUNCEMENTS) && !isMinion() && !isRaidMinion()) + { + final String name = NpcData.getInstance().getTemplate(getId()).getName(); + if ((name != null) && !BossAnnouncementsConfig.RAIDBOSSES_EXCLUDED_FROM_DEFEAT_ANNOUNCEMENTS.contains(getId())) + { + World.broadcastToAllOnlinePlayers(name + " has been defeated!"); + World.broadcastToAllOnlinePlayersOnScreen(name + " has been defeated!"); + } + } + } + else if (isRaid() && BossAnnouncementsConfig.RAIDBOSS_DEFEAT_ANNOUNCEMENTS && (!isInInstance() || BossAnnouncementsConfig.RAIDBOSS_INSTANCE_ANNOUNCEMENTS) && !isMinion() && !isRaidMinion()) + { + final String name = NpcData.getInstance().getTemplate(getId()).getName(); + if ((name != null) && !BossAnnouncementsConfig.RAIDBOSSES_EXCLUDED_FROM_DEFEAT_ANNOUNCEMENTS.contains(getId())) + { + World.broadcastToAllOnlinePlayers(name + " has been defeated!"); + World.broadcastToAllOnlinePlayersOnScreen(name + " has been defeated!"); + } + } + + return true; + } + + @Override + public boolean decayMe() + { + if (hasAI()) + { + if (isAttackable()) + { + getAttackByList().clear(); + asAttackable().clearAggroList(); + getAI().setIntentionIdle(); + } + + getAI().stopAITask(); + } + + // Enable AI. + _disabledAI = false; + + _onCreatureAttack = null; + _onCreatureAttacked = null; + _onCreatureDamageDealt = null; + _onCreatureDamageReceived = null; + _onCreatureAttackAvoid = null; + _onCreatureSkillUse = null; + + return super.decayMe(); + } + + public boolean deleteMe() + { + if (hasAI()) + { + getAI().stopAITask(); + } + + // Remove all effects, do not broadcast changes. + _effectList.stopAllEffectsWithoutExclusions(false, false); + + // Forget all seen creatures. + if (_seenCreatures != null) + { + CreatureSeeTaskManager.getInstance().remove(this); + _seenCreatures.clear(); + } + + // Cancel the BuffFinishTask related to this creature. + _buffFinishTask.stop(); + + // Set world region to null. + setWorldRegion(null); + + return true; + } + + public void detachAI() + { + if (isWalker()) + { + return; + } + + setAI(null); + } + + protected void calculateRewards(Creature killer) + { + } + + /** Sets HP, MP and CP and revives the Creature. */ + public void doRevive() + { + if (!_isDead) + { + return; + } + + if (!_isTeleporting) + { + setIsPendingRevive(false); + setDead(false); + + if ((PlayerConfig.RESPAWN_RESTORE_CP > 0) && (_status.getCurrentCp() < (_stat.getMaxCp() * PlayerConfig.RESPAWN_RESTORE_CP))) + { + _status.setCurrentCp(_stat.getMaxCp() * PlayerConfig.RESPAWN_RESTORE_CP); + } + + if ((PlayerConfig.RESPAWN_RESTORE_HP > 0) && (_status.getCurrentHp() < (_stat.getMaxHp() * PlayerConfig.RESPAWN_RESTORE_HP))) + { + _status.setCurrentHp(_stat.getMaxHp() * PlayerConfig.RESPAWN_RESTORE_HP); + } + + if ((PlayerConfig.RESPAWN_RESTORE_MP > 0) && (_status.getCurrentMp() < (_stat.getMaxMp() * PlayerConfig.RESPAWN_RESTORE_MP))) + { + _status.setCurrentMp(_stat.getMaxMp() * PlayerConfig.RESPAWN_RESTORE_MP); + } + + // Start broadcast status + broadcastPacket(new Revive(this)); + ZoneManager.getInstance().getRegion(this).onRevive(this); + } + else + { + setIsPendingRevive(true); + } + } + + /** + * Revives the Creature using skill. + * @param revivePower + */ + public void doRevive(double revivePower) + { + doRevive(); + } + + /** + * Gets this creature's AI. + * @return the AI + */ + public CreatureAI getAI() + { + CreatureAI ai = _ai; + if (ai == null) + { + synchronized (this) + { + ai = _ai; + if (ai == null) + { + _ai = ai = initAI(); + } + } + } + + return ai; + } + + /** + * Initialize this creature's AI.
    + * OOP approach to be overridden in child classes. + * @return the new AI + */ + protected CreatureAI initAI() + { + return new CreatureAI(this); + } + + public void setAI(CreatureAI newAI) + { + final CreatureAI oldAI = _ai; + if ((oldAI != null) && (oldAI != newAI) && (oldAI instanceof AttackableAI)) + { + oldAI.stopAITask(); + } + + _ai = newAI; + } + + /** + * Verifies if this creature has an AI, + * @return {@code true} if this creature has an AI, {@code false} otherwise + */ + public boolean hasAI() + { + return _ai != null; + } + + /** + * @return True if the Creature is RaidBoss or his minion. + */ + public boolean isRaid() + { + return false; + } + + /** + * @return True if the Creature is minion. + */ + public boolean isMinion() + { + return false; + } + + /** + * @return True if the Creature is minion of RaidBoss. + */ + public boolean isRaidMinion() + { + return false; + } + + /** + * @return a list of Creature that attacked. + */ + public Set getAttackByList() + { + return _attackByList; + } + + public Skill getLastSimultaneousSkillCast() + { + return _lastSimultaneousSkillCast; + } + + public void setLastSimultaneousSkillCast(Skill skill) + { + _lastSimultaneousSkillCast = skill; + } + + public Skill getLastSkillCast() + { + return _lastSkillCast; + } + + public void setLastSkillCast(Skill skill) + { + _lastSkillCast = skill; + } + + public boolean isControlBlocked() + { + return isAffected(EffectFlag.FEAR); + } + + /** + * @return True if the Creature can't use its skills (ex : stun, sleep...). + */ + public boolean isAllSkillsDisabled() + { + return _allSkillsDisabled || isStunned() || isSleeping() || isParalyzed(); + } + + /** + * @return True if the Creature can't attack (attackEndTime, attackMute, fake death, stun, sleep, paralyze). + */ + public boolean isAttackDisabled() + { + return isAttackingNow() || isDisabled(); + } + + /** + * @return True if the Creature is disabled (attackMute, fake death, stun, sleep, paralyze). + */ + public boolean isDisabled() + { + return _disabledAI || isAlikeDead() || isPhysicalAttackMuted() || isStunned() || isSleeping() || isParalyzed(); + } + + public Calculator[] getCalculators() + { + return _calculators; + } + + public boolean isConfused() + { + return isAffected(EffectFlag.CONFUSED); + } + + /** + * @return True if the Creature is dead or use fake death. + */ + public boolean isAlikeDead() + { + return _isDead; + } + + /** + * @return True if the Creature is dead. + */ + public boolean isDead() + { + return _isDead; + } + + public void setDead(boolean value) + { + _isDead = value; + } + + public boolean isImmobilized() + { + return _isImmobilized; + } + + public void setImmobilized(boolean value) + { + _isImmobilized = value; + } + + public boolean isMuted() + { + return isAffected(EffectFlag.MUTED); + } + + public boolean isPhysicalMuted() + { + return isAffected(EffectFlag.PSYCHICAL_MUTED); + } + + public boolean isPhysicalAttackMuted() + { + return isAffected(EffectFlag.PSYCHICAL_ATTACK_MUTED); + } + + /** + * @return True if the Creature can't move (stun, root, sleep, overload, paralyzed). + */ + public boolean isMovementDisabled() + { + // check for isTeleporting to prevent teleport cheating (if appear packet not received) + return isStunned() || isRooted() || isSleeping() || _isOverloaded || isParalyzed() || _isImmobilized || isAlikeDead() || _isTeleporting; + } + + /** + * @return True if the Creature can not be controlled by the player (confused, afraid). + */ + public boolean isOutOfControl() + { + return isConfused() || isControlBlocked(); + } + + public boolean isOverloaded() + { + return _isOverloaded; + } + + /** + * Set the overloaded status of the Creature is overloaded (if True, the Player can't take more item). + * @param value + */ + public void setOverloaded(boolean value) + { + _isOverloaded = value; + } + + public boolean isParalyzed() + { + return _isParalyzed || isAffected(EffectFlag.PARALYZED); + } + + public void setParalyzed(boolean value) + { + _isParalyzed = value; + } + + public boolean isPendingRevive() + { + return _isDead && _isPendingRevive; + } + + public void setIsPendingRevive(boolean value) + { + _isPendingRevive = value; + } + + public boolean isDisarmed() + { + return isAffected(EffectFlag.DISARMED); + } + + public boolean isRooted() + { + return isAffected(EffectFlag.ROOTED); + } + + /** + * @return True if the Creature is running. + */ + public boolean isRunning() + { + return _isRunning; + } + + private void setRunning(boolean value) + { + if (_isRunning == value) + { + return; + } + + _isRunning = value; + if (_stat.getRunSpeed() != 0) + { + broadcastPacket(new ChangeMoveType(this)); + } + + if (isPlayer()) + { + asPlayer().broadcastUserInfo(); + } + else if (isSummon()) + { + broadcastStatusUpdate(); + } + else if (isNpc()) + { + World.forEachVisibleObject(this, Player.class, player -> + { + if (!isVisibleFor(player)) + { + return; + } + + if (isFakePlayer()) + { + player.sendPacket(new FakePlayerInfo(asNpc())); + } + else if (_stat.getRunSpeed() == 0) + { + player.sendPacket(new ServerObjectInfo(asNpc(), player)); + } + else + { + player.sendPacket(new AbstractNpcInfo.NpcInfo(asNpc(), player)); + } + }); + } + } + + /** Set the Creature movement type to run and send Server->Client packet ChangeMoveType to all others Player. */ + public void setRunning() + { + setRunning(true); + } + + public boolean isSleeping() + { + return isAffected(EffectFlag.SLEEP); + } + + public boolean isStunned() + { + return isAffected(EffectFlag.STUNNED); + } + + public boolean isBetrayed() + { + return isAffected(EffectFlag.BETRAYED); + } + + public boolean isTeleporting() + { + return _isTeleporting; + } + + public void setTeleporting(boolean value) + { + _isTeleporting = value; + } + + public void setInvul(boolean value) + { + _isInvul = value; + } + + public boolean isInvul() + { + return _isInvul || _isTeleporting || isAffected(EffectFlag.INVUL); + } + + public void setMortal(boolean value) + { + _isMortal = value; + } + + public boolean isMortal() + { + return _isMortal; + } + + public boolean isUndead() + { + return false; + } + + public boolean isResurrectionBlocked() + { + return isAffected(EffectFlag.BLOCK_RESURRECTION); + } + + public boolean isFlying() + { + return _isFlying; + } + + public void setFlying(boolean mode) + { + _isFlying = mode; + } + + public CreatureStat getStat() + { + return _stat; + } + + /** + * Initializes the CharStat class of the WorldObject, is overwritten in classes that require a different CharStat Type.
    + * Removes the need for instanceof checks. + */ + public void initCharStat() + { + _stat = new CreatureStat(this); + } + + public void setStat(CreatureStat value) + { + _stat = value; + } + + public CreatureStatus getStatus() + { + return _status; + } + + /** + * Initializes the CharStatus class of the WorldObject, is overwritten in classes that require a different CharStatus Type.
    + * Removes the need for instanceof checks. + */ + public void initCharStatus() + { + _status = new CreatureStatus(this); + } + + public void setStatus(CreatureStatus value) + { + _status = value; + } + + public CreatureTemplate getTemplate() + { + return _template; + } + + /** + * Set the template of the Creature.
    + *
    + * Concept:
    + *
    + * Each Creature owns generic and static properties (ex : all Keltir have the same number of HP...).
    + * All of those properties are stored in a different template for each type of Creature.
    + * Each template is loaded once in the server cache memory (reduce memory use).
    + * When a new instance of Creature is spawned, server just create a link between the instance and the template This link is stored in _template. + * @param template + */ + protected void setTemplate(CreatureTemplate template) + { + _template = template; + } + + /** + * @return the Title of the Creature. + */ + public String getTitle() + { + return _title; + } + + /** + * Set the Title of the Creature. + * @param value + */ + public void setTitle(String value) + { + if (value == null) + { + _title = ""; + } + else + { + _title = isPlayer() && (value.length() > 21) ? value.substring(0, 20) : value; + } + } + + /** + * Set the Creature movement type to walk and send Server->Client packet ChangeMoveType to all others Player. + */ + public void setWalking() + { + setRunning(false); + } + + /** + * Gets the abnormal visual effects affecting this character. + * @return a map of 32 bits containing all abnormal visual effects in progress for this character + */ + public int getAbnormalVisualEffects() + { + return _abnormalVisualEffects; + } + + /** + * Gets the special abnormal visual effects affecting this character. + * @return a map of 32 bits containing all special effect in progress for this character + */ + public int getAbnormalVisualEffectSpecial() + { + return _abnormalVisualEffectsSpecial; + } + + /** + * Gets the event abnormal visual effects affecting this character. + * @return a map of 32 bits containing all event abnormal visual effects in progress for this character + */ + public int getAbnormalVisualEffectEvent() + { + return _abnormalVisualEffectsEvent; + } + + /** + * Verify if this creature is affected by the given abnormal visual effect. + * @param ave the abnormal visual effect + * @return {@code true} if the creature is affected by the abnormal visual effect, {@code false} otherwise + */ + public boolean hasAbnormalVisualEffect(AbnormalVisualEffect ave) + { + if (ave.isEvent()) + { + return (_abnormalVisualEffectsEvent & ave.getMask()) == ave.getMask(); + } + + if (ave.isSpecial()) + { + return (_abnormalVisualEffectsSpecial & ave.getMask()) == ave.getMask(); + } + + return (_abnormalVisualEffects & ave.getMask()) == ave.getMask(); + } + + /** + * Adds the abnormal visual effect flags in the binary mask and send Server->Client UserInfo/CharInfo packet. + * @param update if {@code true} update packets will be sent + * @param aves the abnormal visual effects + */ + public void startAbnormalVisualEffect(boolean update, AbnormalVisualEffect... aves) + { + for (AbnormalVisualEffect ave : aves) + { + if (ave.isEvent()) + { + _abnormalVisualEffectsEvent |= ave.getMask(); + } + else if (ave.isSpecial()) + { + _abnormalVisualEffectsSpecial |= ave.getMask(); + } + else + { + _abnormalVisualEffects |= ave.getMask(); + } + } + + if (update) + { + updateAbnormalEffect(); + } + } + + /** + * Removes the abnormal visual effect flags from the binary mask and send Server->Client UserInfo/CharInfo packet. + * @param update if {@code true} update packets will be sent + * @param aves the abnormal visual effects + */ + public void stopAbnormalVisualEffect(boolean update, AbnormalVisualEffect... aves) + { + for (AbnormalVisualEffect ave : aves) + { + if (ave.isEvent()) + { + _abnormalVisualEffectsEvent &= ~ave.getMask(); + } + else if (ave.isSpecial()) + { + _abnormalVisualEffectsSpecial &= ~ave.getMask(); + } + else + { + _abnormalVisualEffects &= ~ave.getMask(); + } + } + + if (update) + { + updateAbnormalEffect(); + } + } + + /** + * Active the abnormal effect Fake Death flag, notify the Creature AI and send Server->Client UserInfo/CharInfo packet. + */ + public void startFakeDeath() + { + if (!isPlayer()) + { + return; + } + + asPlayer().setFakeDeath(true); + + // Aborts any attacks/casts if fake dead. + abortAttack(); + abortCast(); + stopMove(null); + getAI().notifyActionFakeDeath(); + broadcastPacket(new ChangeWaitType(this, ChangeWaitType.WT_START_FAKEDEATH)); + + // Remove target from those that have the untargetable creature on target. + if (PlayerConfig.FAKE_DEATH_UNTARGET) + { + World.forEachVisibleObject(this, Creature.class, c -> + { + if (c.getTarget() == this) + { + c.setTarget(null); + } + }); + } + } + + /** + * Launch a Stun Abnormal Effect on the Creature.
    + *
    + * Actions: + *
      + *
    • Calculate the success rate of the Stun Abnormal Effect on this Creature
    • + *
    • If Stun succeed, active the abnormal effect Stun flag, notify the Creature AI and send Server->Client UserInfo/CharInfo packet
    • + *
    • If Stun NOT succeed, send a system message Failed to the Player attacker
    • + *
    + */ + public void startStunning() + { + // Aborts any attacks/casts if stunned. + abortAttack(); + abortCast(); + stopMove(null); + getAI().notifyActionStunned(); + if (!isSummon()) + { + getAI().setIntentionIdle(); + } + + updateAbnormalEffect(); + } + + public void startParalyze() + { + // Aborts any attacks/casts if paralyzed. + abortAttack(); + abortCast(); + stopMove(null); + getAI().notifyActionParalyzed(); + } + + /** + * Stop all active skills effects in progress on the Creature. + */ + public void stopAllEffects() + { + _effectList.stopAllEffects(); + } + + /** + * Stops all effects, except those that last through death. + */ + public void stopAllEffectsExceptThoseThatLastThroughDeath() + { + _effectList.stopAllEffectsExceptThoseThatLastThroughDeath(); + } + + /** + * Stop and remove the effects corresponding to the skill ID. + * @param type determines the system message that will be sent. + * @param skillId the skill Id + */ + public void stopSkillEffects(SkillFinishType type, int skillId) + { + _effectList.stopSkillEffects(type, skillId); + } + + public void stopEffects(EffectType type) + { + _effectList.stopEffects(type); + } + + /** + * Exits all buffs effects of the skills with "removedOnAnyAction" set.
    + * Called on any action except movement (attack, cast). + */ + public void stopEffectsOnAction() + { + _effectList.stopEffectsOnAction(); + } + + /** + * Exits all buffs effects of the skills with "removedOnDamage" set.
    + * Called on decreasing HP and mana burn. + * @param awake + */ + public void stopEffectsOnDamage(boolean awake) + { + _effectList.stopEffectsOnDamage(awake); + } + + /** + * Stop a specified/all Fake Death abnormal Effect.
    + *
    + * Actions: + *
      + *
    • Delete a specified/all (if effect=null) Fake Death abnormal Effect from Creature and update client magic icon
    • + *
    • Set the abnormal effect flag _fake_death to False
    • + *
    • Notify the Creature AI
    • + *
    + * @param removeEffects + */ + public void stopFakeDeath(boolean removeEffects) + { + if (removeEffects) + { + stopEffects(EffectType.FAKE_DEATH); + } + + // If this is a player instance, start the grace period for this character (grace from mobs only)! + if (isPlayer()) + { + final Player player = asPlayer(); + player.setFakeDeath(false); + player.setRecentFakeDeath(true); + } + + broadcastPacket(new ChangeWaitType(this, ChangeWaitType.WT_STOP_FAKEDEATH)); + + // TODO: Temp hack: players see FD on ppl that are moving: Teleport to someone who uses FD - if he gets up he will fall down again for that client - + // even tho he is actually standing... Probably bad info in CharInfo packet? + broadcastPacket(new Revive(this)); + } + + /** + * Stop a specified/all Stun abnormal Effect.
    + *
    + * Actions: + *
      + *
    • Delete a specified/all (if effect=null) Stun abnormal Effect from Creature and update client magic icon
    • + *
    • Set the abnormal effect flag _stuned to False
    • + *
    • Notify the Creature AI
    • + *
    • Send Server->Client UserInfo/CharInfo packet
    • + *
    + * @param removeEffects + */ + public void stopStunning(boolean removeEffects) + { + if (removeEffects) + { + stopEffects(EffectType.STUN); + } + + if (!isPlayer()) + { + getAI().notifyActionThink(); + } + + updateAbnormalEffect(); + } + + public abstract void updateAbnormalEffect(); + + /** + * Update active skills in progress (In Use and Not In Use because stacked) icons on client.
    + *
    + * Concept:
    + *
    + * All active skills effects in progress (In Use and Not In Use because stacked) are represented by an icon on the client.
    + * Caution: This method ONLY UPDATE the client of the player and not clients of all players in the party. + */ + public void updateEffectIcons() + { + updateEffectIcons(false); + } + + /** + * Updates Effect Icons for this character(player/summon) and his party if any. + * @param partyOnly + */ + public void updateEffectIcons(boolean partyOnly) + { + // overridden + } + + public boolean isAffectedBySkill(int skillId) + { + return _effectList.isAffectedBySkill(skillId); + } + + /** + * This class groups all movement data. + */ + public static class MoveData + { + // When we retrieve x/y/z we use GameTimeControl.getGameTicks(). + // If we are moving, but move timestamp==gameticks, we don't need to recalculate position. + public int moveStartTime; + public int moveTimestamp; // Last movement update. + public int xDestination; + public int yDestination; + public int zDestination; + public double xAccurate; // Otherwise there would be rounding errors. + public double yAccurate; + public double zAccurate; + public int heading; + + public boolean disregardingGeodata; + public int onGeodataPathIndex; + public List geoPath; + public int geoPathAccurateTx; + public int geoPathAccurateTy; + public int geoPathGtx; + public int geoPathGty; + + public int lastBroadcastTime; + } + + /** + * Add a Func to the Calculator set of the Creature.
    + *
    + * Concept: A Creature owns a table of Calculators called _calculators.
    + * Each Calculator (a calculator per state) own a table of Func object.
    + * A Func object is a mathematical function that permit to calculate the modifier of a state (ex : REGENERATE_HP_RATE...).
    + * To reduce cache memory use, Npcs who don't have skills share the same Calculator set called NPC_STD_CALCULATOR.
    + * That's why, if a Npc is under a skill/spell effect that modify one of its state, a copy of the NPC_STD_CALCULATOR must be create in its _calculators before adding new Func object.
    + *
    + * Actions: + *
      + *
    • If _calculators is linked to NPC_STD_CALCULATOR, create a copy of NPC_STD_CALCULATOR in _calculators
    • + *
    • Add the Func object to _calculators
    • + *
    + * @param function The Func object to add to the Calculator corresponding to the state affected + */ + public void addStatFunc(AbstractFunction function) + { + if (function == null) + { + return; + } + + synchronized (this) + { + // Check if Calculator set is linked to the standard Calculator set of NPC. + if (_calculators == NPC_STD_CALCULATOR) + { + // Create a copy of the standard NPC Calculator set. + _calculators = new Calculator[Stat.NUM_STATS]; + for (int i = 0; i < Stat.NUM_STATS; i++) + { + if (NPC_STD_CALCULATOR[i] != null) + { + _calculators[i] = new Calculator(NPC_STD_CALCULATOR[i]); + } + } + } + + // Select the Calculator of the affected state in the Calculator set. + final int stat = function.getStat().ordinal(); + if (_calculators[stat] == null) + { + _calculators[stat] = new Calculator(); + } + + // Add the Func to the calculator corresponding to the state. + _calculators[stat].addFunc(function); + } + } + + /** + * Add a list of Funcs to the Calculator set of the Creature.
    + *
    + * Concept:
    + *
    + * A Creature owns a table of Calculators called _calculators.
    + * Each Calculator (a calculator per state) own a table of Func object.
    + * A Func object is a mathematic function that permit to calculate the modifier of a state (ex : REGENERATE_HP_RATE...).
    + * Caution: This method is ONLY for Player
    + *
    + * Example of use: + *
      + *
    • Equip an item from inventory
    • + *
    • Learn a new passive skill
    • + *
    • Use an active skill
    • + *
    + * @param functions The list of Func objects to add to the Calculator corresponding to the state affected + */ + public void addStatFuncs(List functions) + { + final List modifiedStats = new ArrayList<>(); + for (AbstractFunction f : functions) + { + modifiedStats.add(f.getStat()); + addStatFunc(f); + } + + broadcastModifiedStats(modifiedStats); + } + + /** + * Remove a Func from the Calculator set of the Creature.
    + *
    + * Concept:
    + *
    + * A Creature owns a table of Calculators called _calculators.
    + * Each Calculator (a calculator per state) own a table of Func object.
    + * A Func object is a mathematic function that permit to calculate the modifier of a state (ex : REGENERATE_HP_RATE...).
    + * To reduce cache memory use, Npcs who don't have skills share the same Calculator set called NPC_STD_CALCULATOR.
    + * That's why, if a Npc is under a skill/spell effect that modify one of its state, a copy of the NPC_STD_CALCULATOR must be create in its _calculators before addind new Func object.
    + *
    + * Actions: + *
      + *
    • Remove the Func object from _calculators
    • + *
    • If Creature is a Npc and _calculators is equal to NPC_STD_CALCULATOR, free cache memory and just create a link on NPC_STD_CALCULATOR in _calculators
    • + *
    + * @param function The Func object to remove from the Calculator corresponding to the state affected + */ + public void removeStatFunc(AbstractFunction function) + { + if (function == null) + { + return; + } + + // Select the Calculator of the affected state in the Calculator set. + final int stat = function.getStat().ordinal(); + + synchronized (this) + { + if (_calculators[stat] == null) + { + return; + } + + // Remove the Func object from the Calculator. + _calculators[stat].removeFunc(function); + + if (_calculators[stat].size() == 0) + { + _calculators[stat] = null; + } + + // If possible, free the memory and just create a link on NPC_STD_CALCULATOR. + if (isNpc()) + { + int i = 0; + for (; i < Stat.NUM_STATS; i++) + { + if (!Calculator.equalsCals(_calculators[i], NPC_STD_CALCULATOR[i])) + { + break; + } + } + + if (i >= Stat.NUM_STATS) + { + _calculators = NPC_STD_CALCULATOR; + } + } + } + } + + /** + * Remove a list of Funcs from the Calculator set of the Player.
    + *
    + * Concept:
    + *
    + * A Creature owns a table of Calculators called _calculators.
    + * Each Calculator (a calculator per state) own a table of Func object.
    + * A Func object is a mathematic function that permit to calculate the modifier of a state (ex : REGENERATE_HP_RATE...).
    + * Caution: This method is ONLY for Player
    + *
    + * Example of use: + *
      + *
    • Unequip an item from inventory
    • + *
    • Stop an active skill
    • + *
    + * @param functions The list of Func objects to add to the Calculator corresponding to the state affected + */ + public void removeStatFuncs(AbstractFunction[] functions) + { + final List modifiedStats = new ArrayList<>(); + for (AbstractFunction f : functions) + { + modifiedStats.add(f.getStat()); + removeStatFunc(f); + } + + broadcastModifiedStats(modifiedStats); + } + + /** + * Remove all Func objects with the selected owner from the Calculator set of the Creature.
    + *
    + * Concept:
    + *
    + * A Creature owns a table of Calculators called _calculators.
    + * Each Calculator (a calculator per state) own a table of Func object.
    + * A Func object is a mathematic function that permit to calculate the modifier of a state (ex : REGENERATE_HP_RATE...).
    + * To reduce cache memory use, Npcs who don't have skills share the same Calculator set called NPC_STD_CALCULATOR.
    + * That's why, if a Npc is under a skill/spell effect that modify one of its state, a copy of the NPC_STD_CALCULATOR must be create in its _calculators before addind new Func object.
    + *
    + * Actions: + *
      + *
    • Remove all Func objects of the selected owner from _calculators
    • + *
    • If Creature is a Npc and _calculators is equal to NPC_STD_CALCULATOR, free cache memory and just create a link on NPC_STD_CALCULATOR in _calculators
    • + *
    + *
    + * Example of use: + *
      + *
    • Unequip an item from inventory
    • + *
    • Stop an active skill
    • + *
    + * @param owner The Object(Skill, Item...) that has created the effect + */ + public void removeStatsOwner(Object owner) + { + List modifiedStats = null; + int i = 0; + + // Go through the Calculator set. + synchronized (this) + { + for (Calculator calc : _calculators) + { + if (calc != null) + { + // Delete all Func objects of the selected owner + if (modifiedStats != null) + { + modifiedStats.addAll(calc.removeOwner(owner)); + } + else + { + modifiedStats = calc.removeOwner(owner); + } + + if (calc.size() == 0) + { + _calculators[i] = null; + } + } + + i++; + } + + // If possible, free the memory and just create a link on NPC_STD_CALCULATOR. + if (isNpc()) + { + i = 0; + for (; i < Stat.NUM_STATS; i++) + { + if (!Calculator.equalsCals(_calculators[i], NPC_STD_CALCULATOR[i])) + { + break; + } + } + + if (i >= Stat.NUM_STATS) + { + _calculators = NPC_STD_CALCULATOR; + } + } + + if (isSummon()) + { + if (getCurrentHp() > getMaxHp()) + { + setCurrentHp(getMaxHp()); + } + + if (getCurrentMp() > getMaxMp()) + { + setCurrentMp(getMaxMp()); + } + } + + broadcastModifiedStats(modifiedStats); + } + } + + protected void broadcastModifiedStats(List stats) + { + if (!isSpawned()) + { + return; + } + + if ((stats == null) || stats.isEmpty()) + { + return; + } + + if (isSummon()) + { + final Summon summon = asSummon(); + if (summon.getOwner() != null) + { + summon.updateAndBroadcastStatus(1); + } + } + else + { + boolean broadcastFull = false; + final StatusUpdate su = new StatusUpdate(this); + for (Stat stat : stats) + { + if (stat == Stat.POWER_ATTACK_SPEED) + { + su.addAttribute(StatusUpdate.ATK_SPD, (int) _stat.getPAtkSpd()); + } + else if (stat == Stat.MAGIC_ATTACK_SPEED) + { + su.addAttribute(StatusUpdate.CAST_SPD, _stat.getMAtkSpd()); + } + else if (stat == Stat.MOVE_SPEED) + { + broadcastFull = true; + } + } + + if (isPlayer()) + { + final Player player = asPlayer(); + if (broadcastFull) + { + player.broadcastUserInfo(); + } + else + { + player.updateUserInfo(); + if (su.hasAttributes()) + { + broadcastPacket(su); + } + } + + final Summon summon = player.getSummon(); + if ((summon != null) && isAffected(EffectFlag.SERVITOR_SHARE)) + { + summon.broadcastStatusUpdate(); + } + } + else if (isNpc()) + { + if (broadcastFull) + { + World.forEachVisibleObject(this, Player.class, player -> + { + if (!isVisibleFor(player)) + { + return; + } + + if (isFakePlayer()) + { + player.sendPacket(new FakePlayerInfo(asNpc())); + } + else if (_stat.getRunSpeed() == 0) + { + player.sendPacket(new ServerObjectInfo(asNpc(), player)); + } + else + { + player.sendPacket(new AbstractNpcInfo.NpcInfo(asNpc(), player)); + } + }); + } + else if (su.hasAttributes()) + { + broadcastPacket(su); + } + } + else if (su.hasAttributes()) + { + broadcastPacket(su); + } + } + } + + /** + * @return the X destination of the Creature or the X position if not in movement. + */ + public int getXdestination() + { + final MoveData move = _move; + if (move != null) + { + return move.xDestination; + } + + return getX(); + } + + /** + * @return the Y destination of the Creature or the Y position if not in movement. + */ + public int getYdestination() + { + final MoveData move = _move; + if (move != null) + { + return move.yDestination; + } + + return getY(); + } + + /** + * @return the Z destination of the Creature or the Z position if not in movement. + */ + public int getZdestination() + { + final MoveData move = _move; + if (move != null) + { + return move.zDestination; + } + + return getZ(); + } + + /** + * @return True if the Creature is in combat. + */ + public boolean isInCombat() + { + return hasAI() && ((getAI().getAttackTarget() != null) || getAI().isAutoAttacking()); + } + + /** + * @return True if the Creature is moving. + */ + public boolean isMoving() + { + return _move != null; + } + + /** + * @return True if the Creature is traveling a calculated path. + */ + public boolean isOnGeodataPath() + { + final MoveData move = _move; + if (move == null) + { + return false; + } + + return isOnGeodataPath(move); + } + + /** + * @param move the MoveData to check (must not be null). + * @return True if the Creature is traveling a calculated path. + */ + public boolean isOnGeodataPath(MoveData move) + { + if (move.onGeodataPathIndex == -1) + { + return false; + } + + if (move.onGeodataPathIndex == (move.geoPath.size() - 1)) + { + return false; + } + + return true; + } + + /** + * This method returns a list of {@link GeoLocation} objects representing the movement path.
    + * If the move operation is defined (not null), it returns the path from the 'geoPath' field of the move.
    + * Otherwise, it returns null. + * @return List of {@link GeoLocation} representing the movement path, or null if move is undefined. + */ + public List getGeoPath() + { + final MoveData move = _move; + if (move != null) + { + return move.geoPath; + } + + return null; + } + + /** + * @return True if the Creature is casting. + */ + public boolean isCastingNow() + { + return _isCastingNow; + } + + public void setCastingNow(boolean value) + { + _isCastingNow = value; + } + + public boolean isCastingSimultaneouslyNow() + { + return _isCastingSimultaneouslyNow; + } + + public void setCastingSimultaneouslyNow(boolean value) + { + _isCastingSimultaneouslyNow = value; + } + + /** + * @return True if the cast of the Creature can be aborted. + */ + public boolean canAbortCast() + { + return _castInterruptTime > GameTimeTaskManager.getInstance().getGameTicks(); + } + + public int getCastInterruptTime() + { + return _castInterruptTime; + } + + /** + * Verifies if the creature is attacking or casting now. + * @return {@code true} if the creature is attacking or casting now, {@code false} otherwise + */ + public boolean isAttackingOrCastingNow() + { + return isAttackingNow() || isRangeAttackingNow() || isCastingNow() || isCastingSimultaneouslyNow(); + } + + /** + * Verifies if the creature is attacking now. + * @return {@code true} if the creature is attacking now, {@code false} otherwise + */ + public boolean isAttackingNow() + { + return _attackEndTime > System.nanoTime(); + } + + /** + * @return True if the Creature is attacking with a ranged weapon. + */ + public boolean isRangeAttackingNow() + { + return _disableBowAttackEndTime > GameTimeTaskManager.getInstance().getGameTicks(); + } + + /** + * Abort the attack of the Creature and send Server->Client ActionFailed packet. + */ + public void abortAttack() + { + if (isAttackingNow()) + { + sendPacket(ActionFailed.STATIC_PACKET); + } + } + + /** + * Abort the cast of the Creature and send Server->Client MagicSkillCanceld/ActionFailed packet. + */ + public void abortCast() + { + if (_isCastingNow || _isCastingSimultaneouslyNow) + { + Future future = _skillCast; + + // cancels the skill hit scheduled task + if (future != null) + { + future.cancel(true); + _skillCast = null; + } + + future = _skillCast2; + if (future != null) + { + future.cancel(true); + _skillCast2 = null; + } + + // TODO: Handle removing spawned npc. + if (isChanneling()) + { + getSkillChannelizer().stopChanneling(); + } + + if (_allSkillsDisabled) + { + enableAllSkills(); // this remains for forced skill use, e.g. scroll of escape + } + + setCastingNow(false); + setCastingSimultaneouslyNow(false); + + // safeguard for cannot be interrupt any more + _castInterruptTime = 0; + if (isPlayer()) + { + getAI().notifyActionFinishCasting(); // setting back previous intention + } + + broadcastPacket(new MagicSkillCanceled(getObjectId())); // broadcast packet to stop animations client-side + sendPacket(ActionFailed.STATIC_PACKET); // send an "action failed" packet to the caster + } + } + + /** + * Update the position of the Creature during a movement and return True if the movement is finished.
    + *
    + * Concept:
    + *
    + * At the beginning of the move action, all properties of the movement are stored in the MoveData object called _move of the Creature.
    + * The position of the start point and of the destination permit to estimated in function of the movement speed the time to achieve the destination.
    + * When the movement is started (ex : by MovetoLocation), this method will be called each 0.1 sec to estimate and update the Creature position on the server.
    + * Note, that the current server position can differe from the current client position even if each movement is straight foward.
    + * That's why, client send regularly a Client->Server ValidatePosition packet to eventually correct the gap on the server.
    + * But, it's always the server position that is used in range calculation. At the end of the estimated movement time,
    + * the Creature position is automatically set to the destination position even if the movement is not finished.
    + * Caution: The current Z position is obtained FROM THE CLIENT by the Client->Server ValidatePosition Packet.
    + * But x and y positions must be calculated to avoid that players try to modify their movement speed.
    + * @return True if the movement is finished + */ + public boolean updatePosition() + { + if (!isSpawned()) + { + _move = null; + return true; + } + + // Get movement data + final MoveData move = _move; + if (move == null) + { + return true; + } + + // Check if this is the first update. + if (move.moveTimestamp == 0) + { + move.moveTimestamp = move.moveStartTime; + move.xAccurate = getX(); + move.yAccurate = getY(); + } + + // Check if the position has already been calculated. + final int gameTicks = GameTimeTaskManager.getInstance().getGameTicks(); + if (move.moveTimestamp == gameTicks) + { + return false; + } + + final int xPrev = getX(); + final int yPrev = getY(); + final int zPrev = getZ(); // The z coordinate may be modified by coordinate synchronizations. + double dx = move.xDestination - move.xAccurate; + double dy = move.yDestination - move.yAccurate; + double dz = move.zDestination - zPrev; // Z coordinate will follow client values. + + if (isPlayer() && !_isFlying) + { + // In case of cursor movement, avoid moving through obstacles. + if (_cursorKeyMovement) + { + final double angle = LocationUtil.convertHeadingToDegree(getHeading()); + final double radian = Math.toRadians(angle); + final double course = Math.toRadians(180); + final double frontDistance = 10 * (_stat.getMoveSpeed() / 100); + final int x1 = (int) (Math.cos(Math.PI + radian + course) * frontDistance); + final int y1 = (int) (Math.sin(Math.PI + radian + course) * frontDistance); + final int x = xPrev + x1; + final int y = yPrev + y1; + if (!GeoEngine.getInstance().canMoveToTarget(xPrev, yPrev, zPrev, x, y, zPrev, getInstanceId())) + { + // Stop at the current position, not lastServerPosition (which is where the move started). + // Snapping back to lastServerPosition while the client renders the player at the wall causes a large WASD desync that grows with each pivot against geometry. + move.onGeodataPathIndex = -1; + stopMove(getLocation()); + return true; + } + } + else // Mouse click movement. + { + // Stop movement when player has clicked far away and intersected with an obstacle. + final double distance = Math.sqrt((dx * dx) + (dy * dy)); + if (distance > 3000) + { + final double angle = LocationUtil.convertHeadingToDegree(getHeading()); + final double radian = Math.toRadians(angle); + final double course = Math.toRadians(180); + final double frontDistance = 10 * (_stat.getMoveSpeed() / 100); + final int x1 = (int) (Math.cos(Math.PI + radian + course) * frontDistance); + final int y1 = (int) (Math.sin(Math.PI + radian + course) * frontDistance); + final int x = xPrev + x1; + final int y = yPrev + y1; + if (!GeoEngine.getInstance().canMoveToTarget(xPrev, yPrev, zPrev, x, y, zPrev, getInstanceId())) + { + move.onGeodataPathIndex = -1; + if (hasAI()) + { + if (getAI().isFollowing()) + { + getAI().stopFollow(); + } + + getAI().setIntentionIdle(); + } + + return true; + } + } + else // Check for nearby doors or fences. + { + if (hasAI() && (getAI().getIntention() == Intention.ATTACK)) // Support for player attack with direct movement. Tested at retail on May 11th 2023. + { + final double angle = LocationUtil.convertHeadingToDegree(getHeading()); + final double radian = Math.toRadians(angle); + final double course = Math.toRadians(180); + final double frontDistance = 10 * (_stat.getMoveSpeed() / 100); + final int x1 = (int) (Math.cos(Math.PI + radian + course) * frontDistance); + final int y1 = (int) (Math.sin(Math.PI + radian + course) * frontDistance); + final int x = xPrev + x1; + final int y = yPrev + y1; + if (!GeoEngine.getInstance().canMoveToTarget(xPrev, yPrev, zPrev, x, y, zPrev, getInstanceId())) + { + move.onGeodataPathIndex = -1; + broadcastPacket(new StopMove(this)); + return true; + } + } + else // Check for nearby doors or fences. + { + final WorldRegion region = getWorldRegion(); + if (region != null) + { + final boolean hasDoors = !region.getDoors().isEmpty(); + final boolean hasFences = !region.getFences().isEmpty(); + if (hasDoors || hasFences) + { + final double angle = LocationUtil.convertHeadingToDegree(getHeading()); + final double radian = Math.toRadians(angle); + final double course = Math.toRadians(180); + final double frontDistance = 10 * (_stat.getMoveSpeed() / 100); + final int x1 = (int) (Math.cos(Math.PI + radian + course) * frontDistance); + final int y1 = (int) (Math.sin(Math.PI + radian + course) * frontDistance); + final int x = xPrev + x1; + final int y = yPrev + y1; + if ((hasDoors && DoorData.getInstance().checkIfDoorsBetween(xPrev, yPrev, zPrev, x, y, zPrev, getInstanceId(), false)) // + || (hasFences && FenceData.getInstance().checkIfFenceBetween(xPrev, yPrev, zPrev, x, y, zPrev, getInstanceId()))) + { + move.onGeodataPathIndex = -1; + if (hasAI()) + { + if (getAI().isFollowing()) + { + getAI().stopFollow(); + } + + getAI().setIntentionIdle(); + } + + stopMove(null); + return true; + } + } + } + } + } + } + } + + // Distance from destination. + double delta = (dx * dx) + (dy * dy); + final boolean isFloating = _isFlying || (isInsideZone(ZoneId.WATER) && !isInsideZone(ZoneId.CASTLE)); + if (!isFloating && (delta < 10000) && ((dz * dz) > 2500)) // Close enough, allows error between client and server geodata if it cannot be avoided. + { + delta = Math.sqrt(delta); + } + else + { + delta = Math.sqrt(delta + (dz * dz)); + } + + // Target collision should be subtracted from current distance. + final double collision; + final WorldObject target = _target; + if ((target != null) && target.isCreature() && hasAI() && (getAI().getIntention() == Intention.ATTACK)) + { + collision = target.asCreature().getTemplate().getCollisionRadius(); + } + else + { + collision = getTemplate().getCollisionRadius(); + } + + delta = Math.max(0.00001, delta - collision); + + double distFraction = Double.MAX_VALUE; + if (delta > 1) + { + final double distPassed = (_stat.getMoveSpeed() * (gameTicks - move.moveTimestamp)) / GameTimeTaskManager.TICKS_PER_SECOND; + distFraction = distPassed / delta; + } + + final boolean arrived = distFraction > 1; + if (arrived) + { + // Set the position of the Creature to the destination. + super.setXYZ(move.xDestination, move.yDestination, move.zDestination); + } + else + { + final int newZ = zPrev + (int) ((dz * distFraction) + 0.5); + move.xAccurate += dx * distFraction; + move.yAccurate += dy * distFraction; + + // Prevent attackables teleporting to another ground layer while moving. + if (isAttackable() && !isFloating && (Math.abs(newZ - zPrev) > 300)) + { + final Spawn spawn = asAttackable().getSpawn(); + if (spawn != null) + { + teleToLocation(spawn, getInstanceId(), 0); + getAttackByList().clear(); + asAttackable().clearAggroList(); + getAI().setIntentionIdle(); + } + + return true; + } + + // Set the position of the Creature to estimated after parcial move. + super.setXYZ((int) move.xAccurate, (int) move.yAccurate, newZ); + } + + revalidateZone(false); + + // Set the timer of last position update to now. + move.moveTimestamp = gameTicks; + + // Broadcast MoveToLocation on arrived. + if (arrived && !isOnGeodataPath()) + { + broadcastMoveToLocation(true); + } + else if (isAttackable() && (target != null)) // Attackable with target. + { + broadcastMoveToLocation(); + } + + return arrived; + } + + public void revalidateZone(boolean force) + { + // This function is called too often from movement code. + if (!force && (calculateDistance3D(_lastZoneValidateLocation) < (isNpc() && !isInCombat() ? NpcConfig.MAX_DRIFT_RANGE : 100))) + { + return; + } + + _lastZoneValidateLocation.setXYZ(this); + + final ZoneRegion region = ZoneManager.getInstance().getRegion(this); + if (region != null) + { + region.revalidateZones(this); + } + else // Precaution. Moved at invalid region? + { + World.disposeOutOfBoundsObject(this); + } + } + + /** + * Stop movement of the Creature (Called by AI Accessor only).
    + *
    + * Actions: + *
      + *
    • Delete movement data of the Creature
    • + *
    • Set the current position (x,y,z), its current WorldRegion if necessary and its heading
    • + *
    • Remove the WorldObject object from _gmList of GmListTable
    • + *
    • Remove object from _knownObjects and _knownPlayer of all surrounding WorldRegion Creatures
    • + *
    + * Caution: This method DOESN'T send Server->Client packet StopMove/StopRotation + * @param loc + */ + public void stopMove(Location loc) + { + // Delete movement data of the Creature. + _move = null; + + // All data are contained in a Location object. + if (loc != null) + { + setXYZ(loc.getX(), loc.getY(), loc.getZ()); + setHeading(loc.getHeading()); + revalidateZone(true); + } + + broadcastPacket(new StopMove(this)); + } + + /** + * @return Returns the showSummonAnimation. + */ + public boolean isShowSummonAnimation() + { + return _showSummonAnimation; + } + + /** + * @param showSummonAnimation The showSummonAnimation to set. + */ + public void setShowSummonAnimation(boolean showSummonAnimation) + { + _showSummonAnimation = showSummonAnimation; + } + + /** + * Target a WorldObject (add the target to the Creature _target, _knownObject and Creature to _KnownObject of the WorldObject).
    + *
    + * Concept:
    + *
    + * The WorldObject (including Creature) targeted is identified in _target of the Creature.
    + *
    + * Actions: + *
      + *
    • Set the _target of Creature to WorldObject
    • + *
    • If necessary, add WorldObject to _knownObject of the Creature
    • + *
    • If necessary, add Creature to _KnownObject of the WorldObject
    • + *
    • If object==null, cancel Attak or Cast
    • + *
    + * @param object L2object to target + */ + public void setTarget(WorldObject object) + { + if ((object != null) && !object.isSpawned()) + { + _target = null; + return; + } + + _target = object; + } + + /** + * @return the identifier of the WorldObject targeted or -1. + */ + public int getTargetId() + { + if (_target != null) + { + return _target.getObjectId(); + } + + return 0; + } + + /** + * @return the WorldObject targeted or null. + */ + public WorldObject getTarget() + { + return _target; + } + + /** + * Calculate movement data for a move to location action and add the Creature to MOVING_OBJECTS of MovementTaskManager (only called by AI Accessor).
    + *
    + * Concept:
    + *
    + * At the beginning of the move action, all properties of the movement are stored in the MoveData object called _move of the Creature.
    + * The position of the start point and of the destination permit to estimated in function of the movement speed the time to achieve the destination.
    + * All Creature in movement are identified in MOVING_OBJECTS of MovementTaskManager that will call the updatePosition method of those Creature each 0.1s.
    + *
    + * Actions: + *
      + *
    • Get current position of the Creature
    • + *
    • Calculate distance (dx,dy) between current position and destination including offset
    • + *
    • Create and Init a MoveData object
    • + *
    • Set the Creature _move object to MoveData object
    • + *
    • Add the Creature to MOVING_OBJECTS of the MovementTaskManager
    • + *
    • Create a task to notify the AI that Creature arrives at a check point of the movement
    • + *
    + * Caution: This method DOESN'T send Server->Client packet MoveToPawn/MoveToLocation.
    + *
    + * Example of use: + *
      + *
    • AI : onIntentionMoveTo(Location), onIntentionPickUp(WorldObject), onIntentionInteract(WorldObject)
    • + *
    • FollowTask
    • + *
    + * @param xValue The X position of the destination + * @param yValue The Y position of the destination + * @param zValue The Y position of the destination + * @param offsetValue The size of the interaction area of the Creature targeted + */ + public void moveToLocation(int xValue, int yValue, int zValue, int offsetValue) + { + // Get the Move Speed of the Creature. + final double speed = _stat.getMoveSpeed(); + if ((speed <= 0) || isMovementDisabled()) + { + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + + int x = xValue; + int y = yValue; + int z = zValue; + int offset = offsetValue; + + // Get current position of the Creature. + final int curX = getX(); + final int curY = getY(); + final int curZ = getZ(); + + // Calculate distance (dx,dy) between current position and destination. + // TODO: improve Z axis move/follow support when dx,dy are small compared to dz + double dx = (x - curX); + double dy = (y - curY); + double dz = (z - curZ); + double distance = Math.sqrt((dx * dx) + (dy * dy)); + + final boolean verticalMovementOnly = _isFlying && (distance == 0) && (dz != 0); + if (verticalMovementOnly) + { + distance = Math.abs(dz); + } + + // Make water move short and use no geodata checks for swimming chars distance in a click can easily be over 3000. + final boolean isInWater = isInsideZone(ZoneId.WATER) && !isInsideZone(ZoneId.CASTLE); + if (isInWater && (distance > 700)) + { + final double divider = 700 / distance; + x = curX + (int) (divider * dx); + y = curY + (int) (divider * dy); + z = curZ + (int) (divider * dz); + dx = (x - curX); + dy = (y - curY); + dz = (z - curZ); + distance = Math.sqrt((dx * dx) + (dy * dy)); + } + + // @formatter:off + // Define movement angles needed. + // ^ + // | X (x,y) + // | / + // | / distance + // | / + // |/ angle + // X ----------> + // (curx,cury) + // @formatter:on + + double cos; + double sin; + + // Check if a movement offset is defined or no distance to go through. + if ((offset > 0) || (distance < 1)) + { + // approximation for moving closer when z coordinates are different + // TODO: handle Z axis movement better + offset -= Math.abs(dz); + if (offset < 5) + { + offset = 5; + } + + // If no distance to go through, the movement is canceled. + if ((distance < 1) || ((distance - offset) <= 0)) + { + // Notify the AI that the Creature is arrived at destination. + getAI().notifyActionArrived(); + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + + // Calculate movement angles needed. + sin = dy / distance; + cos = dx / distance; + distance -= (offset - 5); // Due to rounding error, we have to move a bit closer to be in range. + + // Calculate the new destination with offset included. + x = curX + (int) (distance * cos); + y = curY + (int) (distance * sin); + } + else + { + // Calculate movement angles needed. + sin = dy / distance; + cos = dx / distance; + } + + // Create and Init a MoveData object. + final MoveData move = new MoveData(); + + // GEODATA MOVEMENT CHECKS AND PATHFINDING + final WorldRegion region = getWorldRegion(); + move.disregardingGeodata = (region == null) || !region.areNeighborsActive(); + move.onGeodataPathIndex = -1; // Initialize not on geodata path. + if (!move.disregardingGeodata && !_isFlying && !isInWater && !isVehicle() && !_cursorKeyMovement) + { + final boolean isInVehicle = isPlayer() && (asPlayer().getVehicle() != null); + if (isInVehicle) + { + move.disregardingGeodata = true; + } + + // Movement checks. + if ((GeoEngineConfig.PATHFINDING > 0) && !(this instanceof QuestGuard)) + { + int originalX = x; + int originalY = y; + final int originalZ = z; + final double originalDistance = distance; + final int gtx = (originalX - World.WORLD_X_MIN) >> 4; + final int gty = (originalY - World.WORLD_Y_MIN) >> 4; + final MoveData currentMove = _move; + if ((currentMove != null) && isOnGeodataPath(currentMove)) + { + if ((gtx == currentMove.geoPathGtx) && (gty == currentMove.geoPathGty)) + { + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + + currentMove.onGeodataPathIndex = -1; // Set not on geodata path. + } + + // Support for player attack with direct movement. Tested at retail on May 11th 2023. + final boolean directMove = isPlayer() && hasAI() && (getAI().getIntention() == Intention.ATTACK); + + // Check if destination is a water zone. + final boolean isDestinationInWater = isPlayer() && (ZoneManager.getInstance().getZone(x, y, z, WaterZone.class) != null); + + if (directMove // + || (!isInVehicle && !isDestinationInWater // Not in vehicle and destination not in water. + && !(isPlayer() && (distance > 3000)) // Should be able to click far away and move. + && !(isMonster() && (Math.abs(dz) > 100) && GeoEngine.getInstance().canSeeTarget(this, _target)) // Monsters can move on ledges. + && !(((curZ - z) > 300) && (distance < 300)))) // Prohibit correcting destination if character wants to fall. + { + // Location is different if destination wasn't reached (or just z coord is different). + final Location destination = GeoEngine.getInstance().getValidLocation(curX, curY, curZ, x, y, z, getInstanceId()); + x = destination.getX(); + y = destination.getY(); + if (!isPlayer()) + { + z = destination.getZ(); + } + + dx = x - curX; + dy = y - curY; + dz = z - curZ; + distance = verticalMovementOnly ? (dz * dz) : Math.sqrt((dx * dx) + (dy * dy)); + } + + // Pathfinding checks. + if (!directMove && ((originalDistance - distance) > 30) && !isControlBlocked() && !isInVehicle) + { + // Path calculation -- overrides previous movement check. + move.geoPath = PathFinding.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceId(), isPlayer()); + boolean found = (move.geoPath != null) && (move.geoPath.size() > 1); + + // If path not found and this is an Attackable, attempt to find closest path to destination. + if (!found && isAttackable()) + { + int xMin = Math.min(curX, originalX); + int xMax = Math.max(curX, originalX); + int yMin = Math.min(curY, originalY); + int yMax = Math.max(curY, originalY); + final int maxDiff = Math.min(Math.max(xMax - xMin, yMax - yMin), 500); + xMin -= maxDiff; + xMax += maxDiff; + yMin -= maxDiff; + yMax += maxDiff; + int destinationX = 0; + int destinationY = 0; + double shortDistance = Double.MAX_VALUE; + double tempDistance; + List tempPath; + for (int sX = xMin; sX < xMax; sX += 500) + { + for (int sY = yMin; sY < yMax; sY += 500) + { + final double tdx = sX - originalX; + final double tdy = sY - originalY; + tempDistance = Math.sqrt((tdx * tdx) + (tdy * tdy)); + if (tempDistance < shortDistance) + { + tempPath = PathFinding.getInstance().findPath(curX, curY, curZ, sX, sY, originalZ, getInstanceId(), false); + found = (tempPath != null) && (tempPath.size() > 1); + if (found) + { + shortDistance = tempDistance; + move.geoPath = tempPath; + destinationX = sX; + destinationY = sY; + } + } + } + } + + found = (move.geoPath != null) && (move.geoPath.size() > 1); + if (found) + { + originalX = destinationX; + originalY = destinationY; + } + } + + if (found) + { + move.onGeodataPathIndex = 0; // On first segment. + move.geoPathGtx = gtx; + move.geoPathGty = gty; + move.geoPathAccurateTx = originalX; + move.geoPathAccurateTy = originalY; + x = move.geoPath.get(move.onGeodataPathIndex).getX(); + y = move.geoPath.get(move.onGeodataPathIndex).getY(); + z = move.geoPath.get(move.onGeodataPathIndex).getZ(); + dx = x - curX; + dy = y - curY; + dz = z - curZ; + distance = verticalMovementOnly ? (dz * dz) : Math.sqrt((dx * dx) + (dy * dy)); + sin = dy / distance; + cos = dx / distance; + } + else // No path found. + { + // When no move path was found, use direct movement. Tested at retail on October 21st 2024. + // if (isPlayer() && !_isFlying && !isInWater) + // { + // sendPacket(ActionFailed.STATIC_PACKET); + // return; + // } + + move.disregardingGeodata = true; + x = originalX; + y = originalY; + z = originalZ; + distance = originalDistance; + } + } + + // Verify destination when using mouse movement and no path is found. + if (isPlayable() && !_cursorKeyMovement && (move.geoPath == null) // + && !isInVehicle // Not in vehicle. + && (distance < 3000) // Should be able to click far away and move. + && !(((curZ - z) > 300) && (distance < 300))) // Prohibit correcting destination if character wants to fall. + { + final Location destination = GeoEngine.getInstance().getValidLocation(curX, curY, curZ, x, y, z, getInstanceId()); + x = destination.getX(); + y = destination.getY(); + z = destination.getZ(); + dx = x - curX; + dy = y - curY; + dz = z - curZ; + distance = verticalMovementOnly ? (dz * dz) : Math.sqrt((dx * dx) + (dy * dy)); + } + } + + // If no distance to go through, the movement is canceled. + if ((distance < 1) && ((GeoEngineConfig.PATHFINDING > 0) || isPlayable())) + { + // An attack or cast already in flight has to survive this, cancelling it is what strands melee. + final Intention intention = hasAI() ? getAI().getIntention() : null; + final boolean pendingTargetAction = (intention == Intention.ATTACK) || (intention == Intention.CAST); + if (isSummon() && !pendingTargetAction) + { + // Do not break following owner. + if (getAI().getFollowTarget() != asPlayer()) + { + asSummon().setFollowStatus(false); + getAI().setIntentionIdle(); + } + } + else if (!pendingTargetAction) + { + getAI().setIntentionIdle(); + } + + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + } + + // Apply Z distance for flying or swimming for correct timing calculations. + if ((_isFlying || isInWater) && !verticalMovementOnly) + { + distance = Math.sqrt((distance * distance) + (dz * dz)); + } + + // Calculate the number of ticks between the current position and the destination. + final int ticksToMove = (int) ((GameTimeTaskManager.TICKS_PER_SECOND * distance) / speed); + move.xDestination = x; + move.yDestination = y; + move.zDestination = z; // This is what was requested from client. + + // Calculate and set the heading of the Creature. + move.heading = 0; // Initial value for coordinate sync. + + // Does not break heading on vertical movements. + if (!verticalMovementOnly) + { + setHeading(LocationUtil.calculateHeadingFrom(cos, sin)); + } + + move.moveStartTime = GameTimeTaskManager.getInstance().getGameTicks(); + + // Set the Creature _move object to MoveData object. + _move = move; + + // Add the Creature to moving objects of the MovementTaskManager. + // The MovementTaskManager manages object movement. + MovementTaskManager.getInstance().registerMovingObject(this); + + // Create a task to notify the AI that Creature arrives at a check point of the movement. + if ((ticksToMove * GameTimeTaskManager.MILLIS_IN_TICK) > 3000) + { + ThreadPool.schedule(() -> getAI().notifyActionArrivedRevalidate(), 2000); + } + + // The Event.ARRIVED will be sent when the character will actually arrive to destination by MovementTaskManager. + } + + /** + * Move to next route point. + * @return true, if successful + */ + public boolean moveToNextRoutePoint() + { + final MoveData move = _move; + if (move == null) + { + return false; + } + + if (!isOnGeodataPath(move)) + { + // Cancel the move action. + _move = null; + return false; + } + + // Get the Move Speed of the Creature. + final double speed = _stat.getMoveSpeed(); + if ((speed <= 0) || isMovementDisabled()) + { + // Cancel the move action. + _move = null; + return false; + } + + // Get current position of the Creature. + final int curX = getX(); + final int curY = getY(); + + // Create and Init a MoveData object. + final MoveData newMove = new MoveData(); + + // Update MoveData object + newMove.onGeodataPathIndex = move.onGeodataPathIndex + 1; // next segment + newMove.geoPath = move.geoPath; + newMove.geoPathGtx = move.geoPathGtx; + newMove.geoPathGty = move.geoPathGty; + newMove.geoPathAccurateTx = move.geoPathAccurateTx; + newMove.geoPathAccurateTy = move.geoPathAccurateTy; + if (move.onGeodataPathIndex == (move.geoPath.size() - 2)) + { + newMove.xDestination = move.geoPathAccurateTx; + newMove.yDestination = move.geoPathAccurateTy; + newMove.zDestination = move.geoPath.get(newMove.onGeodataPathIndex).getZ(); + } + else + { + newMove.xDestination = move.geoPath.get(newMove.onGeodataPathIndex).getX(); + newMove.yDestination = move.geoPath.get(newMove.onGeodataPathIndex).getY(); + newMove.zDestination = move.geoPath.get(newMove.onGeodataPathIndex).getZ(); + } + + // Calculate and set the heading of the Creature. + final double ndx = newMove.xDestination - curX; + final double ndy = newMove.yDestination - curY; + final double distance = Math.sqrt((ndx * ndx) + (ndy * ndy)); + if (distance != 0) + { + setHeading(LocationUtil.calculateHeadingFrom(curX, curY, newMove.xDestination, newMove.yDestination)); + } + + // Calculate the number of ticks between the current position and the destination. + final int ticksToMove = (int) ((GameTimeTaskManager.TICKS_PER_SECOND * distance) / speed); + newMove.heading = 0; // Initial value for coordinate sync. + newMove.moveStartTime = GameTimeTaskManager.getInstance().getGameTicks(); + + // Set the Creature _move object to MoveData object. + _move = newMove; + + // Add the Creature to moving objects of the MovementTaskManager. + // The MovementTaskManager manages object movement. + MovementTaskManager.getInstance().registerMovingObject(this); + + // Create a task to notify the AI that Creature arrives at a check point of the movement. + if ((ticksToMove * GameTimeTaskManager.MILLIS_IN_TICK) > 3000) + { + ThreadPool.schedule(() -> getAI().notifyActionArrivedRevalidate(), 2000); + } + + // The Event.ARRIVED will be sent when the character will actually arrive to destination by MovementTaskManager. + + // Send a Server->Client packet MoveToLocation to the actor and all Player in its known players. + broadcastMoveToLocation(true); + return true; + } + + /** + * Validate movement heading. + * @param heading the heading + * @return true, if successful + */ + public boolean validateMovementHeading(int heading) + { + final MoveData move = _move; + if (move == null) + { + return true; + } + + boolean result = true; + if (move.heading != heading) + { + result = (move.heading == 0); // initial value or false + move.heading = heading; + } + + return result; + } + + /** + * Check if this object is inside the given 2D radius around the given point. + * @param loc Location of the target + * @param radius the radius around the target + * @return true if the Creature is inside the radius. + */ + public boolean isInsideRadius2D(ILocational loc, int radius) + { + return isInsideRadius2D(loc.getX(), loc.getY(), loc.getZ(), radius); + } + + /** + * Check if this object is inside the given 2D radius around the given point. + * @param x X position of the target + * @param y Y position of the target + * @param z Z position of the target + * @param radius the radius around the target + * @return true if the Creature is inside the radius. + */ + public boolean isInsideRadius2D(int x, int y, int z, int radius) + { + return calculateDistance2D(x, y, z) < radius; + } + + /** + * Check if this object is inside the given 3D radius around the given point. + * @param loc Location of the target + * @param radius the radius around the target + * @return true if the Creature is inside the radius. + */ + public boolean isInsideRadius3D(ILocational loc, int radius) + { + return isInsideRadius3D(loc.getX(), loc.getY(), loc.getZ(), radius); + } + + /** + * Check if this object is inside the given 3D radius around the given point. + * @param x X position of the target + * @param y Y position of the target + * @param z Z position of the target + * @param radius the radius around the target + * @return true if the Creature is inside the radius. + */ + public boolean isInsideRadius3D(int x, int y, int z, int radius) + { + return calculateDistance3D(x, y, z) < radius; + } + + /** + * Overridden in: + *
  • Player
  • + * @return True if arrows are available. + */ + protected boolean checkAndEquipArrows() + { + return true; + } + + /** + * Add Exp and Sp to the Creature.
    + *
    + * Overridden in: + *
  • Player
  • + *
  • Pet

  • + * @param addToExp + * @param addToSp + */ + public synchronized void addExpAndSp(double addToExp, double addToSp) + { + // Dummy method (overridden by players and pets). + } + + /** + * Overridden in: + *
  • Player
  • + * @return the active weapon instance (always equipped in the right hand). + */ + public abstract Item getActiveWeaponInstance(); + + /** + * Overridden in: + *
  • Player
  • + * @return the active weapon item (always equipped in the right hand). + */ + public abstract Weapon getActiveWeaponItem(); + + /** + * Overridden in: + *
  • Player
  • + * @return the secondary weapon instance (always equipped in the left hand). + */ + public abstract Item getSecondaryWeaponInstance(); + + /** + * Overridden in: + *
  • Player
  • + * @return the secondary {@link ItemTemplate} item (always equipped in the left hand). + */ + public abstract ItemTemplate getSecondaryWeaponItem(); + + /** + * Manage hit process (called by Hit Task).
    + *
    + * Actions: + *
      + *
    • If the attacker/target is dead or use fake death, notify the AI with CANCEL and send a Server->Client packet ActionFailed (if attacker is a Player)
    • + *
    • If attack isn't aborted, send a message system (critical hit, missed...) to attacker/target if they are Player
    • + *
    • If attack isn't aborted and hit isn't missed, reduce HP of the target and calculate reflection damage to reduce HP of attacker if necessary
    • + *
    • if attack isn't aborted and hit isn't missed, manage attack or cast break of the target (calculating rate, sending message...)
    • + *
    + * @param target The Creature targeted + * @param damageValue Number of HP to reduce + * @param crit True if hit is critical + * @param miss True if hit is missed + * @param shld True if shield is efficient + * @param soulshot True if SoulShot are charged + * @param rechargeShots True if SoulShots are re-charged + */ + public void onHitTimer(Creature target, int damageValue, boolean crit, boolean miss, byte shld, boolean soulshot, boolean rechargeShots) + { + // If the attacker/target is dead or use fake death, notify the AI with CANCEL + // and send a Server->Client packet ActionFailed (if attacker is a Player) + if ((target == null) || isAlikeDead()) + { + getAI().notifyActionCancel(); + return; + } + + // Check if fake players should aggro each other. + if (isFakePlayer() && !FakePlayersConfig.FAKE_PLAYER_AGGRO_FPC && target.isFakePlayer()) + { + return; + } + + if ((isNpc() && target.isAlikeDead()) || target.isDead() || (!isInSurroundingRegion(target) && !isDoor())) + { + // getAI().setIntentionActive(); + // Some times attack is processed but target die before the hit + // So we need to recharge shot for next attack + if (rechargeShots) + { + rechargeShots(true, false); + } + + getAI().notifyActionCancel(); + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + + if (miss) + { + // Notify target AI + if (target.hasAI()) + { + target.getAI().notifyActionEvaded(this); + } + + notifyAttackAvoid(target, false); + } + // else + // { + // If we didn't miss the hit, discharge the shoulshots, if any. + // setChargedShot(ShotType.SOULSHOTS, false); + // } + + // Send message about damage/crit or miss + int damage = damageValue; + sendDamageMessage(target, damage, false, crit, miss); + + // Check Raidboss attack Creature will be petrified if attacking a raid that's more than 8 levels lower + if (target.isRaid() && target.giveRaidCurse() && !NpcConfig.RAID_DISABLE_CURSE && (getLevel() > (target.getLevel() + 8))) + { + final Skill skill = CommonSkill.RAID_CURSE2.getSkill(); + if (skill != null) + { + abortAttack(); + abortCast(); + getAI().setIntentionIdle(); + skill.applyEffects(target, this); + } + else + { + LOGGER.warning("Skill 4515 at level 1 is missing in DP."); + } + + damage = 0; // prevents messing up drop calculation + } + + // If Creature target is a Player, send a system message + if (target.isPlayer()) + { + final Player enemy = target.asPlayer(); + enemy.getAI().clientStartAutoAttack(); + } + + if (!miss && (damage > 0)) + { + final Weapon weapon = getActiveWeaponItem(); + final boolean isBow = (weapon != null) && (weapon.getItemType() == WeaponType.BOW); + int reflectedDamage = 0; + if (!isBow && !target.isInvul()) // Do not reflect if weapon is of type bow or target is invunlerable + { + // quick fix for no drop from raid if boss attack high-level char with damage reflection + if (!target.isRaid() || (asPlayer() == null) || (asPlayer().getLevel() <= (target.getLevel() + 8))) + { + // Reduce HP of the target and calculate reflection damage to reduce HP of attacker if necessary. + final double reflectPercent = target.getStat().calcStat(Stat.REFLECT_DAMAGE_PERCENT, 0, null, null); + if (reflectPercent > 0) + { + reflectedDamage = (int) ((reflectPercent / 100.) * damage); + if (reflectedDamage > target.getMaxHp()) + { + reflectedDamage = target.getMaxHp(); + } + } + } + } + + // reduce targets HP + target.reduceCurrentHp(damage, this, null); + target.notifyDamageReceived(damage, this, null, crit, false); + if (reflectedDamage > 0) + { + reduceCurrentHp(reflectedDamage, target, true, false, null); + notifyDamageReceived(reflectedDamage, target, null, crit, false); + } + + if (!isBow) // Do not absorb if weapon is of type bow + { + // Absorb HP from the damage inflicted. + double absorbPercent = _stat.calcStat(Stat.ABSORB_DAMAGE_PERCENT, 0, null, null); + if (absorbPercent > 0) + { + final int maxCanAbsorb = (int) (_stat.getMaxRecoverableHp() - _status.getCurrentHp()); + int absorbDamage = (int) ((absorbPercent / 100.) * damage); + if (absorbDamage > maxCanAbsorb) + { + absorbDamage = maxCanAbsorb; // Can't absord more than max hp + } + + if (absorbDamage > 0) + { + setCurrentHp(_status.getCurrentHp() + absorbDamage); + } + } + + // Absorb MP from the damage inflicted + absorbPercent = _stat.calcStat(Stat.ABSORB_MANA_DAMAGE_PERCENT, 0, null, null); + if (absorbPercent > 0) + { + final int maxCanAbsorb = (int) (_stat.getMaxRecoverableMp() - _status.getCurrentMp()); + int absorbDamage = (int) ((absorbPercent / 100.) * damage); + if (absorbDamage > maxCanAbsorb) + { + absorbDamage = maxCanAbsorb; // Can't absord more than max hp + } + + if (absorbDamage > 0) + { + setCurrentMp(_status.getCurrentMp() + absorbDamage); + } + } + } + + // Notify AI with ATTACKED + if (target.hasAI()) + { + target.getAI().notifyActionAttacked(this); + } + + getAI().clientStartAutoAttack(); + if (isSummon()) + { + final Player owner = asSummon().getOwner(); + if (owner != null) + { + owner.getAI().clientStartAutoAttack(); + } + } + + // Manage attack or cast break of the target (calculating rate, sending message...) + if (!target.isRaid() && Formulas.calcAtkBreak(target, damage)) + { + target.breakAttack(); + target.breakCast(); + } + + if (!_triggerSkills.isEmpty()) + { + for (OptionSkillHolder holder : _triggerSkills.values()) + { + if (((!crit && (holder.getSkillType() == OptionSkillType.ATTACK)) || ((holder.getSkillType() == OptionSkillType.CRITICAL) && crit)) && (Rnd.get(100) < holder.getChance())) + { + makeTriggerCast(holder.getSkill(), target); + } + } + } + + // Launch weapon onCritical Special ability effect if available + if (crit && (weapon != null)) + { + weapon.castOnCriticalSkill(this, target); + } + } + + // Recharge any active auto-soulshot tasks for current creature. + if (rechargeShots) + { + rechargeShots(true, false); + } + } + + /** + * Break an attack and send Server->Client ActionFailed packet and a System Message to the Creature. + */ + public void breakAttack() + { + if (isAttackingNow()) + { + // Abort the attack of the Creature and send Server->Client ActionFailed packet. + abortAttack(); + if (isPlayer()) + { + // Send a system message. + sendPacket(SystemMessageId.YOUR_ATTACK_HAS_FAILED); + } + } + } + + /** + * Break a cast and send Server->Client ActionFailed packet and a System Message to the Creature. + */ + public void breakCast() + { + // damage can only cancel magical & static skills + if (_isCastingNow && canAbortCast() && (_lastSkillCast != null) && (_lastSkillCast.isMagic() || _lastSkillCast.isStatic())) + { + // Abort the cast of the Creature and send Server->Client MagicSkillCanceld/ActionFailed packet. + abortCast(); + + if (isPlayer()) + { + // Send a system message. + sendPacket(SystemMessageId.YOUR_CASTING_HAS_BEEN_INTERRUPTED); + } + } + } + + /** + * Reduce the arrow number of the Creature.
    + *
    + * Overridden in: + *
  • Player

  • + */ + protected void reduceArrowCount() + { + // default is to do nothing + } + + /** + * Manage Forced attack (shift + select target).
    + *
    + * Actions: + *
      + *
    • If Creature or target is in a town area, send a system message TARGET_IN_PEACEZONE a Server->Client packet ActionFailed
    • + *
    • If target is confused, send a Server->Client packet ActionFailed
    • + *
    • If Creature is a Artefact, send a Server->Client packet ActionFailed
    • + *
    • Send a Server->Client packet MyTargetSelected to start attack and Notify AI with ATTACK
    • + *
    + * @param player The Player to attack + */ + @Override + public void onForcedAttack(Player player) + { + if (isInsidePeaceZone(player)) + { + // If Creature or target is in a peace zone, send a system message TARGET_IN_PEACEZONE a Server->Client packet ActionFailed. + player.sendPacket(SystemMessageId.YOU_MAY_NOT_ATTACK_THIS_TARGET_IN_A_PEACEFUL_ZONE); + player.sendPacket(ActionFailed.STATIC_PACKET); + return; + } + + if (player.isInOlympiadMode() && (player.getTarget() != null) && player.getTarget().isPlayable()) + { + Player target = null; + final WorldObject object = player.getTarget(); + if ((object != null) && object.isPlayable()) + { + target = object.asPlayer(); + } + + if ((target == null) || (target.isInOlympiadMode() && (!player.isOlympiadStart() || (player.getOlympiadGameId() != target.getOlympiadGameId())))) + { + // If Player is in Olympia and the match isn't already start, send a Server->Client packet ActionFailed. + player.sendPacket(ActionFailed.STATIC_PACKET); + return; + } + } + + if ((player.getTarget() != null) && !player.getTarget().canBeAttacked() && !player.getAccessLevel().allowPeaceAttack()) + { + // If target is not attackable, send a Server->Client packet ActionFailed. + player.sendPacket(ActionFailed.STATIC_PACKET); + return; + } + + if (player.isConfused()) + { + // If target is confused, send a Server->Client packet ActionFailed. + player.sendPacket(ActionFailed.STATIC_PACKET); + return; + } + + // GeoData Los Check or dz > 1000. + // if (!GeoEngine.getInstance().canSeeTarget(player, this)) + // { + // player.sendPacket(SystemMessageId.CANNOT_SEE_TARGET); + // player.sendPacket(ActionFailed.STATIC_PACKET); + // return; + // } + + // Notify AI with ATTACK. + player.getAI().setIntentionAttack(this); + } + + /** + * @param attacker + * @return True if inside peace zone. + */ + public boolean isInsidePeaceZone(Player attacker) + { + return isInsidePeaceZone(attacker, this); + } + + public boolean isInsidePeaceZone(Player attacker, WorldObject target) + { + return (!attacker.getAccessLevel().allowPeaceAttack() && isInsidePeaceZone((WorldObject) attacker, target)); + } + + public boolean isInsidePeaceZone(WorldObject attacker, WorldObject target) + { + if ((target == null) || !((target.isPlayable() || target.isFakePlayer()) && attacker.isPlayable())) + { + return false; + } + + if (InstanceManager.getInstance().getInstance(getInstanceId()).isPvP()) + { + return false; + } + + if (PlayerConfig.ALT_GAME_KARMA_PLAYER_CAN_BE_KILLED_IN_PEACEZONE) + { + // Allows red to be attacked and red to attack flagged players. + final Player player = asPlayer(); + if ((player != null) && (player.getKarma() > 0)) + { + return false; + } + + final Player attackerPlayer = attacker.asPlayer(); + if ((attackerPlayer != null) && (attackerPlayer.getKarma() > 0) && (player != null) && (player.getPvpFlag() > 0)) + { + return false; + } + } + + return (target.isInsideZone(ZoneId.PEACE) || attacker.isInsideZone(ZoneId.PEACE) || target.isInsideZone(ZoneId.NO_PVP) || attacker.isInsideZone(ZoneId.NO_PVP)); + } + + /** + * @return true if this character is inside an active grid. + */ + public boolean isInActiveRegion() + { + final WorldRegion region = getWorldRegion(); + return ((region != null) && (region.isActive())); + } + + /** + * @return True if the Creature has a Party in progress. + */ + public boolean isInParty() + { + return false; + } + + /** + * @return the Party object of the Creature. + */ + public Party getParty() + { + return null; + } + + /** + * @return the Attack Speed of the Creature (delay (in milliseconds) before next attack). + */ + public int calculateTimeBetweenAttacks() + { + return (int) (500000 / _stat.getPAtkSpd()); + } + + /** + * @param weapon + * @return the Reuse Time of Attack (used for bow delay) + */ + public int calculateReuseTime(Weapon weapon) + { + if ((weapon == null) || (weapon.getReuseDelay() == 0)) + { + return 0; + } + + return (int) ((weapon.getReuseDelay() * 333) / _stat.getPAtkSpd()); + } + + /** + * Add a skill to the Creature _skills and its Func objects to the calculator set of the Creature.
    + *
    + * Concept:
    + *
    + * All skills own by a Creature are identified in _skills
    + *
    + * Actions: + *
      + *
    • Replace oldSkill by newSkill or Add the newSkill
    • + *
    • If an old skill has been replaced, remove all its Func objects of Creature calculator set
    • + *
    • Add Func objects of newSkill to the calculator set of the Creature
    • + *
    + *
    + * Overridden in: + *
      + *
    • Player : Save update in the character_skills table of the database
    • + *
    + * @param newSkill The Skill to add to the Creature + * @return The Skill replaced or null if just added a new Skill + */ + public Skill addSkill(Skill newSkill) + { + Skill oldSkill = null; + if (newSkill != null) + { + // Replace oldSkill by newSkill or Add the newSkill. + oldSkill = _skills.put(newSkill.getId(), newSkill); + + // If an old skill has been replaced, remove all its Func objects. + if (oldSkill != null) + { + removeStatsOwner(oldSkill); + + if (oldSkill.isPassive()) + { + stopSkillEffects(SkillFinishType.NORMAL, oldSkill.getId()); + } + } + + // Add Func objects of newSkill to the calculator set of the Creature + addStatFuncs(newSkill.getStatFuncs(null, this)); + if (newSkill.isPassive()) + { + newSkill.applyEffects(this, this, false, true, false, 0); + } + } + + return oldSkill; + } + + public Skill removeSkill(Skill skill, boolean cancelEffect) + { + return (skill != null) ? removeSkill(skill.getId(), cancelEffect) : null; + } + + public Skill removeSkill(int skillId) + { + return removeSkill(skillId, true); + } + + public Skill removeSkill(int skillId, boolean cancelEffect) + { + // Remove the skill from the Creature _skills. + final Skill oldSkill = _skills.remove(skillId); + + // Remove all its Func objects from the Creature calculator set. + if (oldSkill != null) + { + // Stop casting if this skill is used right now. + if ((_lastSkillCast != null) && _isCastingNow && (oldSkill.getId() == _lastSkillCast.getId())) + { + abortCast(); + } + + if ((_lastSimultaneousSkillCast != null) && _isCastingSimultaneouslyNow && (oldSkill.getId() == _lastSimultaneousSkillCast.getId())) + { + abortCast(); + } + + // Stop effects. + if (cancelEffect || oldSkill.isToggle() || oldSkill.isPassive()) + { + removeStatsOwner(oldSkill); + stopSkillEffects(SkillFinishType.REMOVED, oldSkill.getId()); + } + } + + return oldSkill; + } + + /** + * Concept:
    + *
    + * All skills own by a Creature are identified in _skills the Creature + * @return all skills own by the Creature in a table of Skill. + */ + public Collection getAllSkills() + { + return _skills.values(); + } + + /** + * @return the map containing this character skills. + */ + public Map getSkills() + { + return _skills; + } + + /** + * Return the level of a skill owned by the Creature. + * @param skillId The identifier of the Skill whose level must be returned + * @return The level of the Skill identified by skillId + */ + public int getSkillLevel(int skillId) + { + final Skill skill = getKnownSkill(skillId); + return (skill == null) ? 0 : skill.getLevel(); + } + + /** + * @param skillId The identifier of the Skill to check the knowledge + * @return the skill from the known skill. + */ + public Skill getKnownSkill(int skillId) + { + return _skills.get(skillId); + } + + /** + * Return the number of buffs affecting this Creature. + * @return The number of Buffs affecting this Creature + */ + public int getBuffCount() + { + return _effectList.getBuffCount(); + } + + public int getDanceCount() + { + return _effectList.getDanceCount(); + } + + /** + * Manage the magic skill launching task (MP, HP, Item consumation...) and display the magic skill animation on client.
    + *
    + * Actions: + *
      + *
    • Send a Server->Client packet MagicSkillLaunched (to display magic skill animation) to all Player of Creature known players
    • + *
    • Consumme MP, HP and Item if necessary
    • + *
    • Send a Server->Client packet StatusUpdate with MP modification to the Player
    • + *
    • Launch the magic skill in order to calculate its effects
    • + *
    • If the skill type is PDAM, notify the AI of the target with ATTACK
    • + *
    • Notify the AI of the Creature with FINISH_CASTING
    • + *
    + * Caution: A magic skill casting MUST BE in progress + * @param mut + */ + public void onMagicLaunchedTimer(MagicUseTask mut) + { + final Skill skill = mut.getSkill(); + final List targets = mut.getTargets(); + if ((skill == null) || (targets == null)) + { + abortCast(); + return; + } + + if (targets.isEmpty()) + { + switch (skill.getTargetType()) + { + // only AURA-type skills can be cast without target + case AURA: + case FRONT_AURA: + case BEHIND_AURA: + case AURA_CORPSE_MOB: + case AURA_FRIENDLY: + { + break; + } + default: + { + abortCast(); + return; + } + } + } + + // Escaping from under skill's radius and peace zone check. First version, not perfect in AoE skills. + int escapeRange = 0; + if (skill.getEffectRange() > escapeRange) + { + escapeRange = skill.getEffectRange(); + } + else if ((skill.getCastRange() < 0) && (skill.getAffectRange() > 80)) + { + escapeRange = skill.getAffectRange(); + } + + if (!targets.isEmpty() && (escapeRange > 0)) + { + int skipRange = 0; + int skipLOS = 0; + int skipPeaceZone = 0; + final List targetList = new ArrayList<>(); + for (WorldObject target : targets) + { + if (target.isCreature()) + { + if (!isInsideRadius3D(target.getX(), target.getY(), target.getZ(), escapeRange + _template.getCollisionRadius())) + { + skipRange++; + continue; + } + + // Healing party members should ignore LOS. + if (((skill.getTargetType() != TargetType.PARTY) || !skill.hasEffectType(EffectType.HEAL)) // + && (mut.getSkillTime() > 550) && !GeoEngine.getInstance().canSeeTarget(this, target)) + { + skipLOS++; + continue; + } + + if (skill.hasNegativeEffect()) + { + if (isPlayer()) + { + if (target.asCreature().isInsidePeaceZone(asPlayer())) + { + skipPeaceZone++; + continue; + } + } + else + { + if (target.asCreature().isInsidePeaceZone(this, target)) + { + skipPeaceZone++; + continue; + } + } + } + + targetList.add(target); + } + } + + if (targetList.isEmpty()) + { + if (isPlayer()) + { + if (skipRange > 0) + { + sendPacket(SystemMessageId.THE_DISTANCE_IS_TOO_FAR_AND_SO_THE_CASTING_HAS_BEEN_STOPPED); + } + else if (skipLOS > 0) + { + sendPacket(SystemMessageId.CANNOT_SEE_TARGET); + } + else if (skipPeaceZone > 0) + { + sendMessage("A malicious skill cannot be used in a peace zone."); + } + } + + abortCast(); + return; + } + + mut.setTargets(targetList); + } + + // Ensure that a cast is in progress + // Check if player is using fake death. + // Static skills can be used while faking death. + if ((mut.isSimultaneous() && !_isCastingSimultaneouslyNow) || (!mut.isSimultaneous() && !_isCastingNow) || (isAlikeDead() && !skill.isStatic())) + { + // now cancels both, simultaneous and normal + getAI().notifyActionCancel(); + return; + } + + mut.setPhase(2); + if (mut.getSkillTime() == 0) + { + onMagicHitTimer(mut); + } + else + { + _skillCast = ThreadPool.schedule(mut, 400); + } + } + + // Runs in the end of skill casting + public void onMagicHitTimer(MagicUseTask mut) + { + final Skill skill = mut.getSkill(); + final List targets = mut.getTargets(); + if ((skill == null) || (targets == null)) + { + abortCast(); + return; + } + + try + { + // Go through targets table + for (WorldObject tgt : targets) + { + if (tgt.isPlayable()) + { + if (isPlayer() && tgt.isSummon()) + { + tgt.asSummon().updateAndBroadcastStatus(1); + } + } + else if (isPlayable() && tgt.isAttackable()) + { + final Creature target = tgt.asCreature(); + if (skill.getEffectPoint() > 0) + { + target.asAttackable().reduceHate(this, skill.getEffectPoint()); + } + else if (skill.getEffectPoint() < 0) + { + target.asAttackable().addDamageHate(this, 0, -skill.getEffectPoint()); + } + } + } + + rechargeShots(skill.useSoulShot(), skill.useSpiritShot()); + + final StatusUpdate su = new StatusUpdate(this); + boolean isSendStatus = false; + + // Consume MP of the Creature and Send the Server->Client packet StatusUpdate with current HP and MP to all other Player to inform + final double mpConsume = _stat.getMpConsume(skill); + if (mpConsume > 0) + { + if (mpConsume > _status.getCurrentMp()) + { + sendPacket(SystemMessageId.NOT_ENOUGH_MP); + abortCast(); + return; + } + + _status.reduceMp(mpConsume); + su.addAttribute(StatusUpdate.CUR_MP, (int) _status.getCurrentMp()); + isSendStatus = true; + } + + // Consume HP if necessary and Send the Server->Client packet StatusUpdate with current HP and MP to all other Player to inform + if (skill.getHpConsume() > 0) + { + final double consumeHp = skill.getHpConsume(); + if (consumeHp >= _status.getCurrentHp()) + { + sendPacket(SystemMessageId.NOT_ENOUGH_HP); + abortCast(); + return; + } + + _status.reduceHp(consumeHp, this, true); + su.addAttribute(StatusUpdate.CUR_HP, (int) _status.getCurrentHp()); + isSendStatus = true; + } + + // Send a Server->Client packet StatusUpdate with MP modification to the Player + if (isSendStatus) + { + sendPacket(su); + } + + // Consume Charges + if (isPlayer() && (skill.getChargeConsumeCount() > 0)) + { + asPlayer().decreaseCharges(skill.getChargeConsumeCount()); + } + + // Launch the magic skill in order to calculate its effects + callSkill(mut.getSkill(), mut.getTargets()); + } + catch (NullPointerException e) + { + LOGGER.log(Level.WARNING, "onMagicHitTimer: ", e); + } + + if (mut.getSkillTime() > 0) + { + mut.setCount(mut.getCount() + 1); + } + + mut.setPhase(3); + if (mut.getSkillTime() == 0) + { + onMagicFinalizer(mut); + } + else + { + if (mut.isSimultaneous()) + { + _skillCast2 = ThreadPool.schedule(mut, 0); + } + else + { + _skillCast = ThreadPool.schedule(mut, 0); + } + } + } + + // Runs after skillTime + public void onMagicFinalizer(MagicUseTask mut) + { + if (mut.isSimultaneous()) + { + _skillCast2 = null; + setCastingSimultaneouslyNow(false); + return; + } + + // Cleanup + _skillCast = null; + _castInterruptTime = 0; + + // On each repeat recharge shots before cast. + if (mut.getCount() > 0) + { + rechargeShots(mut.getSkill().useSoulShot(), mut.getSkill().useSpiritShot()); + } + + // Stop casting + setCastingNow(false); + setCastingSimultaneouslyNow(false); + + final Skill skill = mut.getSkill(); + final WorldObject target = !mut.getTargets().isEmpty() ? mut.getTargets().get(0) : null; + + // Attack target after skill use. + if (skill.nextActionIsAttack() && (_target != this) && (target != null) && (_target == target) && _target.isCreature() && target.canBeAttacked() && (!isPlayer() || !asPlayer().isAutoPlaying())) + { + final Intention nextIntention = getAI().getNextIntention(); + if (nextIntention != Intention.MOVE_TO) + { + if (isPlayer()) + { + final SkillUseHolder currSkill = asPlayer().getCurrentSkill(); + if ((currSkill == null) || !currSkill.isShiftPressed()) + { + ThreadPool.schedule(() -> + { + if (!isDisabled() && !isAttackingOrCastingNow()) + { + getAI().setIntentionAttack(target); + } + }, 333); // Wait for skill land animation. + } + } + else + { + getAI().setIntentionAttack(target); + } + } + else if (isPlayer()) // Player is moving. + { + ThreadPool.schedule(() -> completeMagicFinalizer(skill, target), 333); // Wait for skill land animation. + return; + } + } + + completeMagicFinalizer(skill, target); + } + + private void completeMagicFinalizer(Skill skill, WorldObject target) + { + if (skill.hasNegativeEffect() && (skill.getTargetType() != TargetType.UNLOCKABLE)) + { + getAI().clientStartAutoAttack(); + } + + // Notify the AI of the Creature with FINISH_CASTING + getAI().notifyActionFinishCasting(); + + // Notify DP Scripts + notifyQuestEventSkillFinished(skill, target); + + // If character is a player, then wipe their current cast state and check if a skill is queued. + // If there is a queued skill, launch it and wipe the queue. + if (isPlayer()) + { + final Player player = asPlayer(); + final SkillUseHolder queuedSkill = player.getQueuedSkill(); + player.setCurrentSkill(null, false, false); + if (queuedSkill != null) + { + player.setQueuedSkill(null, false, false); + + // DO NOT USE: Recursive call to useMagic() method. + // player.useMagic(queuedSkill.getSkill(), queuedSkill.isCtrlPressed(), queuedSkill.isShiftPressed()); + ThreadPool.execute(new QueuedMagicUseTask(player, queuedSkill.getSkill(), queuedSkill.isCtrlPressed(), queuedSkill.isShiftPressed())); + } + } + + if (isChanneling()) + { + getSkillChannelizer().stopChanneling(); + } + } + + // Quest event ON_SPELL_FNISHED + protected void notifyQuestEventSkillFinished(Skill skill, WorldObject target) + { + } + + /** + * Launch the magic skill and calculate its effects on each target contained in the targets table. + * @param skill The Skill to use + * @param targets The table of WorldObject targets + */ + public void callSkill(Skill skill, List targets) + { + try + { + // Check if the toggle skill effects are already in progress on the Creature + if (skill.isToggle() && isAffectedBySkill(skill.getId())) + { + return; + } + + if ((targets == null) || targets.isEmpty()) + { + return; + } + + // Block RaidBoss from receiving heals/buffs from players (and their pet/summon). + if (GrandBossConfig.DISABLE_RAIDBOSS_HEAL_FROM_PLAYERS || GrandBossConfig.DISABLE_RAIDBOSS_BUFF_FROM_PLAYERS) + { + final Player actingPlayer = isPlayer() ? asPlayer() : (isSummon() ? asSummon().getOwner() : (isPet() ? asPet().getOwner() : null)); + if (actingPlayer != null) + { + final boolean isHealSkill = skill.hasEffectType(EffectType.HEAL, EffectType.CPHEAL, EffectType.MANAHEAL_BY_LEVEL, EffectType.MANAHEAL_PERCENT, EffectType.REBALANCE_HP); + final boolean isBuffSkill = !isHealSkill && (skill.hasEffectType(EffectType.BUFF, EffectType.NOBLESSE_BLESSING, EffectType.CHARM_OF_LUCK, EffectType.RELAXING) || (skill.isContinuous() && !skill.isDebuff())); + final boolean blockRaidBoss = (isHealSkill && GrandBossConfig.DISABLE_RAIDBOSS_HEAL_FROM_PLAYERS) || (isBuffSkill && GrandBossConfig.DISABLE_RAIDBOSS_BUFF_FROM_PLAYERS); + + if (blockRaidBoss) + { + boolean hasRaidBossTarget = false; + for (WorldObject obj : targets) + { + if (obj instanceof RaidBoss) + { + hasRaidBossTarget = true; + break; + } + } + + if (hasRaidBossTarget) + { + // If the only target is a RaidBoss, nothing to apply. + if (targets.size() == 1) + { + return; + } + + // Do NOT reassign 'targets' (it's used later in lambdas). Filter in-place. + targets.removeIf(obj -> obj instanceof RaidBoss); + + if (targets.isEmpty()) + { + return; + } + } + } + } + } + + // Initial checks + for (WorldObject obj : targets) + { + if ((obj == null) || !obj.isCreature()) + { + continue; + } + + final Creature target = obj.asCreature(); + + // Check raid monster attack and check buffing characters who attack raid monsters. + Creature targetsAttackTarget = null; + Creature targetsCastTarget = null; + if (target.hasAI()) + { + targetsAttackTarget = target.getAI().getAttackTarget(); + targetsCastTarget = target.getAI().getCastTarget(); + } + + if (!NpcConfig.RAID_DISABLE_CURSE && ((target.isRaid() && target.giveRaidCurse() && (getLevel() > (target.getLevel() + 8))) || (!skill.hasNegativeEffect() && (targetsAttackTarget != null) && targetsAttackTarget.isRaid() && targetsAttackTarget.giveRaidCurse() && targetsAttackTarget.getAttackByList().contains(target) && (getLevel() > (targetsAttackTarget.getLevel() + 8))) || (!skill.hasNegativeEffect() && (targetsCastTarget != null) && targetsCastTarget.isRaid() && targetsCastTarget.giveRaidCurse() && targetsCastTarget.getAttackByList().contains(target) && (getLevel() > (targetsCastTarget.getLevel() + 8))))) + { + final CommonSkill curse = skill.isMagic() ? CommonSkill.RAID_CURSE : CommonSkill.RAID_CURSE2; + final Skill curseSkill = curse.getSkill(); + if (curseSkill != null) + { + abortAttack(); + abortCast(); + getAI().setIntentionIdle(); + curseSkill.applyEffects(target, this); + } + else + { + LOGGER.warning("Skill ID " + curse.getId() + " level " + curse.getLevel() + " is missing in DP!"); + } + return; + } + + // Check if over-hit is possible + if (skill.isOverhit() && target.isAttackable()) + { + target.asAttackable().overhitEnabled(true); + } + + // Static skills not trigger any chance skills + if (!skill.isStatic()) + { + // Launch weapon Special ability skill effect if available + final Weapon activeWeapon = getActiveWeaponItem(); + if ((activeWeapon != null) && !target.isDead()) + { + activeWeapon.castOnMagicSkill(this, target, skill); + } + + if (!_triggerSkills.isEmpty()) + { + for (OptionSkillHolder holder : _triggerSkills.values()) + { + if (((skill.isMagic() && (holder.getSkillType() == OptionSkillType.MAGIC)) || (skill.isPhysical() && (holder.getSkillType() == OptionSkillType.ATTACK))) && (Rnd.get(100) < holder.getChance())) + { + makeTriggerCast(holder.getSkill(), target); + } + } + } + } + } + + // Launch the magic skill and calculate its effects. + skill.activateSkill(this, targets); + + // Flag on Raid/Champion (player or owner of summon/pet). + if ((skill.getEffectPoint() <= 0) && skill.hasNegativeEffect() && (PvpConfig.FLAG_PLAYER_ON_RAID_ATTACK || PvpConfig.FLAG_PLAYER_ON_CHAMPION_ATTACK)) + { + final Player actingPlayer; + if (isPlayer()) + { + actingPlayer = asPlayer(); + } + else if (PvpConfig.FLAG_PLAYER_ON_SUMMON_PET_RAID_CHAMPION_ATTACK && (isSummon() || isPet())) + { + actingPlayer = isSummon() ? asSummon().getOwner() : asPet().getOwner(); + } + else + { + actingPlayer = null; + } + + if (actingPlayer != null) + { + for (WorldObject target : targets) + { + if (target.isCreature()) + { + final Creature targetCreature = target.asCreature(); + if (((targetCreature.isRaid() && PvpConfig.FLAG_PLAYER_ON_RAID_ATTACK) || (targetCreature.isChampion() && PvpConfig.FLAG_PLAYER_ON_CHAMPION_ATTACK))) + { + actingPlayer.updatePvPStatus(); + break; + } + } + } + } + } + + final Player player = asPlayer(); + if (player != null) + { + for (WorldObject target : targets) + { + // ATTACKED and PvPStatus + if (target.isCreature()) + { + if (skill.getEffectPoint() <= 0) + { + if ((target.isPlayable() || target.isTrap()) && skill.hasNegativeEffect()) + { + // Casted on target_self but don't harm self + if (!target.equals(this)) + { + // Combat-mode check + if (target.isPlayer()) + { + target.asPlayer().getAI().clientStartAutoAttack(); + } + else if (target.isSummon() && target.asCreature().hasAI()) + { + final Player owner = target.asSummon().getOwner(); + if (owner != null) + { + owner.getAI().clientStartAutoAttack(); + } + } + + // attack of the own pet does not flag player + // triggering trap not flag trap owner + if ((player.getSummon() != target) && !isTrap() && !((skill.getEffectPoint() == 0) && (skill.getAffectRange() > 0))) + { + player.updatePvPStatus(target.asCreature()); + } + } + } + else if (target.isAttackable()) + { + switch (skill.getId()) + { + case 51: // Lure + case 511: // Temptation + { + break; + } + default: + { + // add attacker into list + target.asCreature().addAttackerToAttackByList(this); + } + } + } + + // notify target AI about the attack + if (target.asCreature().hasAI() && skill.hasNegativeEffect() && !skill.hasEffectType(EffectType.HATE) && (skill.getAbnormalType() != AbnormalType.TURN_PASSIVE)) + { + target.asCreature().getAI().notifyActionAttacked(this); + } + } + else + { + if (target.isPlayer()) + { + // Casting non offensive skill on player with pvp flag set or with karma. + if (!(target.equals(this) || target.equals(player)) && ((target.asPlayer().getPvpFlag() > 0) || (target.asPlayer().getKarma() > 0))) + { + player.updatePvPStatus(); + } + } + else if (target.isAttackable()) + { + player.updatePvPStatus(); + } + } + } + + if (target.isFakePlayer() && !FakePlayersConfig.FAKE_PLAYER_AUTO_ATTACKABLE) + { + player.updatePvPStatus(); + } + } + + // Mobs in range 1000 see spell. + World.forEachVisibleObjectInRange(player, Npc.class, 1000, npc -> + { + if (EventDispatcher.getInstance().hasListener(EventType.ON_NPC_SKILL_SEE, npc)) + { + EventDispatcher.getInstance().notifyEventAsync(new OnNpcSkillSee(npc, player, skill, targets, isSummon()), npc); + } + + // On Skill See logic. + if (npc.isAttackable()) + { + final Attackable attackable = npc.asAttackable(); + int skillEffectPoint = skill.getEffectPoint(); + if (player.hasSummon() && (targets.size() == 1) && targets.contains(player.getSummon())) + { + skillEffectPoint = 0; + } + + if ((skillEffectPoint > 0) && attackable.hasAI() && (attackable.getAI().getIntention() == Intention.ATTACK)) + { + final WorldObject npcTarget = attackable.getTarget(); + for (WorldObject skillTarget : targets) + { + if ((npcTarget == skillTarget) || (npc == skillTarget)) + { + final Creature originalCaster = isSummon() ? this : player; + attackable.addDamageHate(originalCaster, 0, (long) (((skillEffectPoint * 150) / (attackable.getLevel() + 7)) * attackable.getHateRatio(originalCaster))); + } + } + } + + // Players which are 9 levels above a Raid Boss and cast a skill nearby, are silenced with the Raid Curse skill. + if (!NpcConfig.RAID_DISABLE_CURSE && attackable.isRaid() && attackable.giveRaidCurse() && attackable.isInCombat() && ((player.getLevel() - attackable.getLevel()) > 8)) + { + player.abortAttack(); + player.abortCast(); + player.getAI().setIntentionIdle(); + + final CommonSkill curse = skill.hasNegativeEffect() ? CommonSkill.RAID_CURSE2 : CommonSkill.RAID_CURSE; + curse.getSkill().applyEffects(attackable, player); + } + } + }); + } + + // Notify AI + if (skill.hasNegativeEffect() && !skill.hasEffectType(EffectType.HATE)) + { + for (WorldObject target : targets) + { + if (target.isCreature()) + { + final Creature creature = target.asCreature(); + if (creature.hasAI()) + { + // Notify target AI about the attack. + creature.getAI().notifyActionAttacked(this); + } + } + + if (isFakePlayer()) // fake player attacks player + { + if (target.isPlayable() || target.isFakePlayer()) + { + final Npc npc = asNpc(); + if (!npc.isScriptValue(1)) + { + npc.setScriptValue(1); // in combat + npc.broadcastInfo(); // update flag status + ScriptManager.getInstance().getScript("PvpFlaggingStopTask").notifyEvent("FLAG_CHECK", npc, null); + } + } + } + } + } + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": callSkill() failed.", e); + } + } + + /** + * @return the Level Modifier ((level + 89) / 100). + */ + public double getLevelMod() + { + return ((getLevel() + 89) / 100d); + } + + public void setSkillCast(Future newSkillCast) + { + _skillCast = newSkillCast; + } + + /** + * Sets _isCastingNow to true and _castInterruptTime is calculated from end time (ticks) + * @param newSkillCastEndTick + */ + public void forceIsCasting(int newSkillCastEndTick) + { + setCastingNow(true); + + // for interrupt -400 ms + _castInterruptTime = newSkillCastEndTick - 4; + } + + private boolean _disabledAI = false; + + public void updatePvPFlag(int value) + { + // Overridden in Player + } + + /** + * @return a multiplier based on weapon random damage + */ + public double getRandomDamageMultiplier() + { + int random; + if (getActiveWeaponItem() != null) + { + random = (int) _stat.calcStat(Stat.RANDOM_DAMAGE, 0); + } + else + { + random = 5 + (int) Math.sqrt(getLevel()); + } + + return (1 + ((double) Rnd.get(0 - random, random) / 100)); + } + + public long getAttackEndTime() + { + return _attackEndTime; + } + + public int getBowAttackEndTime() + { + return _disableBowAttackEndTime; + } + + /** + * Not Implemented. + * @return + */ + public abstract int getLevel(); + + public double calcStat(Stat stat, double init) + { + return _stat.calcStat(stat, init, null, null); + } + + // Stat - NEED TO REMOVE ONCE CREATURESTATUS IS COMPLETE + public double calcStat(Stat stat, double init, Creature target, Skill skill) + { + return _stat.calcStat(stat, init, target, skill); + } + + public int getAccuracy() + { + return _stat.getAccuracy(); + } + + public float getAttackSpeedMultiplier() + { + return _stat.getAttackSpeedMultiplier(); + } + + public double getCriticalDmg(Creature target, double init) + { + return _stat.getCriticalDmg(target, init); + } + + public int getCriticalHit(Creature target, Skill skill) + { + return _stat.getCriticalHit(target, skill); + } + + public int getEvasionRate(Creature target) + { + return _stat.getEvasionRate(target); + } + + public int getMagicalAttackRange(Skill skill) + { + return _stat.getMagicalAttackRange(skill); + } + + public int getMaxCp() + { + return _stat.getMaxCp(); + } + + public int getMaxRecoverableCp() + { + return _stat.getMaxRecoverableCp(); + } + + public double getMAtk(Creature target, Skill skill) + { + return _stat.getMAtk(target, skill); + } + + public int getMAtkSpd() + { + return _stat.getMAtkSpd(); + } + + public int getMaxMp() + { + return _stat.getMaxMp(); + } + + public int getMaxRecoverableMp() + { + return _stat.getMaxRecoverableMp(); + } + + public int getMaxHp() + { + return _stat.getMaxHp(); + } + + public int getMaxRecoverableHp() + { + return _stat.getMaxRecoverableHp(); + } + + public int getMCriticalHit(Creature target, Skill skill) + { + return _stat.getMCriticalHit(target, skill); + } + + public double getMDef(Creature target, Skill skill) + { + return _stat.getMDef(target, skill); + } + + public double getMReuseRate(Skill skill) + { + return _stat.getMReuseRate(skill); + } + + public double getPAtk(Creature target) + { + return _stat.getPAtk(target); + } + + public double getPAtkSpd() + { + return _stat.getPAtkSpd(); + } + + public double getPDef(Creature target) + { + return _stat.getPDef(target); + } + + public int getPhysicalAttackRange() + { + return _stat.getPhysicalAttackRange(); + } + + public double getMovementSpeedMultiplier() + { + return _stat.getMovementSpeedMultiplier(); + } + + public double getRunSpeed() + { + return _stat.getRunSpeed(); + } + + public double getWalkSpeed() + { + return _stat.getWalkSpeed(); + } + + public double getSwimRunSpeed() + { + return _stat.getSwimRunSpeed(); + } + + public double getSwimWalkSpeed() + { + return _stat.getSwimWalkSpeed(); + } + + public double getMoveSpeed() + { + return _stat.getMoveSpeed(); + } + + public int getShldDef() + { + return _stat.getShldDef(); + } + + public int getSTR() + { + return _stat.getSTR(); + } + + public int getDEX() + { + return _stat.getDEX(); + } + + public int getCON() + { + return _stat.getCON(); + } + + public int getINT() + { + return _stat.getINT(); + } + + public int getWIT() + { + return _stat.getWIT(); + } + + public int getMEN() + { + return _stat.getMEN(); + } + + // Status - NEED TO REMOVE ONCE CREATURESTATUS IS COMPLETE + public void addStatusListener(Creature object) + { + _status.addStatusListener(object); + } + + public void reduceCurrentHp(double amount, Creature attacker, Skill skill) + { + reduceCurrentHp(amount, attacker, true, false, skill); + } + + public void reduceCurrentHpByDOT(double amount, Creature attacker, Skill skill) + { + reduceCurrentHp(amount, attacker, !skill.isToggle(), true, skill); + } + + public void reduceCurrentHp(double amount, Creature attacker, boolean awake, boolean isDOT, Skill skill) + { + if (ChampionMonstersConfig.CHAMPION_ENABLE && isChampion() && (ChampionMonstersConfig.CHAMPION_HP != 0)) + { + _status.reduceHp(amount / ChampionMonstersConfig.CHAMPION_HP, attacker, awake, isDOT, false); + } + else + { + if (isPlayer() && !isDOT && (skill != null) && (skill.getCastRange() > 0) && (attacker != null) && !GeoEngine.getInstance().canSeeTarget(attacker, this)) + { + amount = 0; + } + + _status.reduceHp(amount, attacker, awake, isDOT, false); + } + } + + public void reduceCurrentMp(double amount) + { + _status.reduceMp(amount); + } + + @Override + public void removeStatusListener(Creature object) + { + _status.removeStatusListener(object); + } + + protected void stopHpMpRegeneration() + { + _status.stopHpMpRegeneration(); + } + + public double getCurrentCp() + { + return _status.getCurrentCp(); + } + + public int getCurrentCpPercent() + { + return (int) ((_status.getCurrentCp() * 100) / _stat.getMaxCp()); + } + + public void setCurrentCp(double newCp) + { + _status.setCurrentCp(newCp); + } + + public double getCurrentHp() + { + return _status.getCurrentHp(); + } + + public int getCurrentHpPercent() + { + return (int) ((_status.getCurrentHp() * 100) / _stat.getMaxHp()); + } + + public void setCurrentHp(double newHp) + { + _status.setCurrentHp(newHp); + } + + public void setCurrentHpMp(double newHp, double newMp) + { + _status.setCurrentHpMp(newHp, newMp); + } + + public double getCurrentMp() + { + return _status.getCurrentMp(); + } + + public int getCurrentMpPercent() + { + return (int) ((_status.getCurrentMp() * 100) / _stat.getMaxMp()); + } + + public void setCurrentMp(double newMp) + { + _status.setCurrentMp(newMp); + } + + /** + * Fully restores the creature's HP and MP to their maximum values. + */ + public void fullRestore() + { + _status.setCurrentHp(getMaxHp()); + _status.setCurrentMp(getMaxMp(), isPlayable()); + } + + /** + * @return the max weight that the Creature can load. + */ + public int getMaxLoad() + { + if (isPlayer() || isPet()) + { + // Weight Limit = (CON Modifier*69000) * Skills. + // Source http://l2p.bravehost.com/weightlimit.html (May 2007) + final double baseLoad = Math.floor(BaseStat.CON.calcBonus(this) * 69000 * PlayerConfig.ALT_WEIGHT_LIMIT); + return (int) calcStat(Stat.WEIGHT_LIMIT, baseLoad, this, null); + } + + return 0; + } + + public int getBonusWeightPenalty() + { + if (isPlayer() || isPet()) + { + return (int) calcStat(Stat.WEIGHT_PENALTY, 1, this, null); + } + + return 0; + } + + /** + * @return the current weight of the Creature. + */ + public int getCurrentLoad() + { + if (isPlayer() || isPet()) + { + final Inventory inventory = getInventory(); + return (inventory != null) ? inventory.getTotalWeight() : 0; + } + + return 0; + } + + public boolean isChampion() + { + return false; + } + + /** + * Send system message about damage. + * @param target + * @param damage + * @param mcrit + * @param pcrit + * @param miss + */ + public void sendDamageMessage(Creature target, int damage, boolean mcrit, boolean pcrit, boolean miss) + { + if (miss && target.isPlayer()) + { + final SystemMessage sm = new SystemMessage(SystemMessageId.YOU_HAVE_AVOIDED_S1_S_ATTACK); + sm.addString(getName()); + target.sendPacket(sm); + } + } + + public byte getAttackElement() + { + return _stat.getAttackElement(); + } + + public int getAttackElementValue(byte attackAttribute) + { + return _stat.getAttackElementValue(attackAttribute); + } + + public int getDefenseElementValue(byte defenseAttribute) + { + return _stat.getDefenseElementValue(defenseAttribute); + } + + public void disableCoreAI(boolean value) + { + _disabledAI = value; + } + + public boolean isCoreAIDisabled() + { + return _disabledAI; + } + + /** + * @return true + */ + public boolean giveRaidCurse() + { + return true; + } + + /** + * Check if target is affected with special buff + * @see EffectList#isAffected(EffectFlag) + * @param flag int + * @return boolean + */ + public boolean isAffected(EffectFlag flag) + { + return _effectList.isAffected(flag); + } + + /** + * Check if target is affected by AbnormalType. + * @param type the AbnormalType + * @return boolean true if affected + */ + public boolean isAffectedByAbnormalType(AbnormalType type) + { + return _effectList.isAffectedByAbnormalType(type); + } + + public Team getTeam() + { + return _team; + } + + public void setTeam(Team team) + { + _team = team; + } + + public void setLethalable(boolean value) + { + _lethalable = value; + } + + public boolean isLethalable() + { + return _lethalable; + } + + public Map getTriggerSkills() + { + return _triggerSkills; + } + + public void addTriggerSkill(OptionSkillHolder holder) + { + getTriggerSkills().put(holder.getSkill().getId(), holder); + } + + public void removeTriggerSkill(OptionSkillHolder holder) + { + getTriggerSkills().remove(holder.getSkill().getId()); + } + + public void makeTriggerCast(Skill skill, Creature target, boolean ignoreTargetType) + { + try + { + if (skill == null) + { + return; + } + + if (skill.checkCondition(this, target, false)) + { + if (isSkillDisabled(skill)) + { + return; + } + + if (skill.getReuseDelay() > 0) + { + disableSkill(skill, skill.getReuseDelay()); + } + + final List targets = !ignoreTargetType ? skill.getTargetList(this, false, target) : Collections.singletonList(target); + if (targets.isEmpty()) + { + return; + } + + Creature skillTarget = target; + for (WorldObject obj : targets) + { + if ((obj != null) && obj.isCreature()) + { + skillTarget = obj.asCreature(); + break; + } + } + + if (PlayerConfig.ALT_VALIDATE_TRIGGER_SKILLS && isPlayable() && (skillTarget != null) && skillTarget.isPlayable()) + { + final Player player = asPlayer(); + if (!player.checkPvpSkill(skillTarget, skill)) + { + return; + } + } + + broadcastSkillPacket(new MagicSkillUse(this, skillTarget, skill.getDisplayId(), skill.getLevel(), 0, 0), skillTarget); + broadcastSkillPacket(new MagicSkillLaunched(this, skill.getDisplayId(), skill.getLevel(), targets), targets); + + // Launch the magic skill and calculate its effects. + skill.activateSkill(this, targets); + } + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "makeTriggerCast: ", e); + } + } + + public void makeTriggerCast(Skill skill, Creature target) + { + makeTriggerCast(skill, target, false); + } + + /** + * Dummy method overriden in {@link Player} + * @return {@code true} if current player can revive and shows 'To Village' button upon death, {@code false} otherwise. + */ + public boolean canRevive() + { + return true; + } + + /** + * Dummy method overriden in {@link Player} + * @param value + */ + public void setCanRevive(boolean value) + { + } + + /** + * Dummy method overriden in {@link Attackable} + * @return {@code true} if there is a loot to sweep, {@code false} otherwise. + */ + public boolean isSweepActive() + { + return false; + } + + /** + * Dummy method overriden in {@link Player} + * @return the clan id of current character. + */ + public int getClanId() + { + return 0; + } + + /** + * Dummy method overriden in {@link Player} + * @return the clan of current character. + */ + public Clan getClan() + { + return null; + } + + /** + * Dummy method overriden in {@link Player} + * @return {@code true} if player is in academy, {@code false} otherwise. + */ + public boolean isAcademyMember() + { + return false; + } + + /** + * Dummy method overriden in {@link Player} + * @return the pledge type of current character. + */ + public int getPledgeType() + { + return 0; + } + + /** + * Dummy method overriden in {@link Player} + * @return the alliance id of current character. + */ + public int getAllyId() + { + return 0; + } + + /** + * Notifies to listeners that current character received damage. + * @param damage + * @param attacker + * @param skill + * @param critical + * @param damageOverTime + */ + public void notifyDamageReceived(double damage, Creature attacker, Skill skill, boolean critical, boolean damageOverTime) + { + // Auto attacks make you stand up. + if (isPlayer() && asPlayer().isFakeDeath() && PlayerConfig.FAKE_DEATH_DAMAGE_STAND && (damage > 0)) + { + stopFakeDeath(true); + } + + if ((attacker != null) && EventDispatcher.getInstance().hasListener(EventType.ON_CREATURE_DAMAGE_DEALT, attacker)) + { + if (_onCreatureDamageDealt == null) + { + _onCreatureDamageDealt = new OnCreatureDamageDealt(); + } + + _onCreatureDamageDealt.setAttacker(attacker); + _onCreatureDamageDealt.setTarget(this); + _onCreatureDamageDealt.setDamage(damage); + _onCreatureDamageDealt.setSkill(skill); + _onCreatureDamageDealt.setCritical(critical); + _onCreatureDamageDealt.setDamageOverTime(damageOverTime); + EventDispatcher.getInstance().notifyEvent(_onCreatureDamageDealt, attacker); + } + + if (EventDispatcher.getInstance().hasListener(EventType.ON_CREATURE_DAMAGE_RECEIVED, this)) + { + if (_onCreatureDamageReceived == null) + { + _onCreatureDamageReceived = new OnCreatureDamageReceived(); + } + + _onCreatureDamageReceived.setAttacker(attacker); + _onCreatureDamageReceived.setTarget(this); + _onCreatureDamageReceived.setDamage(damage); + _onCreatureDamageReceived.setSkill(skill); + _onCreatureDamageReceived.setCritical(critical); + _onCreatureDamageReceived.setDamageOverTime(damageOverTime); + EventDispatcher.getInstance().notifyEventAsync(_onCreatureDamageReceived, this); + } + } + + /** + * Notifies to listeners that current character avoid attack. + * @param target + * @param isDot + */ + public void notifyAttackAvoid(Creature target, boolean isDot) + { + if (EventDispatcher.getInstance().hasListener(EventType.ON_CREATURE_ATTACK_AVOID, target)) + { + if (_onCreatureAttackAvoid == null) + { + _onCreatureAttackAvoid = new OnCreatureAttackAvoid(); + } + + _onCreatureAttackAvoid.setAttacker(this); + _onCreatureAttackAvoid.setTarget(target); + _onCreatureAttackAvoid.setDamageOverTime(isDot); + EventDispatcher.getInstance().notifyEvent(_onCreatureAttackAvoid, target); + } + } + + /** + * @return {@link WeaponType} of current character's weapon or basic weapon type. + */ + public WeaponType getAttackType() + { + final Weapon weapon = getActiveWeaponItem(); + if (weapon != null) + { + return weapon.getItemType(); + } + + return _template.getBaseAttackType(); + } + + public boolean isInCategory(CategoryType type) + { + return CategoryData.getInstance().isInCategory(type, getId()); + } + + /** + * @return the character that summoned this NPC. + */ + public Creature getSummoner() + { + return _summoner; + } + + /** + * @param summoner the summoner of this NPC. + */ + public void setSummoner(Creature summoner) + { + _summoner = summoner; + } + + @Override + public boolean isCreature() + { + return true; + } + + @Override + public Creature asCreature() + { + return this; + } + + /** + * @return {@code true} if current character is casting channeling skill, {@code false} otherwise. + */ + public boolean isChanneling() + { + return (_channelizer != null) && _channelizer.isChanneling(); + } + + public SkillChannelizer getSkillChannelizer() + { + if (_channelizer == null) + { + _channelizer = new SkillChannelizer(this); + } + + return _channelizer; + } + + /** + * @return {@code true} if current character is affected by channeling skill, {@code false} otherwise. + */ + public boolean isChannelized() + { + return (_channelized != null) && !_channelized.isChannelized(); + } + + public SkillChannelized getSkillChannelized() + { + if (_channelized == null) + { + _channelized = new SkillChannelized(); + } + + return _channelized; + } + + public void addInvulAgainst(SkillHolder holder) + { + final InvulSkillHolder invulHolder = getInvulAgainstSkills().get(holder.getSkillId()); + if (invulHolder != null) + { + invulHolder.increaseInstances(); + return; + } + + getInvulAgainstSkills().put(holder.getSkillId(), new InvulSkillHolder(holder)); + } + + public void removeInvulAgainst(SkillHolder holder) + { + final InvulSkillHolder invulHolder = getInvulAgainstSkills().get(holder.getSkillId()); + if ((invulHolder != null) && (invulHolder.decreaseInstances() < 1)) + { + getInvulAgainstSkills().remove(holder.getSkillId()); + } + } + + public boolean isInvulAgainst(int skillId, int skillLevel) + { + if (!_invulAgainst.isEmpty()) + { + final SkillHolder holder = getInvulAgainstSkills().get(skillId); + return ((holder != null) && ((holder.getSkillLevel() < 1) || (holder.getSkillLevel() == skillLevel))); + } + + return false; + } + + private Map getInvulAgainstSkills() + { + return _invulAgainst; + } + + @Override + public Collection getListeners(EventType type) + { + final Collection objectListeners = super.getListeners(type); + final Collection templateListeners = _template.getListeners(type); + final Collection globalListeners = isMonster() ? Containers.Monsters().getListeners(type) : isNpc() ? Containers.Npcs().getListeners(type) : isPlayer() ? Containers.Players().getListeners(type) : Collections.emptyList(); + + // Avoid creating a new object. + if (objectListeners.isEmpty() && templateListeners.isEmpty() && globalListeners.isEmpty()) + { + return Collections.emptyList(); + } + else if (!objectListeners.isEmpty() && templateListeners.isEmpty() && globalListeners.isEmpty()) + { + return objectListeners; + } + else if (!templateListeners.isEmpty() && objectListeners.isEmpty() && globalListeners.isEmpty()) + { + return templateListeners; + } + else if (!globalListeners.isEmpty() && objectListeners.isEmpty() && templateListeners.isEmpty()) + { + return globalListeners; + } + + final Collection allListeners = new ArrayList<>(objectListeners.size() + templateListeners.size() + globalListeners.size()); + allListeners.addAll(objectListeners); + allListeners.addAll(templateListeners); + allListeners.addAll(globalListeners); + return allListeners; + } + + public Race getRace() + { + return _template.getRace(); + } + + @Override + public void setXYZ(int newX, int newY, int newZ) + { + // 0, 0 is not a valid location. + if ((newX == 0) && (newY == 0)) + { + return; + } + + final ZoneRegion newZoneRegion = ZoneManager.getInstance().getRegion(newX, newY); + // Mobius: Prevent moving to nonexistent regions. + if (newZoneRegion == null) + { + return; + } + + final ZoneRegion oldZoneRegion = ZoneManager.getInstance().getRegion(this); + if (oldZoneRegion != newZoneRegion) + { + oldZoneRegion.removeFromZones(this); + newZoneRegion.revalidateZones(this); + } + + super.setXYZ(newX, newY, newZ); + } + + public boolean isInDuel() + { + return false; + } + + public int getDuelId() + { + return 0; + } + + public byte getSiegeState() + { + return 0; + } + + public int getSiegeSide() + { + return 0; + } + + public Map getKnownRelations() + { + return _knownRelations; + } + + protected void initSeenCreatures() + { + if (_seenCreatures == null) + { + synchronized (this) + { + if (_seenCreatures == null) + { + if (isNpc()) + { + final NpcTemplate template = asNpc().getTemplate(); + if ((template != null) && (template.getAggroRange() > 0)) + { + _seenCreatureRange = template.getAggroRange(); + } + } + + _seenCreatures = ConcurrentHashMap.newKeySet(1); + } + } + } + + CreatureSeeTaskManager.getInstance().add(this); + } + + public void updateSeenCreatures() + { + if ((_seenCreatures == null) || _isDead || !isSpawned()) + { + return; + } + + // Check if region and its neighbors are active. + final WorldRegion region = getWorldRegion(); + if ((region == null) || !region.areNeighborsActive()) + { + return; + } + + World.forEachVisibleObjectInRange(this, Creature.class, _seenCreatureRange, creature -> + { + if (!creature.isInvisible() && _seenCreatures.add(creature) && EventDispatcher.getInstance().hasListener(EventType.ON_CREATURE_SEE, this)) + { + EventDispatcher.getInstance().notifyEventAsync(new OnCreatureSee(this, creature), this); + } + }); + } + + public void removeSeenCreature(WorldObject worldObject) + { + if (_seenCreatures == null) + { + return; + } + + _seenCreatures.remove(worldObject); + } + + public int getKarma() + { + return _karma; + } + + public void setKarma(int karma) + { + _karma = karma; + } + + public int getMinShopDistance() + { + return 0; + } + + public void setCursorKeyMovement(boolean value) + { + _cursorKeyMovement = value; + } + + public boolean isCursorKeyMovement() + { + return _cursorKeyMovement; + } + + /** + * Adopt a client-reported position while a movement is in progress.
    + * Updates both the world location AND the active MoveData's accurate-coord state, so the next MovementTaskManager tick advances from the synced position instead of snapping back to the stale internal xAccurate / yAccurate.
    + * Used by ValidatePosition for cursor-key movement where server-side moveSpeed under-runs the client and lets the server position lag behind. + * @param x the new world X + * @param y the new world Y + * @param z the new world Z + */ + public void setSyncedXYZ(int x, int y, int z) + { + setXYZ(x, y, z); + + final MoveData move = _move; + if (move != null) + { + move.xAccurate = x; + move.yAccurate = y; + } + } + + public List getFakePlayerDrops() + { + return _fakePlayerDrops; + } + + public void addBuffInfoTime(BuffInfo info) + { + _buffFinishTask.addBuffInfo(info); + } + + public void removeBuffInfoTime(BuffInfo info) + { + _buffFinishTask.removeBuffInfo(info); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/Npc.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/Npc.java new file mode 100644 index 0000000..6dd6cb9 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/Npc.java @@ -0,0 +1,2114 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; + +import org.l2jmobius.commons.threads.ThreadPool; +import org.l2jmobius.commons.util.Rnd; +import org.l2jmobius.gameserver.cache.HtmCache; +import org.l2jmobius.gameserver.config.FeatureConfig; +import org.l2jmobius.gameserver.config.GeneralConfig; +import org.l2jmobius.gameserver.config.NpcConfig; +import org.l2jmobius.gameserver.config.PlayerConfig; +import org.l2jmobius.gameserver.config.RatesConfig; +import org.l2jmobius.gameserver.config.ServerConfig; +import org.l2jmobius.gameserver.config.custom.FakePlayersConfig; +import org.l2jmobius.gameserver.config.custom.PrivateStoreRangeConfig; +import org.l2jmobius.gameserver.config.custom.PvpAnnounceConfig; +import org.l2jmobius.gameserver.config.custom.PvpRewardItemConfig; +import org.l2jmobius.gameserver.data.xml.DynamicExpRateData; +import org.l2jmobius.gameserver.data.xml.ItemData; +import org.l2jmobius.gameserver.data.xml.NpcData; +import org.l2jmobius.gameserver.data.xml.NpcPersonalAIData; +import org.l2jmobius.gameserver.entity.Location; +import org.l2jmobius.gameserver.entity.World; +import org.l2jmobius.gameserver.entity.WorldObject; +import org.l2jmobius.gameserver.entity.actor.enums.creature.InstanceType; +import org.l2jmobius.gameserver.entity.actor.enums.creature.Race; +import org.l2jmobius.gameserver.entity.actor.enums.creature.Team; +import org.l2jmobius.gameserver.entity.actor.enums.npc.AISkillScope; +import org.l2jmobius.gameserver.entity.actor.enums.npc.AIType; +import org.l2jmobius.gameserver.entity.actor.holders.npc.FakePlayerHolder; +import org.l2jmobius.gameserver.entity.actor.instance.ClanHallManager; +import org.l2jmobius.gameserver.entity.actor.instance.Doorman; +import org.l2jmobius.gameserver.entity.actor.instance.Fisherman; +import org.l2jmobius.gameserver.entity.actor.instance.Merchant; +import org.l2jmobius.gameserver.entity.actor.instance.Teleporter; +import org.l2jmobius.gameserver.entity.actor.instance.Trainer; +import org.l2jmobius.gameserver.entity.actor.instance.Warehouse; +import org.l2jmobius.gameserver.entity.actor.stat.NpcStat; +import org.l2jmobius.gameserver.entity.actor.status.NpcStatus; +import org.l2jmobius.gameserver.entity.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.entity.item.ItemTemplate; +import org.l2jmobius.gameserver.entity.item.Weapon; +import org.l2jmobius.gameserver.entity.item.enums.ItemProcessType; +import org.l2jmobius.gameserver.entity.item.enums.ShotType; +import org.l2jmobius.gameserver.entity.item.holders.ItemHolder; +import org.l2jmobius.gameserver.entity.item.instance.Item; +import org.l2jmobius.gameserver.entity.spawns.Spawn; +import org.l2jmobius.gameserver.entity.zone.ZoneId; +import org.l2jmobius.gameserver.entity.zone.type.TownZone; +import org.l2jmobius.gameserver.handler.BypassHandler; +import org.l2jmobius.gameserver.handler.IBypassHandler; +import org.l2jmobius.gameserver.managers.CHSiegeManager; +import org.l2jmobius.gameserver.managers.CastleManager; +import org.l2jmobius.gameserver.managers.ItemManager; +import org.l2jmobius.gameserver.managers.RaidBossSpawnManager; +import org.l2jmobius.gameserver.managers.TownManager; +import org.l2jmobius.gameserver.managers.WalkingManager; +import org.l2jmobius.gameserver.managers.ZoneManager; +import org.l2jmobius.gameserver.mechanics.events.EventDispatcher; +import org.l2jmobius.gameserver.mechanics.events.EventType; +import org.l2jmobius.gameserver.mechanics.events.holders.actor.npc.OnNpcCanBeSeen; +import org.l2jmobius.gameserver.mechanics.events.holders.actor.npc.OnNpcEventReceived; +import org.l2jmobius.gameserver.mechanics.events.holders.actor.npc.OnNpcSkillFinished; +import org.l2jmobius.gameserver.mechanics.events.holders.actor.npc.OnNpcSpawn; +import org.l2jmobius.gameserver.mechanics.events.holders.actor.npc.OnNpcTeleport; +import org.l2jmobius.gameserver.mechanics.events.returns.TerminateReturn; +import org.l2jmobius.gameserver.mechanics.olympiad.Olympiad; +import org.l2jmobius.gameserver.mechanics.script.QuestTimer; +import org.l2jmobius.gameserver.mechanics.script.timers.TimerHolder; +import org.l2jmobius.gameserver.mechanics.sevensigns.SevenSigns; +import org.l2jmobius.gameserver.mechanics.sevensigns.SevenSignsFestival; +import org.l2jmobius.gameserver.mechanics.siege.Castle; +import org.l2jmobius.gameserver.mechanics.siege.clanhalls.SiegableHall; +import org.l2jmobius.gameserver.mechanics.skill.Skill; +import org.l2jmobius.gameserver.mechanics.stats.Formulas; +import org.l2jmobius.gameserver.mechanics.variables.NpcVariables; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.enums.ChatType; +import org.l2jmobius.gameserver.network.serverpackets.AbstractNpcInfo; +import org.l2jmobius.gameserver.network.serverpackets.ActionFailed; +import org.l2jmobius.gameserver.network.serverpackets.FakePlayerInfo; +import org.l2jmobius.gameserver.network.serverpackets.MagicSkillUse; +import org.l2jmobius.gameserver.network.serverpackets.NpcHtmlMessage; +import org.l2jmobius.gameserver.network.serverpackets.NpcSay; +import org.l2jmobius.gameserver.network.serverpackets.PrivateStoreMsgBuy; +import org.l2jmobius.gameserver.network.serverpackets.PrivateStoreMsgSell; +import org.l2jmobius.gameserver.network.serverpackets.RecipeShopMsg; +import org.l2jmobius.gameserver.network.serverpackets.ServerObjectInfo; +import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; +import org.l2jmobius.gameserver.taskmanagers.DecayTaskManager; +import org.l2jmobius.gameserver.taskmanagers.ItemsAutoDestroyTaskManager; + +/** + * This class represents a Non-Player-Creature in the world.
    + * It can be a monster or a friendly creature.
    + * It uses a template to fetch some static values. + */ +public class Npc extends Creature +{ + /** The interaction distance of the Npc(is used as offset in MovetoLocation method) */ + public static final int INTERACTION_DISTANCE = 250; + /** Maximum distance where the drop may appear given this NPC position. */ + public static final int RANDOM_ITEM_DROP_LIMIT = 70; + /** Ids of NPCs that see creatures through the OnCreatureSee event. */ + private static final Set CREATURE_SEE_IDS = ConcurrentHashMap.newKeySet(); + /** The Spawn object that manage this Npc */ + private Spawn _spawn; + /** The flag to specify if this Npc is busy */ + private boolean _isBusy = false; + /** The busy message for this Npc */ + private String _busyMessage = ""; + /** True if endDecayTask has already been called */ + private volatile boolean _isDecayed = false; + /** The castle index in the array of Castle this Npc belongs to */ + private int _castleIndex = -2; + private boolean _isInTown = false; + /** True if this Npc is autoattackable **/ + private boolean _isAutoAttackable = false; + /** Time of last social packet broadcast */ + private long _lastSocialBroadcast = 0; + /** Minimum interval between social packets */ + private static final int MINIMUM_SOCIAL_INTERVAL = 6000; + /** Support for random animation switching */ + private boolean _isRandomAnimationEnabled = true; + private boolean _isRandomWalkingEnabled = true; + private boolean _isWalker = false; + private boolean _isTalkable = getTemplate().isTalkable(); + private final boolean _isQuestMonster = getTemplate().isQuestMonster(); + private final boolean _isFakePlayer = getTemplate().isFakePlayer(); + + private int _currentLHandId; // Normally this shouldn't change from the template, but there exist exceptions. + private int _currentRHandId; // Normally this shouldn't change from the template, but there exist exceptions. + private int _currentEnchant; // Normally this shouldn't change from the template, but there exist exceptions. + private double _currentCollisionHeight; // Used for npc grow effect skills. + private double _currentCollisionRadius; // Used for npc grow effect skills. + + private int _soulshotamount = 0; + private int _spiritshotamount = 0; + + private int _shotsMask = 0; + private int _killingBlowWeaponId; + + private volatile int _scriptValue = 0; + + /** Map of summoned NPCs by this NPC. */ + private Map _summonedNpcs = null; + + private final List _questTimers = new ArrayList<>(); + private final List> _timerHolders = new ArrayList<>(); + + /** + * Creates a NPC. + * @param template the NPC template + */ + public Npc(NpcTemplate template) + { + // Call the Creature constructor to set the _template of the Creature, copy skills from template to object. + // and link _calculators to NPC_STD_CALCULATOR. + super(template); + setInstanceType(InstanceType.Npc); + initCharStatusUpdateValues(); + + // Initialize the "current" equipment. + _currentLHandId = getTemplate().getLHandId(); + _currentRHandId = getTemplate().getRHandId(); + _currentEnchant = NpcConfig.ENABLE_RANDOM_ENCHANT_EFFECT ? Rnd.get(4, 21) : getTemplate().getWeaponEnchant(); + + // Initialize the "current" collisions. + _currentCollisionHeight = getTemplate().getFCollisionHeight(); + _currentCollisionRadius = getTemplate().getFCollisionRadius(); + setFlying(template.isFlying()); + } + + /** + * Creates a NPC. + * @param npcId the NPC ID + */ + public Npc(int npcId) + { + this(NpcData.getInstance().getTemplate(npcId)); + } + + public int getSoulShotChance() + { + return getTemplate().getSoulShotChance(); + } + + public int getSpiritShotChance() + { + return getTemplate().getSpiritShotChance(); + } + + public int getMinSkillChance() + { + return getTemplate().getMinSkillChance(); + } + + public int getMaxSkillChance() + { + return getTemplate().getMaxSkillChance(); + } + + /** + * Verifies if the NPC can cast a skill given the minimum and maximum skill chances. + * @return {@code true} if the NPC has chances of casting a skill + */ + public boolean hasSkillChance() + { + return Rnd.get(100) < Rnd.get(getMinSkillChance(), getMaxSkillChance()); + } + + public boolean canMove() + { + return getTemplate().canMove(); + } + + public boolean isChaos() + { + return getTemplate().isChaos(); + } + + public List getLongRangeSkills() + { + return getTemplate().getAISkills(AISkillScope.LONG_RANGE); + } + + public List getShortRangeSkills() + { + return getTemplate().getAISkills(AISkillScope.SHORT_RANGE); + } + + /** + * Send a packet SocialAction to all Player in the _KnownPlayers of the Npc and create a new RandomAnimation Task. + * @param animationId + */ + public void onRandomAnimation(int animationId) + { + // Send a packet SocialAction to all Player in the _KnownPlayers of the Npc. + final long now = System.currentTimeMillis(); + if ((now - _lastSocialBroadcast) > MINIMUM_SOCIAL_INTERVAL) + { + _lastSocialBroadcast = now; + broadcastSocialAction(animationId); + } + } + + /** + * @return true if the server allows Random Animation. + */ + public boolean hasRandomAnimation() + { + return ((GeneralConfig.MAX_NPC_ANIMATION > 0) && isRandomAnimationEnabled() && (getAiType() != AIType.CORPSE)); + } + + /** + * Switches random Animation state into val. + * @param value needed state of random animation + */ + public void setRandomAnimationEnabled(boolean value) + { + _isRandomAnimationEnabled = value; + } + + /** + * @return {@code true}, if random animation is enabled, {@code false} otherwise. + */ + public boolean isRandomAnimationEnabled() + { + return !_isFakePlayer && _isRandomAnimationEnabled; + } + + public void setRandomWalking(boolean enabled) + { + _isRandomWalkingEnabled = enabled; + } + + public boolean isRandomWalkingEnabled() + { + return _isRandomWalkingEnabled; + } + + @Override + public NpcStat getStat() + { + return (NpcStat) super.getStat(); + } + + @Override + public void initCharStat() + { + setStat(new NpcStat(this)); + } + + @Override + public NpcStatus getStatus() + { + return (NpcStatus) super.getStatus(); + } + + @Override + public void initCharStatus() + { + setStatus(new NpcStatus(this)); + } + + /** Return the NpcTemplate of the Npc. */ + @Override + public NpcTemplate getTemplate() + { + return (NpcTemplate) super.getTemplate(); + } + + /** + * Gets the NPC ID. + * @return the NPC ID + */ + @Override + public int getId() + { + return getTemplate().getId(); + } + + @Override + public boolean canBeAttacked() + { + return NpcConfig.ALT_ATTACKABLE_NPCS; + } + + /** + * Return the Level of this Npc contained in the NpcTemplate. + */ + @Override + public int getLevel() + { + return getTemplate().getLevel(); + } + + /** + * @return True if the Npc is aggressive (ex : Monster in function of aggroRange). + */ + public boolean isAggressive() + { + return false; + } + + /** + * @return the Aggro Range of this Npc either contained in the NpcTemplate, or overriden by spawnlist AI value. + */ + public int getAggroRange() + { + return hasAIValue("aggroRange") ? getAIValue("aggroRange") : getTemplate().getAggroRange(); + } + + public boolean isInMyClan(Npc npc) + { + return getTemplate().isClan(npc.getTemplate().getClans()); + } + + /** + * Return True if this Npc is undead in function of the NpcTemplate. + */ + @Override + public boolean isUndead() + { + return getTemplate().getRace() == Race.UNDEAD; + } + + /** + * Send a packet NpcInfo with state of abnormal effect to all Player in the _KnownPlayers of the Npc. + */ + @Override + public void updateAbnormalEffect() + { + World.forEachVisibleObject(this, Player.class, player -> + { + if (!isVisibleFor(player)) + { + return; + } + + if (_isFakePlayer) + { + player.sendPacket(new FakePlayerInfo(this)); + } + else if (getRunSpeed() == 0) + { + player.sendPacket(new ServerObjectInfo(this, player)); + } + else if (isSpawned()) + { + player.sendPacket(new AbstractNpcInfo.NpcInfo(this, player)); + } + }); + } + + @Override + public boolean isAutoAttackable(Creature attacker) + { + return _isAutoAttackable; + } + + public void setAutoAttackable(boolean flag) + { + _isAutoAttackable = flag; + } + + /** + * @return the Identifier of the item in the left hand of this Npc contained in the NpcTemplate. + */ + public int getLeftHandItem() + { + return _currentLHandId; + } + + /** + * @return the Identifier of the item in the right hand of this Npc contained in the NpcTemplate. + */ + public int getRightHandItem() + { + return _currentRHandId; + } + + public int getEnchantEffect() + { + return _currentEnchant; + } + + /** + * @return the busy status of this Npc. + */ + public boolean isBusy() + { + return _isBusy; + } + + /** + * @param isBusy the busy status of this Npc + */ + public void setBusy(boolean isBusy) + { + _isBusy = isBusy; + } + + /** + * @return the busy message of this Npc. + */ + public String getBusyMessage() + { + return _busyMessage; + } + + /** + * @param message the busy message of this Npc. + */ + public void setBusyMessage(String message) + { + _busyMessage = message; + } + + /** + * @return true if this Npc instance can be warehouse manager. + */ + public boolean isWarehouse() + { + return false; + } + + public boolean canTarget(Player player) + { + if (player.isOutOfControl()) + { + player.sendPacket(ActionFailed.STATIC_PACKET); + return false; + } + + if (player.isLockedTarget() && (player.getLockedTarget() != this)) + { + player.sendPacket(SystemMessageId.FAILED_TO_CHANGE_ATTACK_TARGET); + player.sendPacket(ActionFailed.STATIC_PACKET); + return false; + } + + return true; + } + + public boolean canInteract(Player player) + { + if (player.isCastingNow() || player.isCastingSimultaneouslyNow()) + { + return false; + } + else if (player.isDead() || player.isFakeDeath()) + { + return false; + } + else if (player.isSitting()) + { + return false; + } + else if (player.isInStoreMode()) + { + return false; + } + else if (!isInsideRadius3D(player, INTERACTION_DISTANCE)) + { + return false; + } + else if (player.getInstanceId() != getInstanceId()) + { + return false; + } + + return true; + } + + /** + * @return the Castle this Npc belongs to. + */ + public Castle getCastle() + { + // Get castle this NPC belongs to (excluding Attackable). + if (_castleIndex < 0) + { + final TownZone town = TownManager.getTown(getX(), getY(), getZ()); + if (town != null) + { + _castleIndex = CastleManager.getInstance().getCastleIndex(town.getTaxById()); + } + + if (_castleIndex < 0) + { + _castleIndex = CastleManager.getInstance().findNearestCastleIndex(this); + } + else + { + _isInTown = true; // Npc was spawned in town. + } + } + + if (_castleIndex < 0) + { + return null; + } + + return CastleManager.getInstance().getCastles().get(_castleIndex); + } + + /** + * Verify if the given player is this NPC's lord. + * @param player the player to check + * @return {@code true} if the player is clan leader and owner of a castle of fort that this NPC belongs to, {@code false} otherwise + */ + public boolean isMyLord(Player player) + { + if (player.isClanLeader()) + { + final int castleId = getCastle() != null ? getCastle().getResidenceId() : -1; + return player.getClan().getCastleId() == castleId; + } + + return false; + } + + public SiegableHall getConquerableHall() + { + return CHSiegeManager.getInstance().getNearbyClanHall(getX(), getY(), 10000); + } + + /** + * Return closest castle in defined distance + * @param maxDistance long + * @return Castle + */ + public Castle getCastle(long maxDistance) + { + final int index = CastleManager.getInstance().findNearestCastleIndex(this, maxDistance); + if (index < 0) + { + return null; + } + + return CastleManager.getInstance().getCastles().get(index); + } + + public boolean isInTown() + { + if (_castleIndex < 0) + { + getCastle(); + } + + return _isInTown; + } + + /** + * Open a quest or chat window on client with the text of the Npc in function of the command.
    + *
    + * Example of use: + *
      + *
    • Client packet : RequestBypassToServer
    • + *
    + * @param player + * @param command The command string received from client + */ + public void onBypassFeedback(Player player, String command) + { + if (canInteract(player)) + { + if (_isBusy && (!_busyMessage.isEmpty())) + { + player.sendPacket(ActionFailed.STATIC_PACKET); + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, "data/html/npcbusy.htm"); + html.replace("%busymessage%", _busyMessage); + html.replace("%npcname%", getName()); + html.replace("%playername%", player.getName()); + player.sendPacket(html); + } + else + { + final IBypassHandler handler = BypassHandler.getInstance().getHandler(command); + if (handler != null) + { + handler.onCommand(command, player, this); + } + else + { + LOGGER.info(getClass().getSimpleName() + ": Unknown NPC bypass: \"" + command + "\" NpcId: " + getId()); + } + } + } + } + + /** + * Return null (regular NPCs don't have weapons instances). + */ + @Override + public Item getActiveWeaponInstance() + { + return null; + } + + /** + * Return the weapon item equipped in the right hand of the Npc or null. + */ + @Override + public Weapon getActiveWeaponItem() + { + // Get the weapon identifier equipped in the right hand of the Npc. + final int weaponId = getTemplate().getRHandId(); + if (weaponId < 1) + { + return null; + } + + // Get the weapon item equipped in the right hand of the Npc. + final ItemTemplate item = ItemData.getInstance().getTemplate(getTemplate().getRHandId()); + if (!(item instanceof Weapon)) + { + return null; + } + + return (Weapon) item; + } + + /** + * Return null (regular NPCs don't have weapons instances). + */ + @Override + public Item getSecondaryWeaponInstance() + { + return null; + } + + /** + * Return the weapon item equipped in the left hand of the Npc or null. + */ + @Override + public Weapon getSecondaryWeaponItem() + { + // Get the weapon identifier equipped in the right hand of the Npc. + final int weaponId = getTemplate().getLHandId(); + if (weaponId < 1) + { + return null; + } + + // Get the weapon item equipped in the right hand of the Npc. + final ItemTemplate item = ItemData.getInstance().getTemplate(getTemplate().getLHandId()); + if (!(item instanceof Weapon)) + { + return null; + } + + return (Weapon) item; + } + + /** + * Send a Server->Client packet NpcHtmlMessage to the Player in order to display the message of the Npc. + * @param player The Player who talks with the Npc + * @param content The text of the NpcMessage + */ + public void insertObjectIdAndShowChatWindow(Player player, String content) + { + // Send a Server->Client packet NpcHtmlMessage to the Player in order to display the message of the Npc. + player.sendPacket(new NpcHtmlMessage(getObjectId(), content.replaceAll("%objectId%", String.valueOf(getObjectId())))); + } + + /** + * : + *
      + *
    • if the file exists on the server (page number = 0) : data/html/default/12006.htm (npcId-page number)
    • + *
    • if the file exists on the server (page number > 0) : data/html/default/12006-1.htm (npcId-page number)
    • + *
    • if the file doesn't exist on the server : data/html/npcdefault.htm (message : "I have nothing to say to you")
    • + *
    + * @param npcId The Identifier of the Npc whose text must be display + * @param value The number of the page to display + * @return the pathfile of the selected HTML file in function of the npcId and of the page number. + */ + public String getHtmlPath(int npcId, int value) + { + String pom = ""; + if (value == 0) + { + pom = Integer.toString(npcId); + } + else + { + pom = npcId + "-" + value; + } + + final String temp = "data/html/default/" + pom + ".htm"; + if (GeneralConfig.HTM_CACHE) + { + // If not running lazy cache the file must be in the cache or it does not exist. + if (HtmCache.getInstance().contains(temp)) + { + return temp; + } + } + else + { + final File file = new File(ServerConfig.DATAPACK_ROOT, temp); + if (file.isFile()) + { + final String lowerCaseName = file.getName().toLowerCase(); + if (lowerCaseName.endsWith(".htm") || lowerCaseName.endsWith(".html")) + { + return temp; + } + } + } + + // If the file is not found, the standard message "I have nothing to say to you" is returned. + return "data/html/npcdefault.htm"; + } + + public void showChatWindow(Player player) + { + showChatWindow(player, 0); + } + + /** + * Returns true if html exists + * @param player + * @param type + * @return boolean + */ + private boolean showPkDenyChatWindow(Player player, String type) + { + final String html = HtmCache.getInstance().getHtm(player, "data/html/" + type + "/" + getId() + "-pk.htm"); + if (html != null) + { + insertObjectIdAndShowChatWindow(player, html); + player.sendPacket(ActionFailed.STATIC_PACKET); + return true; + } + + return false; + } + + /** + * Open a chat window on client with the text of the Npc.
    + *
    + * Actions: + *
      + *
    • Get the text of the selected HTML file in function of the npcId and of the page number
    • + *
    • Send a Server->Client NpcHtmlMessage containing the text of the Npc to the Player
    • + *
    • Send a Server->Client ActionFailed to the Player in order to avoid that the client wait another packet
    • + *
    + * @param player The Player that talk with the Npc + * @param value The number of the page of the Npc to display + */ + public void showChatWindow(Player player, int value) + { + if (!_isTalkable) + { + player.sendPacket(ActionFailed.STATIC_PACKET); + return; + } + + if (player.isCursedWeaponEquipped() && (!(player.getTarget() instanceof ClanHallManager) || !(player.getTarget() instanceof Doorman))) + { + player.setTarget(player); + return; + } + + if (player.getKarma() > 0) + { + if (!PlayerConfig.ALT_GAME_KARMA_PLAYER_CAN_SHOP && (this instanceof Merchant)) + { + if (showPkDenyChatWindow(player, "merchant")) + { + return; + } + } + else if (!PlayerConfig.ALT_GAME_KARMA_PLAYER_CAN_USE_GK && (this instanceof Teleporter)) + { + if (showPkDenyChatWindow(player, "teleporter")) + { + return; + } + } + else if (!PlayerConfig.ALT_GAME_KARMA_PLAYER_CAN_USE_WAREHOUSE && (this instanceof Warehouse)) + { + if (showPkDenyChatWindow(player, "warehouse")) + { + return; + } + } + else if (!PlayerConfig.ALT_GAME_KARMA_PLAYER_CAN_SHOP && (this instanceof Fisherman)) + { + if (showPkDenyChatWindow(player, "fisherman")) + { + return; + } + } + } + + if (getTemplate().isType("Auctioneer") && (value == 0)) + { + return; + } + + final int npcId = getTemplate().getId(); + + /* For use with Seven Signs implementation */ + String filename = SevenSigns.SEVEN_SIGNS_HTML_PATH; + final int sealAvariceOwner = SevenSigns.getInstance().getSealOwner(SevenSigns.SEAL_AVARICE); + final int sealGnosisOwner = SevenSigns.getInstance().getSealOwner(SevenSigns.SEAL_GNOSIS); + final int playerCabal = SevenSigns.getInstance().getPlayerCabal(player.getObjectId()); + final int compWinner = SevenSigns.getInstance().getCabalHighestScore(); + + switch (npcId) + { + case 31127: // + case 31128: // + case 31129: // Dawn Festival Guides + case 31130: // + case 31131: // + { + filename += "festival/dawn_guide.htm"; + break; + } + case 31137: // + case 31138: // + case 31139: // Dusk Festival Guides + case 31140: // + case 31141: // + { + filename += "festival/dusk_guide.htm"; + break; + } + case 31092: // Black Marketeer of Mammon + { + filename += "blkmrkt_1.htm"; + break; + } + case 31113: // Merchant of Mammon + { + if (FeatureConfig.ALT_STRICT_SEVENSIGNS) + { + switch (compWinner) + { + case SevenSigns.CABAL_DAWN: + { + if ((playerCabal != compWinner) || (playerCabal != sealAvariceOwner)) + { + player.sendPacket(SystemMessageId.ONLY_A_LORD_OF_DAWN_MAY_USE_THIS); + player.sendPacket(ActionFailed.STATIC_PACKET); + return; + } + break; + } + case SevenSigns.CABAL_DUSK: + { + if ((playerCabal != compWinner) || (playerCabal != sealAvariceOwner)) + { + player.sendPacket(SystemMessageId.ONLY_A_REVOLUTIONARY_OF_DUSK_MAY_USE_THIS); + player.sendPacket(ActionFailed.STATIC_PACKET); + return; + } + break; + } + default: + { + player.sendPacket(SystemMessageId.THIS_IS_A_QUEST_EVENT_PERIOD); + return; + } + } + } + + filename += "mammmerch_1.htm"; + break; + } + case 31126: // Blacksmith of Mammon + { + if (FeatureConfig.ALT_STRICT_SEVENSIGNS) + { + switch (compWinner) + { + case SevenSigns.CABAL_DAWN: + { + if ((playerCabal != compWinner) || (playerCabal != sealGnosisOwner)) + { + player.sendPacket(SystemMessageId.ONLY_A_LORD_OF_DAWN_MAY_USE_THIS); + player.sendPacket(ActionFailed.STATIC_PACKET); + return; + } + break; + } + case SevenSigns.CABAL_DUSK: + { + if ((playerCabal != compWinner) || (playerCabal != sealGnosisOwner)) + { + player.sendPacket(SystemMessageId.ONLY_A_REVOLUTIONARY_OF_DUSK_MAY_USE_THIS); + player.sendPacket(ActionFailed.STATIC_PACKET); + return; + } + break; + } + default: + { + player.sendPacket(SystemMessageId.THIS_IS_A_QUEST_EVENT_PERIOD); + return; + } + } + } + + filename += "mammblack_1.htm"; + break; + } + case 31132: + case 31133: + case 31134: + case 31135: + case 31136: // Festival Witches + case 31142: + case 31143: + case 31144: + case 31145: + case 31146: + { + filename += "festival/festival_witch.htm"; + break; + } + case 31688: + { + if (player.isNoble()) + { + filename = Olympiad.OLYMPIAD_HTML_PATH + "noble_main.htm"; + } + else + { + filename = (getHtmlPath(npcId, value)); + } + break; + } + case 31690: + case 31769: + case 31770: + case 31771: + case 31772: + { + if (player.isHero() || player.isNoble()) + { + filename = Olympiad.OLYMPIAD_HTML_PATH + "hero_main.htm"; + } + else + { + filename = (getHtmlPath(npcId, value)); + } + break; + } + case 30298: // Blacksmith Pinter + { + if (player.isAcademyMember()) + { + filename = (getHtmlPath(npcId, 1)); + } + else + { + filename = (getHtmlPath(npcId, value)); + } + break; + } + default: + { + if ((npcId >= 31865) && (npcId <= 31918)) + { + if (value == 0) + { + filename += "rift/GuardianOfBorder.htm"; + } + else + { + filename += "rift/GuardianOfBorder-" + value + ".htm"; + } + break; + } + + if (((npcId >= 31093) && (npcId <= 31094)) || ((npcId >= 31172) && (npcId <= 31201)) || ((npcId >= 31239) && (npcId <= 31254))) + { + return; + } + + // Get the text of the selected HTML file in function of the npcId and of the page number. + filename = (getHtmlPath(npcId, value)); + break; + } + } + + // Send a Server->Client NpcHtmlMessage containing the text of the Npc to the Player. + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, filename); + html.replace("%npcname%", String.valueOf(getName())); + html.replace("%objectId%", String.valueOf(getObjectId())); + html.replace("%festivalMins%", SevenSignsFestival.getInstance().getTimeToNextFestivalStr()); + player.sendPacket(html); + + // Send a Server->Client ActionFailed to the Player in order to avoid that the client wait another packet. + player.sendPacket(ActionFailed.STATIC_PACKET); + } + + /** + * Open a chat window on client with the text specified by the given file name and path, relative to the datapack root. + * @param player The Player that talk with the Npc + * @param filename The filename that contains the text to send + */ + public void showChatWindow(Player player, String filename) + { + // Send a Server->Client NpcHtmlMessage containing the text of the Npc to the Player. + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, filename); + html.replace("%objectId%", String.valueOf(getObjectId())); + player.sendPacket(html); + + // Send a Server->Client ActionFailed to the Player in order to avoid that the client wait another packet. + player.sendPacket(ActionFailed.STATIC_PACKET); + } + + /** + * @param level + * @return the Exp Reward of this Npc (modified by RATE_XP, or from DynamicExpRates.xml if enabled). + */ + public long getExpReward(int level) + { + if (DynamicExpRateData.getInstance().isEnabled()) + { + return (long) (getTemplate().getExp() * DynamicExpRateData.getInstance().getDynamicExpRate(level)); + } + + return (long) (getTemplate().getExp() * RatesConfig.RATE_XP); + } + + /** + * @param level + * @return the SP Reward of this Npc (modified by RATE_SP, or from DynamicExpRates.xml if enabled). + */ + public int getSpReward(int level) + { + if (DynamicExpRateData.getInstance().isEnabled()) + { + return (int) (getTemplate().getSP() * DynamicExpRateData.getInstance().getDynamicSpRate(level)); + } + + return (int) (getTemplate().getSP() * RatesConfig.RATE_SP); + } + + /** + * Kill the Npc (the corpse disappeared after 7 seconds).
    + *
    + * Actions: + *
      + *
    • Create a DecayTask to remove the corpse of the Npc after 7 seconds
    • + *
    • Set target to null and cancel Attack or Cast
    • + *
    • Stop movement
    • + *
    • Stop HP/MP/CP Regeneration task
    • + *
    • Stop all active skills effects in progress on the Creature
    • + *
    • Send the Server->Client packet StatusUpdate with current HP and MP to all other Player to inform
    • + *
    • Notify Creature AI
    • + *
    + * @param killer The Creature who killed it + */ + @Override + public boolean doDie(Creature killer) + { + if (!super.doDie(killer)) + { + return false; + } + + // Normally this wouldn't really be needed, but for those few exceptions, + // we do need to reset the weapons back to the initial template weapon. + _currentLHandId = getTemplate().getLHandId(); + _currentRHandId = getTemplate().getRHandId(); + _currentCollisionHeight = getTemplate().getFCollisionHeight(); + _currentCollisionRadius = getTemplate().getFCollisionRadius(); + + final Weapon weapon = (killer != null) ? killer.getActiveWeaponItem() : null; + _killingBlowWeaponId = (weapon != null) ? weapon.getId() : 0; + if (_isFakePlayer && (killer != null) && killer.isPlayable()) + { + final Player player = killer.asPlayer(); + if (isScriptValue(0) && (getKarma() < 0)) + { + if (FakePlayersConfig.FAKE_PLAYER_KILL_KARMA) + { + player.setKarma(player.getKarma() + Formulas.calculateKarmaGain(player.getPkKills(), killer.isSummon())); + player.setPkKills(player.getPkKills() + 1); + player.broadcastUserInfo(); + player.checkItemRestriction(); + + // pk item rewards + if (PvpRewardItemConfig.REWARD_PK_ITEM) + { + if (!(PvpRewardItemConfig.DISABLE_REWARDS_IN_INSTANCES && (getInstanceId() != 0)) && // + !(PvpRewardItemConfig.DISABLE_REWARDS_IN_PVP_ZONES && isInsideZone(ZoneId.PVP))) + { + player.addItem(ItemProcessType.REWARD, PvpRewardItemConfig.REWARD_PK_ITEM_ID, PvpRewardItemConfig.REWARD_PK_ITEM_AMOUNT, this, PvpRewardItemConfig.REWARD_PK_ITEM_MESSAGE); + } + } + + // announce pk + if (PvpAnnounceConfig.ANNOUNCE_PK_PVP && !player.isGM()) + { + final String msg = PvpAnnounceConfig.ANNOUNCE_PK_MSG.replace("$killer", player.getName()).replace("$target", getName()); + if (PvpAnnounceConfig.ANNOUNCE_PK_PVP_NORMAL_MESSAGE) + { + final SystemMessage sm = new SystemMessage(SystemMessageId.S1_3); + sm.addString(msg); + World.broadcastToAllOnlinePlayers(sm); + } + else + { + World.broadcastToAllOnlinePlayers(msg, false); + } + } + } + } + else if (FakePlayersConfig.FAKE_PLAYER_KILL_PVP) + { + player.setPvpKills(player.getPvpKills() + 1); + player.broadcastUserInfo(); + + // pvp item rewards + if (PvpRewardItemConfig.REWARD_PVP_ITEM) + { + if (!(PvpRewardItemConfig.DISABLE_REWARDS_IN_INSTANCES && (getInstanceId() != 0)) && // + !(PvpRewardItemConfig.DISABLE_REWARDS_IN_PVP_ZONES && isInsideZone(ZoneId.PVP))) + { + player.addItem(ItemProcessType.REWARD, PvpRewardItemConfig.REWARD_PVP_ITEM_ID, PvpRewardItemConfig.REWARD_PVP_ITEM_AMOUNT, this, PvpRewardItemConfig.REWARD_PVP_ITEM_MESSAGE); + } + } + + // announce pvp + if (PvpAnnounceConfig.ANNOUNCE_PK_PVP && !player.isGM()) + { + final String msg = PvpAnnounceConfig.ANNOUNCE_PVP_MSG.replace("$killer", player.getName()).replace("$target", getName()); + if (PvpAnnounceConfig.ANNOUNCE_PK_PVP_NORMAL_MESSAGE) + { + final SystemMessage sm = new SystemMessage(SystemMessageId.S1_3); + sm.addString(msg); + World.broadcastToAllOnlinePlayers(sm); + } + else + { + World.broadcastToAllOnlinePlayers(msg, false); + } + } + } + } + + DecayTaskManager.getInstance().add(this); + return true; + } + + /** + * Set the spawn of the Npc. + * @param spawn The Spawn that manage the Npc + */ + public void setSpawn(Spawn spawn) + { + _spawn = spawn; + } + + @Override + public void onSpawn() + { + super.onSpawn(); + + // Recharge shots + _soulshotamount = getTemplate().getSoulShot(); + _spiritshotamount = getTemplate().getSpiritShot(); + _killingBlowWeaponId = 0; + _isRandomAnimationEnabled = getTemplate().isRandomAnimationEnabled(); + _isRandomWalkingEnabled = !WalkingManager.getInstance().isTargeted(this) && getTemplate().isRandomWalkEnabled(); + + if (isTeleporting()) + { + if (EventDispatcher.getInstance().hasListener(EventType.ON_NPC_TELEPORT, this)) + { + EventDispatcher.getInstance().notifyEventAsync(new OnNpcTeleport(this), this); + } + } + else if (EventDispatcher.getInstance().hasListener(EventType.ON_NPC_SPAWN, this)) + { + EventDispatcher.getInstance().notifyEventAsync(new OnNpcSpawn(this), this); + } + + if (!isTeleporting()) + { + WalkingManager.getInstance().onSpawn(this); + } + + if (CREATURE_SEE_IDS.contains(getId())) + { + initSeenCreatures(); + } + } + + public static void addCreatureSeeId(int id) + { + CREATURE_SEE_IDS.add(id); + } + + /** + * Remove the Npc from the world and update its spawn object (for a complete removal use the deleteMe method).
    + *
    + * Actions: + *
      + *
    • Remove the Npc from the world when the decay task is launched
    • + *
    • Decrease its spawn counter
    • + *
    • Manage Siege task (killFlag, killCT)
    • + *
    + * Caution: This method DOESN'T REMOVE the object from _allObjects of World
    + * Caution: This method DOESN'T SEND Server->Client packets to players + */ + @Override + public void onDecay() + { + if (_isDecayed) + { + return; + } + + setDecayed(true); + + // Remove the Npc from the world when the decay task is launched. + super.onDecay(); + + // Decrease its spawn counter. + if ((_spawn != null) && !RaidBossSpawnManager.getInstance().isDefined(getId())) + { + _spawn.decreaseCount(this); + } + + // Notify Walking Manager. + WalkingManager.getInstance().onDeath(this); + + // Removes itself from the summoned list. + final Creature summoner = getSummoner(); + if ((summoner != null) && summoner.isNpc()) + { + summoner.asNpc().removeSummonedNpc(getObjectId()); + } + + // Stop all timers. + stopQuestTimers(); + stopTimerHolders(); + + // Clear script value. + _scriptValue = 0; + } + + /** + * Remove PROPERLY the Npc from the world.
    + *
    + * Actions: + *
      + *
    • Remove the Npc from the world and update its spawn object
    • + *
    • Remove all WorldObject from _knownObjects and _knownPlayer of the Npc then cancel Attack or Cast and notify AI
    • + *
    • Remove WorldObject object from _allObjects of World
    • + *
    + * Caution: This method DOESN'T SEND Server->Client packets to players
    + * UnAfraid: TODO: Add Listener here + */ + @Override + public boolean deleteMe() + { + try + { + onDecay(); + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "Failed decayMe().", e); + } + + if (isChannelized()) + { + getSkillChannelized().abortChannelization(); + } + + ZoneManager.getInstance().getRegion(this).removeFromZones(this); + + return super.deleteMe(); + } + + /** + * @return the Spawn object that manage this Npc. + */ + public Spawn getSpawn() + { + return _spawn; + } + + public boolean isDecayed() + { + return _isDecayed; + } + + public void setDecayed(boolean decayed) + { + _isDecayed = decayed; + } + + public void endDecayTask() + { + if (!_isDecayed) + { + DecayTaskManager.getInstance().cancel(this); + onDecay(); + } + } + + // Two functions to change the appearance of the equipped weapons on the NPC. + // This is only useful for a few NPCs and is most likely going to be called from AI. + public void setLHandId(int newWeaponId) + { + _currentLHandId = newWeaponId; + updateAbnormalEffect(); + } + + public void setRHandId(int newWeaponId) + { + _currentRHandId = newWeaponId; + updateAbnormalEffect(); + } + + public void setLRHandId(int newLWeaponId, int newRWeaponId) + { + _currentRHandId = newRWeaponId; + _currentLHandId = newLWeaponId; + updateAbnormalEffect(); + } + + public void setEnchant(int newEnchantValue) + { + _currentEnchant = newEnchantValue; + updateAbnormalEffect(); + } + + public boolean isShowName() + { + return getTemplate().isShowName(); + } + + @Override + public boolean isTargetable() + { + return getTemplate().isTargetable(); + } + + public void setCollisionHeight(double height) + { + _currentCollisionHeight = height; + } + + public void setCollisionRadius(double radius) + { + _currentCollisionRadius = radius; + } + + public double getCollisionHeight() + { + return _currentCollisionHeight; + } + + public double getCollisionRadius() + { + return _currentCollisionRadius; + } + + @Override + public void sendInfo(Player player) + { + if (isVisibleFor(player)) + { + if (_isFakePlayer) + { + player.sendPacket(new FakePlayerInfo(this)); + + // Private store message support. + final FakePlayerHolder fakePlayerInfo = getTemplate().getFakePlayerInfo(); + final int storeType = fakePlayerInfo.getPrivateStoreType(); + if (storeType > 0) + { + final String message = fakePlayerInfo.getPrivateStoreMessage(); + if (!message.isEmpty()) + { + switch (storeType) + { + case 1: // Sell + { + player.sendPacket(new PrivateStoreMsgSell(getObjectId(), message)); + break; + } + case 3: // Buy + { + player.sendPacket(new PrivateStoreMsgBuy(getObjectId(), message)); + break; + } + case 5: // Manufacture + { + player.sendPacket(new RecipeShopMsg(getObjectId(), message)); + break; + } + case 8: // Package Sell + { + // player.sendPacket(new ExPrivateStoreSetWholeMsg(getObjectId(), message)); + player.sendPacket(new PrivateStoreMsgSell(getObjectId(), message)); + break; + } + } + } + } + } + else if (getRunSpeed() == 0) + { + player.sendPacket(new ServerObjectInfo(this, player)); + } + else + { + player.sendPacket(new AbstractNpcInfo.NpcInfo(this, player)); + } + } + } + + public void showNoTeachHtml(Player player) + { + final int npcId = getId(); + String html = ""; + if (this instanceof Warehouse) + { + html = HtmCache.getInstance().getHtm(player, "data/html/warehouse/" + npcId + "-noteach.htm"); + } + else if (this instanceof Trainer) + { + html = HtmCache.getInstance().getHtm(player, "data/html/trainer/" + npcId + "-noteach.htm"); + + // Trainer Healer? + if (html == null) + { + html = HtmCache.getInstance().getHtm(player, "data/scripts/ai/others/HealerTrainer/" + npcId + "-noteach.html"); + } + } + + final NpcHtmlMessage noTeachMsg = new NpcHtmlMessage(getObjectId()); + if (html == null) + { + LOGGER.warning("Npc " + npcId + " missing noTeach html!"); + noTeachMsg.setHtml("I cannot teach you any skills.
    You must find your current class teachers."); + } + else + { + noTeachMsg.setHtml(html); + noTeachMsg.replace("%objectId%", String.valueOf(getObjectId())); + } + + player.sendPacket(noTeachMsg); + } + + public Npc scheduleDespawn(long delay) + { + ThreadPool.schedule(() -> + { + if (!_isDecayed) + { + deleteMe(); + } + }, delay); + return this; + } + + @Override + protected void notifyQuestEventSkillFinished(Skill skill, WorldObject target) + { + if ((target != null) && target.isPlayable() && EventDispatcher.getInstance().hasListener(EventType.ON_NPC_SKILL_FINISHED, this)) + { + EventDispatcher.getInstance().notifyEventAsync(new OnNpcSkillFinished(this, target.asPlayer(), skill), this); + } + } + + @Override + public boolean isMovementDisabled() + { + return super.isMovementDisabled() || !canMove() || (getAiType() == AIType.CORPSE); + } + + public AIType getAiType() + { + return getTemplate().getAIType(); + } + + public int getColorEffect() + { + return 0; + } + + @Override + public boolean isNpc() + { + return true; + } + + @Override + public Npc asNpc() + { + return this; + } + + public void setTeam(Team team, boolean broadcast) + { + super.setTeam(team); + if (broadcast) + { + broadcastInfo(); + } + } + + @Override + public void setTeam(Team team) + { + super.setTeam(team); + broadcastInfo(); + } + + @Override + public boolean isWalker() + { + return _isWalker; + } + + public void setWalker() + { + _isWalker = true; + } + + @Override + public boolean isChargedShot(ShotType type) + { + return (_shotsMask & type.getMask()) == type.getMask(); + } + + @Override + public void setChargedShot(ShotType type, boolean charged) + { + if (charged) + { + _shotsMask |= type.getMask(); + } + else + { + _shotsMask &= ~type.getMask(); + } + } + + @Override + public void rechargeShots(boolean physical, boolean magic) + { + if (_isFakePlayer && FakePlayersConfig.FAKE_PLAYER_USE_SHOTS) + { + if (physical) + { + broadcastPacket(new MagicSkillUse(this, this, 2154, 1, 0, 0)); + setChargedShot(ShotType.SOULSHOTS, true); + } + + if (magic) + { + broadcastPacket(new MagicSkillUse(this, this, 2159, 1, 0, 0)); + setChargedShot(ShotType.SPIRITSHOTS, true); + } + } + else if ((_soulshotamount > 0) || (_spiritshotamount > 0)) + { + if (physical) + { + if ((_soulshotamount == 0) || (Rnd.get(100) > getSoulShotChance())) + { + return; + } + + _soulshotamount--; + broadcastPacket(new MagicSkillUse(this, this, 2154, 1, 0, 0)); + setChargedShot(ShotType.SOULSHOTS, true); + } + + if (magic) + { + if ((_spiritshotamount == 0) || (Rnd.get(100) > getSpiritShotChance())) + { + return; + } + + _spiritshotamount--; + broadcastPacket(new MagicSkillUse(this, this, 2159, 1, 0, 0)); + setChargedShot(ShotType.SPIRITSHOTS, true); + } + } + } + + /** + * Receive the stored int value for this {@link Npc} instance. + * @return stored script value + */ + public int getScriptValue() + { + return _scriptValue; + } + + /** + * Sets the script value related with this {@link Npc} instance. + * @param value value to store + */ + public void setScriptValue(int value) + { + _scriptValue = value; + } + + /** + * @param value value to store + * @return {@code true} if stored script value equals given value, {@code false} otherwise + */ + public boolean isScriptValue(int value) + { + return _scriptValue == value; + } + + /** + * @param paramName the parameter name to check + * @return given AI parameter value + */ + public int getAIValue(String paramName) + { + return hasAIValue(paramName) ? NpcPersonalAIData.getInstance().getAIValue(getSpawn().getName(), paramName) : -1; + } + + /** + * @param paramName the parameter name to check + * @return {@code true} if given parameter is set for NPC, {@code false} otherwise + */ + public boolean hasAIValue(String paramName) + { + return (getSpawn() != null) && (getSpawn().getName() != null) && NpcPersonalAIData.getInstance().hasAIValue(getSpawn().getName(), paramName); + } + + /** + * @param npc NPC to check + * @return {@code true} if both given NPC and this NPC is in the same spawn group, {@code false} otherwise + */ + public boolean isInMySpawnGroup(Npc npc) + { + return ((getSpawn() != null) && (npc.getSpawn() != null) && (getSpawn().getName() != null) && (getSpawn().getName().equals(npc.getSpawn().getName()))); + } + + /** + * @return {@code true} if NPC currently located in own spawn point, {@code false} otherwise + */ + public boolean staysInSpawnLoc() + { + return ((_spawn != null) && (_spawn.getX() == getX()) && (_spawn.getY() == getY())); + } + + /** + * @return {@code true} if {@link NpcVariables} instance is attached to current player's scripts, {@code false} otherwise. + */ + public boolean hasVariables() + { + return getScript(NpcVariables.class) != null; + } + + /** + * @return {@link NpcVariables} instance containing parameters regarding NPC. + */ + public NpcVariables getVariables() + { + final NpcVariables vars = getScript(NpcVariables.class); + return vars != null ? vars : addScript(new NpcVariables()); + } + + /** + * Send an "event" to all NPCs within given radius + * @param eventName - name of event + * @param radius - radius to send event + * @param reference - WorldObject to pass, if needed + */ + public void broadcastEvent(String eventName, int radius, WorldObject reference) + { + World.forEachVisibleObjectInRange(this, Npc.class, radius, obj -> + { + if (obj.hasListener(EventType.ON_NPC_EVENT_RECEIVED)) + { + EventDispatcher.getInstance().notifyEventAsync(new OnNpcEventReceived(eventName, this, obj, reference), obj); + } + }); + } + + /** + * Sends an event to a given object. + * @param eventName the event name + * @param receiver the receiver + * @param reference the reference + */ + public void sendScriptEvent(String eventName, WorldObject receiver, WorldObject reference) + { + if (EventDispatcher.getInstance().hasListener(EventType.ON_NPC_EVENT_RECEIVED, receiver)) + { + EventDispatcher.getInstance().notifyEventAsync(new OnNpcEventReceived(eventName, this, receiver.asNpc(), reference), receiver); + } + } + + /** + * Gets point in range between radiusMin and radiusMax from this NPC + * @param radiusMin miminal range from NPC (not closer than) + * @param radiusMax maximal range from NPC (not further than) + * @return Location in given range from this NPC + */ + public Location getPointInRange(int radiusMin, int radiusMax) + { + if ((radiusMax == 0) || (radiusMax < radiusMin)) + { + return new Location(getX(), getY(), getZ()); + } + + final int radius = Rnd.get(radiusMin, radiusMax); + final double angle = Rnd.nextDouble() * 2 * Math.PI; + return new Location((int) (getX() + (radius * Math.cos(angle))), (int) (getY() + (radius * Math.sin(angle))), getZ()); + } + + /** + * Drops an item. + * @param creature the last attacker or main damage dealer + * @param itemId the item ID + * @param itemCount the item count + * @return the dropped item + */ + public Item dropItem(Creature creature, int itemId, int itemCount) + { + Item item = null; + for (int i = 0; i < itemCount; i++) + { + if (ItemData.getInstance().getTemplate(itemId) == null) + { + LOGGER.log(Level.SEVERE, "Item doesn't exist so cannot be dropped. Item ID: " + itemId + " Quest: " + getName()); + return null; + } + + item = ItemManager.createItem(ItemProcessType.LOOT, itemId, itemCount, creature, this); + if (item == null) + { + return null; + } + + if (creature != null) + { + item.getDropProtection().protect(creature); + } + + // Randomize drop position. + final int newX = (getX() + Rnd.get((RANDOM_ITEM_DROP_LIMIT * 2) + 1)) - RANDOM_ITEM_DROP_LIMIT; + final int newY = (getY() + Rnd.get((RANDOM_ITEM_DROP_LIMIT * 2) + 1)) - RANDOM_ITEM_DROP_LIMIT; + final int newZ = getZ() + 20; + + item.dropMe(this, newX, newY, newZ); + + // Add drop to auto destroy item task. + if (!GeneralConfig.LIST_PROTECTED_ITEMS.contains(itemId) && (((GeneralConfig.AUTODESTROY_ITEM_AFTER > 0) && !item.getTemplate().hasExImmediateEffect()) || ((GeneralConfig.HERB_AUTO_DESTROY_TIME > 0) && item.getTemplate().hasExImmediateEffect()))) + { + ItemsAutoDestroyTaskManager.getInstance().addItem(item); + } + + item.setProtected(false); + + // If stackable, end loop as entire count is included in 1 instance of item. + if (item.isStackable() || !GeneralConfig.MULTIPLE_ITEM_DROP) + { + break; + } + } + + return item; + } + + /** + * Method overload for {@link Attackable#dropItem(Creature, int, int)} + * @param creature the last attacker or main damage dealer + * @param item the item holder + * @return the dropped item + */ + public Item dropItem(Creature creature, ItemHolder item) + { + return dropItem(creature, item.getId(), item.getCount()); + } + + @Override + public String getName() + { + return getTemplate().getName(); + } + + @Override + public boolean isVisibleFor(Player player) + { + if (hasListener(EventType.ON_NPC_CAN_BE_SEEN)) + { + final TerminateReturn term = EventDispatcher.getInstance().notifyEvent(new OnNpcCanBeSeen(this, player), this, TerminateReturn.class); + if (term != null) + { + return term.terminate(); + } + } + + return super.isVisibleFor(player); + } + + /** + * Sets if the players can talk with this npc or not + * @param value {@code true} if the players can talk, {@code false} otherwise + */ + public void setTalkable(boolean value) + { + _isTalkable = value; + } + + /** + * Checks if the players can talk to this npc. + * @return {@code true} if the players can talk, {@code false} otherwise. + */ + public boolean isTalkable() + { + return _isTalkable; + } + + /** + * Checks if the NPC is a Quest Monster. + * @return {@code true} if the NPC is a Quest Monster, {@code false} otherwise. + */ + public boolean isQuestMonster() + { + return _isQuestMonster; + } + + /** + * Sets the weapon id with which this npc was killed. + * @param weaponId + */ + public void setKillingBlowWeapon(int weaponId) + { + _killingBlowWeaponId = weaponId; + } + + /** + * @return the id of the weapon with which player killed this npc. + */ + public int getKillingBlowWeapon() + { + return _killingBlowWeaponId; + } + + @Override + public boolean isFakePlayer() + { + return _isFakePlayer; + } + + /** + * Adds a summoned NPC. + * @param npc the summoned NPC + */ + public void addSummonedNpc(Npc npc) + { + if (_summonedNpcs == null) + { + synchronized (this) + { + if (_summonedNpcs == null) + { + _summonedNpcs = new ConcurrentHashMap<>(); + } + } + } + + _summonedNpcs.put(npc.getObjectId(), npc); + + npc.setSummoner(this); + } + + /** + * Removes a summoned NPC by object ID. + * @param objectId the summoned NPC object ID + */ + public void removeSummonedNpc(int objectId) + { + if (_summonedNpcs != null) + { + _summonedNpcs.remove(objectId); + } + } + + /** + * Gets the summoned NPCs. + * @return the summoned NPCs + */ + public Collection getSummonedNpcs() + { + return _summonedNpcs != null ? _summonedNpcs.values() : Collections. emptyList(); + } + + /** + * Gets the summoned NPC by object ID. + * @param objectId the summoned NPC object ID + * @return the summoned NPC + */ + public Npc getSummonedNpc(int objectId) + { + if (_summonedNpcs != null) + { + return _summonedNpcs.get(objectId); + } + + return null; + } + + /** + * Gets the summoned NPC count. + * @return the summoned NPC count + */ + public int getSummonedNpcCount() + { + return _summonedNpcs != null ? _summonedNpcs.size() : 0; + } + + /** + * Resets the summoned NPCs list. + */ + public void resetSummonedNpcs() + { + if (_summonedNpcs != null) + { + _summonedNpcs.clear(); + } + } + + /** + * Broadcasts NpcSay packet to all known players. + * @param chatType the chat type + * @param text the text + */ + public void broadcastSay(ChatType chatType, String text) + { + World.broadcastToVisiblePlayers(this, new NpcSay(this, chatType, text)); + } + + /** + * Broadcasts NpcSay packet to all known players with custom string in specific radius. + * @param chatType the chat type + * @param text the text + * @param radius the radius + */ + public void broadcastSay(ChatType chatType, String text, int radius) + { + World.broadcastToVisiblePlayersInRange(this, new NpcSay(this, chatType, text), radius); + } + + @Override + public int getMinShopDistance() + { + return PrivateStoreRangeConfig.SHOP_MIN_RANGE_FROM_NPC; + } + + public void addQuestTimer(QuestTimer questTimer) + { + synchronized (_questTimers) + { + _questTimers.add(questTimer); + } + } + + public void removeQuestTimer(QuestTimer questTimer) + { + synchronized (_questTimers) + { + _questTimers.remove(questTimer); + } + } + + public void stopQuestTimers() + { + synchronized (_questTimers) + { + for (QuestTimer timer : _questTimers) + { + timer.cancelTask(); + } + + _questTimers.clear(); + } + } + + public void addTimerHolder(TimerHolder timer) + { + synchronized (_timerHolders) + { + _timerHolders.add(timer); + } + } + + public void removeTimerHolder(TimerHolder timer) + { + synchronized (_timerHolders) + { + _timerHolders.remove(timer); + } + } + + public void stopTimerHolders() + { + synchronized (_timerHolders) + { + for (TimerHolder timer : _timerHolders) + { + timer.cancelTask(); + } + + _timerHolders.clear(); + } + } + + @Override + public String toString() + { + final StringBuilder sb = new StringBuilder(); + sb.append(getClass().getSimpleName()); + sb.append(':'); + sb.append(getName()); + sb.append('('); + sb.append(getId()); + sb.append(")["); + sb.append(getObjectId()); + sb.append(']'); + return sb.toString(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/Playable.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/Playable.java new file mode 100644 index 0000000..49f0176 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/Playable.java @@ -0,0 +1,351 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor; + +import org.l2jmobius.gameserver.entity.WorldObject; +import org.l2jmobius.gameserver.entity.actor.enums.creature.InstanceType; +import org.l2jmobius.gameserver.entity.actor.stat.PlayableStat; +import org.l2jmobius.gameserver.entity.actor.status.PlayableStatus; +import org.l2jmobius.gameserver.entity.actor.templates.CreatureTemplate; +import org.l2jmobius.gameserver.entity.instancezone.Instance; +import org.l2jmobius.gameserver.managers.InstanceManager; +import org.l2jmobius.gameserver.managers.ZoneManager; +import org.l2jmobius.gameserver.mechanics.effects.EffectFlag; +import org.l2jmobius.gameserver.mechanics.effects.EffectType; +import org.l2jmobius.gameserver.mechanics.events.EventDispatcher; +import org.l2jmobius.gameserver.mechanics.events.EventType; +import org.l2jmobius.gameserver.mechanics.events.holders.actor.creature.OnCreatureDeath; +import org.l2jmobius.gameserver.mechanics.events.returns.TerminateReturn; +import org.l2jmobius.gameserver.mechanics.script.QuestState; +import org.l2jmobius.gameserver.mechanics.skill.Skill; +import org.l2jmobius.gameserver.network.serverpackets.EtcStatusUpdate; + +/** + * This class represents all Playable characters in the world.
    + * Playable: + *
      + *
    • Player
    • + *
    • Summon
    • + *
    + */ +public abstract class Playable extends Creature +{ + private Creature _lockedTarget = null; + private Player transferDmgTo = null; + + /** + * Creates an abstract playable creature. + * @param objectId the playable object ID + * @param template the creature template + */ + public Playable(int objectId, CreatureTemplate template) + { + super(objectId, template); + setInstanceType(InstanceType.Playable); + setInvul(false); + } + + public Playable(CreatureTemplate template) + { + super(template); + setInstanceType(InstanceType.Playable); + setInvul(false); + } + + @Override + public PlayableStat getStat() + { + return (PlayableStat) super.getStat(); + } + + @Override + public void initCharStat() + { + setStat(new PlayableStat(this)); + } + + @Override + public PlayableStatus getStatus() + { + return (PlayableStatus) super.getStatus(); + } + + @Override + public void initCharStatus() + { + setStatus(new PlayableStatus(this)); + } + + @Override + public boolean doDie(Creature killer) + { + if (EventDispatcher.getInstance().hasListener(EventType.ON_CREATURE_DEATH, this)) + { + final TerminateReturn returnBack = EventDispatcher.getInstance().notifyEvent(new OnCreatureDeath(killer, this), this, TerminateReturn.class); + if ((returnBack != null) && returnBack.terminate()) + { + return false; + } + } + + // Killing is only possible one time. + synchronized (this) + { + if (isDead()) + { + return false; + } + + // Now reset currentHp to zero. + setCurrentHp(0); + setDead(true); + } + + // Set target to null and cancel Attack or Cast. + setTarget(null); + + // Stop movement. + stopMove(null); + + // Stop HP/MP/CP Regeneration task. + getStatus().stopHpMpRegeneration(); + + boolean deleteBuffs = true; + if (isNoblesseBlessedAffected()) + { + stopEffects(EffectType.NOBLESSE_BLESSING); + deleteBuffs = false; + } + + if (isResurrectSpecialAffected()) + { + stopEffects(EffectType.RESURRECTION_SPECIAL); + deleteBuffs = false; + } + + final Player player = asPlayer(); + if (isPlayer() && player.hasCharmOfCourage()) + { + if (player.isInSiege()) + { + player.reviveRequest(player, false, 0); + } + + player.setCharmOfCourage(false); + player.sendPacket(new EtcStatusUpdate(player)); + } + + if (deleteBuffs) + { + stopAllEffectsExceptThoseThatLastThroughDeath(); + } + + // Send the Server->Client packet StatusUpdate with current HP and MP to all other Player to inform. + broadcastStatusUpdate(); + + ZoneManager.getInstance().getRegion(this).onDeath(this); + + // Notify Quest of Playable's death. + if (!player.isNotifyQuestOfDeathEmpty()) + { + for (QuestState qs : player.getNotifyQuestOfDeath()) + { + qs.getQuest().onDeath(killer == null ? this : killer, this, qs); + } + } + + // Notify instance. + if (isPlayer() && isInInstance()) + { + final Instance instance = InstanceManager.getInstance().getInstance(getInstanceId()); + if (instance != null) + { + instance.notifyDeath(player); + } + } + + if (killer != null) + { + final Player killerPlayer = killer.asPlayer(); + if (killerPlayer != null) + { + killerPlayer.onKillUpdatePvPKarma(this); + } + } + + // Notify Creature AI. + getAI().notifyActionDeath(); + updateEffectIcons(); + return true; + } + + public boolean checkIfPvP(Creature target) + { + if (target == null) + { + return false; // Target is null + } + + if (target == this) + { + return false; // Target is self + } + + if (!target.isPlayable()) + { + return false; // Target is not a Playable. + } + + final Player player = asPlayer(); + if (player == null) + { + return false; // Active player is null. + } + + if (player.getKarma() != 0) + { + return false; // Active player has karma. + } + + final Player targetPlayer = target.asPlayer(); + if (targetPlayer == null) + { + return false; // Target player is null. + } + + if (targetPlayer == this) + { + return false; // Target player is self. + } + + if (targetPlayer.getKarma() != 0) + { + return false; // Target player has karma. + } + + if (targetPlayer.getPvpFlag() == 0) + { + return false; // Target player is not flagged. + } + + return true; + } + + /** + * Return True. + */ + @Override + public boolean canBeAttacked() + { + return true; + } + + // Support for Noblesse Blessing skill, where buffs are retained after resurrect. + public boolean isNoblesseBlessedAffected() + { + return isAffected(EffectFlag.NOBLESS_BLESSING); + } + + /** + * @return {@code true} if char can resurrect by himself, {@code false} otherwise + */ + public boolean isResurrectSpecialAffected() + { + return isAffected(EffectFlag.RESURRECTION_SPECIAL); + } + + /** + * @return {@code true} if the Silent Moving mode is active, {@code false} otherwise + */ + public boolean isSilentMovingAffected() + { + return isAffected(EffectFlag.SILENT_MOVE); + } + + /** + * For Newbie Protection Blessing skill, keeps you safe from an attack by a chaotic character >= 10 levels apart from you. + * @return + */ + public boolean isProtectionBlessingAffected() + { + return isAffected(EffectFlag.PROTECTION_BLESSING); + } + + @Override + public void updateEffectIcons(boolean partyOnly) + { + getEffectList().updateEffectIcons(partyOnly); + } + + public boolean isLockedTarget() + { + return _lockedTarget != null; + } + + public Creature getLockedTarget() + { + return _lockedTarget; + } + + public void setLockedTarget(Creature creature) + { + _lockedTarget = creature; + } + + public void setTransferDamageTo(Player val) + { + transferDmgTo = val; + } + + public Player getTransferingDamageTo() + { + return transferDmgTo; + } + + public abstract void doPickupItem(WorldObject object); + + public abstract byte getPvpFlag(); + + public abstract boolean useMagic(Skill skill, boolean forceUse, boolean dontMove); + + public abstract void storeMe(); + + public abstract void storeEffect(boolean storeEffects); + + public abstract void restoreEffects(); + + public boolean isOnEvent() + { + return false; + } + + @Override + public boolean isPlayable() + { + return true; + } + + @Override + public Playable asPlayable() + { + return this; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/Player.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/Player.java new file mode 100644 index 0000000..899b3a5 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/Player.java @@ -0,0 +1,14138 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedDeque; +import java.util.concurrent.ConcurrentSkipListMap; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.ReentrantLock; +import java.util.logging.Level; + +import org.l2jmobius.commons.database.DatabaseFactory; +import org.l2jmobius.commons.threads.ThreadPool; +import org.l2jmobius.commons.util.Rnd; +import org.l2jmobius.gameserver.LoginServerThread; +import org.l2jmobius.gameserver.ai.CreatureAI; +import org.l2jmobius.gameserver.ai.Intention; +import org.l2jmobius.gameserver.ai.PlayerAI; +import org.l2jmobius.gameserver.ai.SummonAI; +import org.l2jmobius.gameserver.cache.RelationCache; +import org.l2jmobius.gameserver.communitybbs.BB.Forum; +import org.l2jmobius.gameserver.communitybbs.Manager.ForumsBBSManager; +import org.l2jmobius.gameserver.config.FeatureConfig; +import org.l2jmobius.gameserver.config.GeneralConfig; +import org.l2jmobius.gameserver.config.PlayerConfig; +import org.l2jmobius.gameserver.config.PvpConfig; +import org.l2jmobius.gameserver.config.RatesConfig; +import org.l2jmobius.gameserver.config.custom.AutoPlayConfig; +import org.l2jmobius.gameserver.config.custom.DualboxCheckConfig; +import org.l2jmobius.gameserver.config.custom.FactionSystemConfig; +import org.l2jmobius.gameserver.config.custom.FakePlayersConfig; +import org.l2jmobius.gameserver.config.custom.FreeMountsConfig; +import org.l2jmobius.gameserver.config.custom.MultilingualSupportConfig; +import org.l2jmobius.gameserver.config.custom.OfflinePlayConfig; +import org.l2jmobius.gameserver.config.custom.OfflineTradeConfig; +import org.l2jmobius.gameserver.config.custom.PremiumSystemConfig; +import org.l2jmobius.gameserver.config.custom.PrivateStoreRangeConfig; +import org.l2jmobius.gameserver.config.custom.PvpAnnounceConfig; +import org.l2jmobius.gameserver.config.custom.PvpRewardItemConfig; +import org.l2jmobius.gameserver.config.custom.PvpTitleColorConfig; +import org.l2jmobius.gameserver.config.custom.RebirthConfig; +import org.l2jmobius.gameserver.data.enums.CategoryType; +import org.l2jmobius.gameserver.data.holders.AccessLevel; +import org.l2jmobius.gameserver.data.holders.PetLevelData; +import org.l2jmobius.gameserver.data.holders.SellBuffHolder; +import org.l2jmobius.gameserver.data.sql.CharInfoTable; +import org.l2jmobius.gameserver.data.sql.CharSummonTable; +import org.l2jmobius.gameserver.data.sql.ClanTable; +import org.l2jmobius.gameserver.data.sql.OfflinePlayTable; +import org.l2jmobius.gameserver.data.sql.OfflineTraderTable; +import org.l2jmobius.gameserver.data.xml.AdminData; +import org.l2jmobius.gameserver.data.xml.CategoryData; +import org.l2jmobius.gameserver.data.xml.ClassListData; +import org.l2jmobius.gameserver.data.xml.ExperienceData; +import org.l2jmobius.gameserver.data.xml.ExperienceLossData; +import org.l2jmobius.gameserver.data.xml.FishData; +import org.l2jmobius.gameserver.data.xml.HennaData; +import org.l2jmobius.gameserver.data.xml.ItemData; +import org.l2jmobius.gameserver.data.xml.NpcData; +import org.l2jmobius.gameserver.data.xml.PetDataTable; +import org.l2jmobius.gameserver.data.xml.PlayerTemplateData; +import org.l2jmobius.gameserver.data.xml.RecipeData; +import org.l2jmobius.gameserver.data.xml.SendMessageLocalisationData; +import org.l2jmobius.gameserver.data.xml.SkillData; +import org.l2jmobius.gameserver.data.xml.SkillTreeData; +import org.l2jmobius.gameserver.entity.Location; +import org.l2jmobius.gameserver.entity.World; +import org.l2jmobius.gameserver.entity.WorldObject; +import org.l2jmobius.gameserver.entity.actor.appearance.PlayerAppearance; +import org.l2jmobius.gameserver.entity.actor.enums.creature.InstanceType; +import org.l2jmobius.gameserver.entity.actor.enums.creature.Race; +import org.l2jmobius.gameserver.entity.actor.enums.creature.Team; +import org.l2jmobius.gameserver.entity.actor.enums.player.IllegalActionPunishmentType; +import org.l2jmobius.gameserver.entity.actor.enums.player.MountType; +import org.l2jmobius.gameserver.entity.actor.enums.player.PlayerAction; +import org.l2jmobius.gameserver.entity.actor.enums.player.PlayerClass; +import org.l2jmobius.gameserver.entity.actor.enums.player.PrivateStoreType; +import org.l2jmobius.gameserver.entity.actor.enums.player.Sex; +import org.l2jmobius.gameserver.entity.actor.enums.player.ShortcutType; +import org.l2jmobius.gameserver.entity.actor.enums.player.TeleportWhereType; +import org.l2jmobius.gameserver.entity.actor.holders.creature.TimeStamp; +import org.l2jmobius.gameserver.entity.actor.holders.player.AutoPlaySettingsHolder; +import org.l2jmobius.gameserver.entity.actor.holders.player.AutoUseSettingsHolder; +import org.l2jmobius.gameserver.entity.actor.holders.player.BlockList; +import org.l2jmobius.gameserver.entity.actor.holders.player.ContactList; +import org.l2jmobius.gameserver.entity.actor.holders.player.Duel; +import org.l2jmobius.gameserver.entity.actor.holders.player.Macro; +import org.l2jmobius.gameserver.entity.actor.holders.player.MacroList; +import org.l2jmobius.gameserver.entity.actor.holders.player.Radar; +import org.l2jmobius.gameserver.entity.actor.holders.player.Shortcut; +import org.l2jmobius.gameserver.entity.actor.holders.player.Shortcuts; +import org.l2jmobius.gameserver.entity.actor.holders.player.SubClassHolder; +import org.l2jmobius.gameserver.entity.actor.instance.Boat; +import org.l2jmobius.gameserver.entity.actor.instance.Cubic; +import org.l2jmobius.gameserver.entity.actor.instance.Decoy; +import org.l2jmobius.gameserver.entity.actor.instance.Defender; +import org.l2jmobius.gameserver.entity.actor.instance.Door; +import org.l2jmobius.gameserver.entity.actor.instance.EventMonster; +import org.l2jmobius.gameserver.entity.actor.instance.FriendlyMob; +import org.l2jmobius.gameserver.entity.actor.instance.Guard; +import org.l2jmobius.gameserver.entity.actor.instance.TamedBeast; +import org.l2jmobius.gameserver.entity.actor.instance.Trap; +import org.l2jmobius.gameserver.entity.actor.stat.PlayerStat; +import org.l2jmobius.gameserver.entity.actor.status.PlayerStatus; +import org.l2jmobius.gameserver.entity.actor.tasks.player.DismountTask; +import org.l2jmobius.gameserver.entity.actor.tasks.player.FameTask; +import org.l2jmobius.gameserver.entity.actor.tasks.player.InventoryEnableTask; +import org.l2jmobius.gameserver.entity.actor.tasks.player.LookingForFishTask; +import org.l2jmobius.gameserver.entity.actor.tasks.player.PetFeedTask; +import org.l2jmobius.gameserver.entity.actor.tasks.player.RentPetTask; +import org.l2jmobius.gameserver.entity.actor.tasks.player.ResetChargesTask; +import org.l2jmobius.gameserver.entity.actor.tasks.player.SitDownTask; +import org.l2jmobius.gameserver.entity.actor.tasks.player.StandUpTask; +import org.l2jmobius.gameserver.entity.actor.tasks.player.TeleportWatchdogTask; +import org.l2jmobius.gameserver.entity.actor.tasks.player.VitalityTask; +import org.l2jmobius.gameserver.entity.actor.tasks.player.WarnUserTakeBreakTask; +import org.l2jmobius.gameserver.entity.actor.tasks.player.WaterTask; +import org.l2jmobius.gameserver.entity.actor.templates.PlayerTemplate; +import org.l2jmobius.gameserver.entity.clan.Clan; +import org.l2jmobius.gameserver.entity.clan.ClanAccess; +import org.l2jmobius.gameserver.entity.clan.ClanMember; +import org.l2jmobius.gameserver.entity.clan.ClanPrivileges; +import org.l2jmobius.gameserver.entity.groups.Party; +import org.l2jmobius.gameserver.entity.groups.PartyDistributionType; +import org.l2jmobius.gameserver.entity.groups.PartyMessageType; +import org.l2jmobius.gameserver.entity.groups.matching.PartyMatchRoom; +import org.l2jmobius.gameserver.entity.groups.matching.PartyMatchRoomList; +import org.l2jmobius.gameserver.entity.groups.matching.PartyMatchWaitingList; +import org.l2jmobius.gameserver.entity.instancezone.Instance; +import org.l2jmobius.gameserver.entity.item.Armor; +import org.l2jmobius.gameserver.entity.item.EtcItem; +import org.l2jmobius.gameserver.entity.item.Henna; +import org.l2jmobius.gameserver.entity.item.ItemTemplate; +import org.l2jmobius.gameserver.entity.item.Weapon; +import org.l2jmobius.gameserver.entity.item.enums.BodyPart; +import org.l2jmobius.gameserver.entity.item.enums.ItemLocation; +import org.l2jmobius.gameserver.entity.item.enums.ItemProcessType; +import org.l2jmobius.gameserver.entity.item.enums.ShotType; +import org.l2jmobius.gameserver.entity.item.holders.ItemHolder; +import org.l2jmobius.gameserver.entity.item.instance.Item; +import org.l2jmobius.gameserver.entity.item.recipe.ManufactureItem; +import org.l2jmobius.gameserver.entity.item.recipe.RecipeList; +import org.l2jmobius.gameserver.entity.item.type.ActionType; +import org.l2jmobius.gameserver.entity.item.type.ArmorType; +import org.l2jmobius.gameserver.entity.item.type.EtcItemType; +import org.l2jmobius.gameserver.entity.item.type.WeaponType; +import org.l2jmobius.gameserver.entity.itemcontainer.Inventory; +import org.l2jmobius.gameserver.entity.itemcontainer.ItemContainer; +import org.l2jmobius.gameserver.entity.itemcontainer.PetInventory; +import org.l2jmobius.gameserver.entity.itemcontainer.PlayerFreight; +import org.l2jmobius.gameserver.entity.itemcontainer.PlayerInventory; +import org.l2jmobius.gameserver.entity.itemcontainer.PlayerRefund; +import org.l2jmobius.gameserver.entity.itemcontainer.PlayerWarehouse; +import org.l2jmobius.gameserver.entity.zone.ZoneId; +import org.l2jmobius.gameserver.entity.zone.ZoneRegion; +import org.l2jmobius.gameserver.entity.zone.ZoneType; +import org.l2jmobius.gameserver.entity.zone.type.BossZone; +import org.l2jmobius.gameserver.entity.zone.type.WaterZone; +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.handler.IItemHandler; +import org.l2jmobius.gameserver.handler.ItemHandler; +import org.l2jmobius.gameserver.interfaces.ILocational; +import org.l2jmobius.gameserver.managers.AntiFeedManager; +import org.l2jmobius.gameserver.managers.CastleManager; +import org.l2jmobius.gameserver.managers.CoupleManager; +import org.l2jmobius.gameserver.managers.CursedWeaponsManager; +import org.l2jmobius.gameserver.managers.DimensionalRiftManager; +import org.l2jmobius.gameserver.managers.DuelManager; +import org.l2jmobius.gameserver.managers.GrandBossManager; +import org.l2jmobius.gameserver.managers.IdManager; +import org.l2jmobius.gameserver.managers.InstanceManager; +import org.l2jmobius.gameserver.managers.ItemManager; +import org.l2jmobius.gameserver.managers.ItemsOnGroundManager; +import org.l2jmobius.gameserver.managers.PunishmentManager; +import org.l2jmobius.gameserver.managers.RebirthManager; +import org.l2jmobius.gameserver.managers.RecipeManager; +import org.l2jmobius.gameserver.managers.ScriptManager; +import org.l2jmobius.gameserver.managers.SiegeManager; +import org.l2jmobius.gameserver.managers.ZoneManager; +import org.l2jmobius.gameserver.mechanics.effects.EffectFlag; +import org.l2jmobius.gameserver.mechanics.effects.EffectType; +import org.l2jmobius.gameserver.mechanics.events.EventDispatcher; +import org.l2jmobius.gameserver.mechanics.events.EventType; +import org.l2jmobius.gameserver.mechanics.events.holders.actor.playable.OnPlayableExpChanged; +import org.l2jmobius.gameserver.mechanics.events.holders.actor.player.OnPlayerFameChanged; +import org.l2jmobius.gameserver.mechanics.events.holders.actor.player.OnPlayerHennaRemove; +import org.l2jmobius.gameserver.mechanics.events.holders.actor.player.OnPlayerItemEquip; +import org.l2jmobius.gameserver.mechanics.events.holders.actor.player.OnPlayerKarmaChanged; +import org.l2jmobius.gameserver.mechanics.events.holders.actor.player.OnPlayerLogin; +import org.l2jmobius.gameserver.mechanics.events.holders.actor.player.OnPlayerLogout; +import org.l2jmobius.gameserver.mechanics.events.holders.actor.player.OnPlayerPKChanged; +import org.l2jmobius.gameserver.mechanics.events.holders.actor.player.OnPlayerProfessionCancel; +import org.l2jmobius.gameserver.mechanics.events.holders.actor.player.OnPlayerProfessionChange; +import org.l2jmobius.gameserver.mechanics.events.holders.actor.player.OnPlayerPvPChanged; +import org.l2jmobius.gameserver.mechanics.events.holders.actor.player.OnPlayerPvPKill; +import org.l2jmobius.gameserver.mechanics.events.listeners.FunctionEventListener; +import org.l2jmobius.gameserver.mechanics.events.returns.TerminateReturn; +import org.l2jmobius.gameserver.mechanics.fishing.Fish; +import org.l2jmobius.gameserver.mechanics.fishing.Fishing; +import org.l2jmobius.gameserver.mechanics.multisell.PreparedListContainer; +import org.l2jmobius.gameserver.mechanics.olympiad.Hero; +import org.l2jmobius.gameserver.mechanics.olympiad.Olympiad; +import org.l2jmobius.gameserver.mechanics.punishment.PunishmentAffect; +import org.l2jmobius.gameserver.mechanics.punishment.PunishmentType; +import org.l2jmobius.gameserver.mechanics.script.Quest; +import org.l2jmobius.gameserver.mechanics.script.QuestState; +import org.l2jmobius.gameserver.mechanics.script.QuestTimer; +import org.l2jmobius.gameserver.mechanics.script.timers.TimerHolder; +import org.l2jmobius.gameserver.mechanics.sevensigns.SevenSigns; +import org.l2jmobius.gameserver.mechanics.sevensigns.SevenSignsFestival; +import org.l2jmobius.gameserver.mechanics.siege.Castle; +import org.l2jmobius.gameserver.mechanics.siege.Siege; +import org.l2jmobius.gameserver.mechanics.skill.AbnormalType; +import org.l2jmobius.gameserver.mechanics.skill.BuffInfo; +import org.l2jmobius.gameserver.mechanics.skill.CommonSkill; +import org.l2jmobius.gameserver.mechanics.skill.Skill; +import org.l2jmobius.gameserver.mechanics.skill.enums.Element; +import org.l2jmobius.gameserver.mechanics.skill.enums.SkillFinishType; +import org.l2jmobius.gameserver.mechanics.skill.holders.SkillHolder; +import org.l2jmobius.gameserver.mechanics.skill.holders.SkillLearn; +import org.l2jmobius.gameserver.mechanics.skill.holders.SkillUseHolder; +import org.l2jmobius.gameserver.mechanics.skill.targets.TargetType; +import org.l2jmobius.gameserver.mechanics.stats.Formulas; +import org.l2jmobius.gameserver.mechanics.stats.Stat; +import org.l2jmobius.gameserver.mechanics.variables.AccountVariables; +import org.l2jmobius.gameserver.mechanics.variables.PlayerVariables; +import org.l2jmobius.gameserver.network.Disconnection; +import org.l2jmobius.gameserver.network.GameClient; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.enums.ChatType; +import org.l2jmobius.gameserver.network.enums.HtmlActionScope; +import org.l2jmobius.gameserver.network.holders.RequestPartner; +import org.l2jmobius.gameserver.network.holders.TradeList; +import org.l2jmobius.gameserver.network.serverpackets.AbstractHtmlPacket; +import org.l2jmobius.gameserver.network.serverpackets.ActionFailed; +import org.l2jmobius.gameserver.network.serverpackets.ChangeWaitType; +import org.l2jmobius.gameserver.network.serverpackets.CharInfo; +import org.l2jmobius.gameserver.network.serverpackets.ConfirmDlg; +import org.l2jmobius.gameserver.network.serverpackets.CreatureSay; +import org.l2jmobius.gameserver.network.serverpackets.EtcStatusUpdate; +import org.l2jmobius.gameserver.network.serverpackets.ExAutoSoulShot; +import org.l2jmobius.gameserver.network.serverpackets.ExDuelUpdateUserInfo; +import org.l2jmobius.gameserver.network.serverpackets.ExFishingEnd; +import org.l2jmobius.gameserver.network.serverpackets.ExFishingStart; +import org.l2jmobius.gameserver.network.serverpackets.ExOlympiadMode; +import org.l2jmobius.gameserver.network.serverpackets.ExOlympiadUserInfo; +import org.l2jmobius.gameserver.network.serverpackets.ExSetCompassZoneCode; +import org.l2jmobius.gameserver.network.serverpackets.ExStorageMaxCount; +import org.l2jmobius.gameserver.network.serverpackets.ExUseSharedGroupItem; +import org.l2jmobius.gameserver.network.serverpackets.FriendStatusPacket; +import org.l2jmobius.gameserver.network.serverpackets.GetOnVehicle; +import org.l2jmobius.gameserver.network.serverpackets.HennaInfo; +import org.l2jmobius.gameserver.network.serverpackets.InventoryUpdate; +import org.l2jmobius.gameserver.network.serverpackets.ItemList; +import org.l2jmobius.gameserver.network.serverpackets.LeaveWorld; +import org.l2jmobius.gameserver.network.serverpackets.MagicSkillUse; +import org.l2jmobius.gameserver.network.serverpackets.MyTargetSelected; +import org.l2jmobius.gameserver.network.serverpackets.NicknameChanged; +import org.l2jmobius.gameserver.network.serverpackets.ObservationEnter; +import org.l2jmobius.gameserver.network.serverpackets.ObservationExit; +import org.l2jmobius.gameserver.network.serverpackets.PartySmallWindowUpdate; +import org.l2jmobius.gameserver.network.serverpackets.PetInventoryUpdate; +import org.l2jmobius.gameserver.network.serverpackets.PlaySound; +import org.l2jmobius.gameserver.network.serverpackets.PledgeShowMemberListDelete; +import org.l2jmobius.gameserver.network.serverpackets.PledgeShowMemberListUpdate; +import org.l2jmobius.gameserver.network.serverpackets.PrivateStoreListBuy; +import org.l2jmobius.gameserver.network.serverpackets.PrivateStoreListSell; +import org.l2jmobius.gameserver.network.serverpackets.PrivateStoreManageListBuy; +import org.l2jmobius.gameserver.network.serverpackets.PrivateStoreManageListSell; +import org.l2jmobius.gameserver.network.serverpackets.PrivateStoreMsgBuy; +import org.l2jmobius.gameserver.network.serverpackets.PrivateStoreMsgSell; +import org.l2jmobius.gameserver.network.serverpackets.RecipeShopMsg; +import org.l2jmobius.gameserver.network.serverpackets.RecipeShopSellList; +import org.l2jmobius.gameserver.network.serverpackets.RelationChanged; +import org.l2jmobius.gameserver.network.serverpackets.Ride; +import org.l2jmobius.gameserver.network.serverpackets.ServerPacket; +import org.l2jmobius.gameserver.network.serverpackets.SetupGauge; +import org.l2jmobius.gameserver.network.serverpackets.ShortcutInit; +import org.l2jmobius.gameserver.network.serverpackets.SkillCoolTime; +import org.l2jmobius.gameserver.network.serverpackets.SkillList; +import org.l2jmobius.gameserver.network.serverpackets.Snoop; +import org.l2jmobius.gameserver.network.serverpackets.SocialAction; +import org.l2jmobius.gameserver.network.serverpackets.StatusUpdate; +import org.l2jmobius.gameserver.network.serverpackets.StopMove; +import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; +import org.l2jmobius.gameserver.network.serverpackets.TargetSelected; +import org.l2jmobius.gameserver.network.serverpackets.TargetUnselected; +import org.l2jmobius.gameserver.network.serverpackets.TradeDone; +import org.l2jmobius.gameserver.network.serverpackets.TradeOtherDone; +import org.l2jmobius.gameserver.network.serverpackets.TradeStart; +import org.l2jmobius.gameserver.network.serverpackets.UserInfo; +import org.l2jmobius.gameserver.network.serverpackets.ValidateLocation; +import org.l2jmobius.gameserver.taskmanagers.AttackStanceTaskManager; +import org.l2jmobius.gameserver.taskmanagers.AutoPlayTaskManager; +import org.l2jmobius.gameserver.taskmanagers.AutoUseTaskManager; +import org.l2jmobius.gameserver.taskmanagers.DecayTaskManager; +import org.l2jmobius.gameserver.taskmanagers.GameTimeTaskManager; +import org.l2jmobius.gameserver.taskmanagers.ItemsAutoDestroyTaskManager; +import org.l2jmobius.gameserver.taskmanagers.PlayerAutoSaveTaskManager; +import org.l2jmobius.gameserver.taskmanagers.PvpFlagTaskManager; +import org.l2jmobius.gameserver.util.LocationUtil; + +/** + * This class represents all player characters in the world.
    + * There is always a client-thread connected to this (except if a player-store is activated upon logout). + */ +public class Player extends Playable +{ + // Character Skill SQL String Definitions: + private static final String RESTORE_SKILLS_FOR_CHAR = "SELECT skill_id,skill_level FROM character_skills WHERE charId=? AND class_index=?"; + private static final String UPDATE_CHARACTER_SKILL_LEVEL = "UPDATE character_skills SET skill_level=? WHERE skill_id=? AND charId=? AND class_index=?"; + private static final String ADD_NEW_SKILLS = "REPLACE INTO character_skills (charId,skill_id,skill_level,class_index) VALUES (?,?,?,?)"; + private static final String DELETE_SKILL_FROM_CHAR = "DELETE FROM character_skills WHERE skill_id=? AND charId=? AND class_index=?"; + private static final String DELETE_CHAR_SKILLS = "DELETE FROM character_skills WHERE charId=? AND class_index=?"; + + // Character Skill Save SQL String Definitions: + private static final String ADD_SKILL_SAVE = "REPLACE INTO character_skills_save (charId,skill_id,skill_level,remaining_time,reuse_delay,systime,restore_type,class_index,buff_index) VALUES (?,?,?,?,?,?,?,?,?)"; + private static final String RESTORE_SKILL_SAVE = "SELECT skill_id,skill_level,remaining_time, reuse_delay, systime, restore_type FROM character_skills_save WHERE charId=? AND class_index=? ORDER BY buff_index ASC"; + private static final String DELETE_SKILL_SAVE = "DELETE FROM character_skills_save WHERE charId=? AND class_index=?"; + + // Character Item Reuse Time String Definition: + private static final String ADD_ITEM_REUSE_SAVE = "INSERT INTO character_item_reuse_save (charId,itemId,itemObjId,reuseDelay,systime) VALUES (?,?,?,?,?)"; + private static final String RESTORE_ITEM_REUSE_SAVE = "SELECT charId,itemId,itemObjId,reuseDelay,systime FROM character_item_reuse_save WHERE charId=?"; + private static final String DELETE_ITEM_REUSE_SAVE = "DELETE FROM character_item_reuse_save WHERE charId=?"; + + // Character Character SQL String Definitions: + private static final String INSERT_CHARACTER = "INSERT INTO characters (account_name,charId,char_name,level,maxHp,curHp,maxCp,curCp,maxMp,curMp,face,hairStyle,hairColor,sex,exp,sp,karma,fame,pvpkills,pkkills,clanid,race,classid,deletetime,cancraft,title,title_color,accesslevel,online,isin7sdungeon,clan_privs,wantspeace,base_class,newbie,nobless,power_grade,createDate,lastAccess,last_recom_date) values (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"; + private static final String UPDATE_CHARACTER = "UPDATE characters SET level=?,maxHp=?,curHp=?,maxCp=?,curCp=?,maxMp=?,curMp=?,face=?,hairStyle=?,hairColor=?,sex=?,heading=?,x=?,y=?,z=?,exp=?,expBeforeDeath=?,sp=?,karma=?,fame=?,pvpkills=?,pkkills=?,clanid=?,race=?,classid=?,deletetime=?,title=?,title_color=?,accesslevel=?,online=?,isin7sdungeon=?,clan_privs=?,wantspeace=?,base_class=?,onlinetime=?,newbie=?,nobless=?,power_grade=?,subpledge=?,lvl_joined_academy=?,apprentice=?,sponsor=?,clan_join_expiry_time=?,clan_create_expiry_time=?,char_name=?,death_penalty_level=?,bookmarkslot=?,vitality_points=?,language=?,faction=?,pccafe_points=?,last_recom_date=?,rec_have=?,rec_left=? WHERE charId=?"; + private static final String RESTORE_CHARACTER = "SELECT * FROM characters WHERE charId=?"; + + // Character Subclass SQL String Definitions: + private static final String RESTORE_CHAR_SUBCLASSES = "SELECT class_id,exp,sp,level,class_index FROM character_subclasses WHERE charId=? ORDER BY class_index ASC"; + private static final String ADD_CHAR_SUBCLASS = "INSERT INTO character_subclasses (charId,class_id,exp,sp,level,class_index) VALUES (?,?,?,?,?,?)"; + private static final String UPDATE_CHAR_SUBCLASS = "UPDATE character_subclasses SET exp=?,sp=?,level=?,class_id=? WHERE charId=? AND class_index =?"; + private static final String DELETE_CHAR_SUBCLASS = "DELETE FROM character_subclasses WHERE charId=? AND class_index=?"; + + // Character Henna SQL String Definitions: + private static final String RESTORE_CHAR_HENNAS = "SELECT slot,symbol_id FROM character_hennas WHERE charId=? AND class_index=?"; + private static final String ADD_CHAR_HENNA = "INSERT INTO character_hennas (charId,symbol_id,slot,class_index) VALUES (?,?,?,?)"; + private static final String DELETE_CHAR_HENNA = "DELETE FROM character_hennas WHERE charId=? AND slot=? AND class_index=?"; + + // Character Shortcut SQL String Definitions: + private static final String DELETE_CHAR_SHORTCUTS = "DELETE FROM character_shortcuts WHERE charId=? AND class_index=?"; + + // Character Recommendation SQL String Definitions: + private static final String RESTORE_CHAR_RECOMS = "SELECT charId,target_id FROM character_recommends WHERE charId=?"; + private static final String ADD_CHAR_RECOM = "INSERT INTO character_recommends (charId,target_id) VALUES (?,?)"; + private static final String DELETE_CHAR_RECOMS = "DELETE FROM character_recommends WHERE charId=?"; + + // Character Recipe List Save + private static final String DELETE_CHAR_RECIPE_SHOP = "DELETE FROM character_recipeshoplist WHERE charId=?"; + private static final String INSERT_CHAR_RECIPE_SHOP = "REPLACE INTO character_recipeshoplist (`charId`, `recipeId`, `price`, `index`) VALUES (?, ?, ?, ?)"; + private static final String RESTORE_CHAR_RECIPE_SHOP = "SELECT * FROM character_recipeshoplist WHERE charId=? ORDER BY `index`"; + + public static final String NEWBIE_KEY = "NEWBIE"; + + public static final int ID_NONE = -1; + + public static final int REQUEST_TIMEOUT = 15; + + private int _pcCafePoints = 0; + + private GameClient _client; + private String _ip = "N/A"; + + private final String _accountName; + private long _deleteTimer; + private Calendar _createDate = Calendar.getInstance(); + + private String _lang = null; + private String _htmlPrefix = ""; + + private volatile boolean _isOnline = false; + private boolean _offlinePlay = false; + private boolean _enteredWorld = false; + private long _onlineTime; + private long _onlineBeginTime; + private long _lastAccess; + private long _uptime; + + private final InventoryUpdate _inventoryUpdate = new InventoryUpdate(); + private ScheduledFuture _inventoryUpdateTask; + private ScheduledFuture _itemListTask; + private ScheduledFuture _skillListTask; + private ScheduledFuture _updateAndBroadcastStatusTask; + private ScheduledFuture _broadcastCharInfoTask; + private ScheduledFuture _broadcastStatusUpdateTask; + + private boolean _subclassLock = false; + protected int _baseClass; + protected int _activeClass; + protected int _classIndex = 0; + + /** Data for mounted pets. */ + private int _controlItemId; + private PetLevelData _leveldata; + private int _curFeed; + protected Future _mountFeedTask; + private ScheduledFuture _dismountTask; + private boolean _petItems = false; + + /** The list of sub-classes this character has. */ + private final Map _subClasses = new ConcurrentHashMap<>(); + + private final PlayerAppearance _appearance; + + /** The Experience of the Player before the last Death Penalty */ + private long _expBeforeDeath; + + /** The number of player killed during a PvP (the player killed was PvP Flagged) */ + private int _pvpKills; + + /** The PK counter of the Player (= Number of non PvP Flagged player killed) */ + private int _pkKills; + + /** The PvP Flag state of the Player (0=White, 1=Purple) */ + private byte _pvpFlag; + + /** The Fame of this Player */ + private int _fame; + private ScheduledFuture _fameTask; + + /** Vitality recovery task */ + private ScheduledFuture _vitalityTask; + + private ScheduledFuture _teleportWatchdog; + + /** The Siege state of the Player */ + private byte _siegeState = 0; + + /** The id of castle/fort which the Player is registered for siege */ + private int _siegeSide = 0; + + private int _curWeightPenalty = 0; + + private int _lastCompassZone; // The last compass zone update send to the client. + + private boolean _isIn7sDungeon = false; + + private final ContactList _contactList = new ContactList(this); + + // Friend list. + private final Collection _friendList = ConcurrentHashMap.newKeySet(); + + // Related to Community Board. + private final List _selectedFriendList = new ArrayList<>(); + private final List _selectedBlocksList = new ArrayList<>(); + private int _mailPosition; + + private boolean _canFeed; + private boolean _isInSiege; + private boolean _isInHideoutSiege = false; + + /** Olympiad */ + private boolean _inOlympiadMode = false; + private boolean _olympiadStart = false; + private int _olympiadGameId = -1; + private int _olympiadSide = -1; + /** Olympiad buff count. */ + private int _olyBuffsCount = 0; + + /** Duel */ + private boolean _isInDuel = false; + private boolean _startingDuel = false; + private int _duelState = Duel.DUELSTATE_NODUEL; + private int _duelId = 0; + private SystemMessageId _noDuelReason = SystemMessageId.THERE_IS_NO_OPPONENT_TO_RECEIVE_YOUR_CHALLENGE_FOR_A_DUEL; + + /** Boat and AirShip */ + private Vehicle _vehicle = null; + private Location _inVehiclePosition; + + public ScheduledFuture _taskForFish; + private MountType _mountType = MountType.NONE; + private int _mountNpcId; + private int _mountLevel; + /** Store object used to summon the strider you are mounting **/ + private int _mountObjectID = 0; + + public int _telemode = 0; + + private boolean _inCrystallize; + private boolean _isCrafting; + + private long _offlineShopStart = 0; + + /** The table containing all RecipeList of the Player */ + private final Map _dwarvenRecipeBook = new ConcurrentHashMap<>(); + private final Map _commonRecipeBook = new ConcurrentHashMap<>(); + + /** True if the Player is sitting */ + private boolean _waitTypeSitting; + private boolean _sittingInProgress; + + /** Location before entering Observer Mode */ + private final Location _lastLoc = new Location(0, 0, 0); + private boolean _observerMode = false; + + /** Stored from last ValidatePosition **/ + private final Location _lastServerPosition = new Location(0, 0, 0); + + private final AtomicBoolean _blinkActive = new AtomicBoolean(); + + private final Map _elementSeeds = new HashMap<>(4); + { + _elementSeeds.put(Element.FIRE, new AtomicInteger()); + _elementSeeds.put(Element.WATER, new AtomicInteger()); + _elementSeeds.put(Element.WIND, new AtomicInteger()); + _elementSeeds.put(Element.EARTH, new AtomicInteger()); + } + + /** The number of recommendation obtained by the Player */ + private int _recomHave; // How much I was recommended by others. + /** The number of recommendation that the Player can give */ + private int _recomLeft; // How many recommendations I can give to others. + /** Date when recommendation points were updated last time */ + private long _lastRecomUpdate; + /** List with the recommendations this player gave */ + private final Collection _recomChars = ConcurrentHashMap.newKeySet(); + + private final PlayerInventory _inventory = new PlayerInventory(this); + private final PlayerFreight _freight = new PlayerFreight(this); + private final PlayerWarehouse _warehouse = new PlayerWarehouse(this); + private PlayerRefund _refund; + + private PrivateStoreType _privateStoreType = PrivateStoreType.NONE; + + private TradeList _activeTradeList; + private ItemContainer _activeWarehouse; + private Map _manufactureItems; + private String _storeName = ""; + private TradeList _sellList; + private TradeList _buyList; + + // Multisell + private PreparedListContainer _currentMultiSell = null; + + private boolean _newbie = false; + private boolean _noble = false; + private boolean _hero = false; + + /** Premium System */ + private boolean _premiumStatus = false; + + /** Faction System */ + private boolean _isGood = false; + private boolean _isEvil = false; + + /** The Npc corresponding to the last Folk which one the player talked. */ + private Npc _lastFolkNpc = null; + + /** Last NPC Id talked on a quest */ + private int _questNpcObject = 0; + + /** The table containing all Quests began by the Player */ + private final Map _quests = new ConcurrentSkipListMap<>(String.CASE_INSENSITIVE_ORDER); + + /** The list containing all shortcuts of this player. */ + private final Shortcuts _shortcuts = new Shortcuts(this); + + /** The list containing all macros of this player. */ + private final MacroList _macros = new MacroList(this); + + private final Set _snoopListener = ConcurrentHashMap.newKeySet(1); + private final Set _snoopedPlayer = ConcurrentHashMap.newKeySet(1); + + /** Hennas */ + private final Henna[] _henna = new Henna[3]; + private int _hennaSTR; + private int _hennaINT; + private int _hennaDEX; + private int _hennaMEN; + private int _hennaWIT; + private int _hennaCON; + + /** The Summon of the Player */ + private Summon _summon = null; + /** The Decoy of the Player */ + private Decoy _decoy = null; + /** The Trap of the Player */ + private Trap _trap = null; + /** The Agathion of the Player */ + private int _agathionId = 0; + + // Apparently, a Player CAN have both a summon AND a tamed beast at the same time!! + // After Freya players can control more than one tamed beast. + private Collection _tamedBeast = null; + + private boolean _minimapAllowed = false; + + // client radar + // TODO: This needs to be better integrated and saved/loaded + private final Radar _radar; + + private ScheduledFuture _taskWarnUserTakeBreak; + + // Party matching + // private int _partymatching = 0; + private int _partyroom = 0; + // private int _partywait = 0; + + // Clan related attributes + /** The Clan Identifier of the Player */ + private int _clanId; + + /** The Clan object of the Player */ + private Clan _clan; + + /** Apprentice and Sponsor IDs */ + private int _apprentice = 0; + private int _sponsor = 0; + + private long _clanJoinExpiryTime; + private long _clanCreateExpiryTime; + + private int _powerGrade = 0; + private ClanPrivileges _clanPrivileges = new ClanPrivileges(); + + /** Player's pledge class (knight, Baron, etc.) */ + private int _pledgeClass = 0; + private int _pledgeType = 0; + + /** Level at which the player joined the clan as an academy member */ + private int _lvlJoinedAcademy = 0; + + private int _wantsPeace = 0; + + // Death Penalty Buff Level + private int _deathPenaltyBuffLevel = 0; + + // charges + private final AtomicInteger _charges = new AtomicInteger(); + private ScheduledFuture _chargeTask = null; + + // WorldPosition used by TARGET_SIGNET_GROUND + private Location _currentSkillWorldPosition; + + private AccessLevel _accessLevel; + + private boolean _messageRefusal = false; // message refusal mode + + private boolean _silenceMode = false; // silence mode + private List _silenceModeExcluded; // silence mode + private boolean _dietMode = false; // ignore weight penalty + private boolean _tradeRefusal = false; // Trade refusal + private boolean _exchangeRefusal = false; // Exchange refusal + + private Party _party; + PartyDistributionType _partyDistributionType; + + // This is needed to find the inviting player for Party response. + // There can only be one active party request at once. + private Player _activeRequester; + private long _requestExpireTime = 0; + private final RequestPartner _request = new RequestPartner(this); + private Item _arrowItem; + + // Used for protection after teleport. + private long _spawnProtectEndTime = 0; + private long _teleportProtectEndTime = 0; + + // Protects a char from aggro mobs when getting up from fake death. + private long _recentFakeDeathEndTime = 0; + private boolean _isFakeDeath; + + /** The fists Weapon of the Player (used when no weapon is equipped) */ + private Weapon _fistsWeaponItem; + + private final Map _chars = new LinkedHashMap<>(); + + // private byte _updateKnownCounter = 0; + + private int _expertiseArmorPenalty = 0; + private int _expertiseWeaponPenalty = 0; + private int _expertisePenaltyBonus = 0; + + private boolean _isEnchanting = false; + private int _activeEnchantItemId = ID_NONE; + private long _activeEnchantTimestamp = 0; + + protected boolean _inventoryDisable = false; + /** Player's cubics. */ + private final Map _cubics = new ConcurrentSkipListMap<>(); + private final ConcurrentLinkedDeque _cubicOrder = new ConcurrentLinkedDeque<>(); + /** Active shots. */ + protected Set _activeSoulShots = ConcurrentHashMap.newKeySet(1); + + public ReentrantLock soulShotLock = new ReentrantLock(); + + /** Event parameters */ + private boolean _isRegisteredOnEvent = false; + private boolean _isOnSoloEvent = false; + private boolean _isOnEvent = false; + + /** new loto ticket **/ + private final int[] _loto = new int[5]; + /** New race ticket. */ + private final int[] _raceTickets = new int[2]; + + private final BlockList _blockList = new BlockList(this); + + private Fish _fish; + private Item _lure; + private Fishing _fishCombat; + private boolean _fishing = false; + private int _fishX = 0; + private int _fishY = 0; + private int _fishZ = 0; + + private ScheduledFuture _taskRentPet; + private ScheduledFuture _taskWater; + + /** Last Html Npcs, 0 = last html was not bound to an npc */ + private final int[] _htmlActionOriginObjectIds = new int[HtmlActionScope.values().length]; + /** + * Origin of the last incoming html action request.
    + * This can be used for htmls continuing the conversation with an npc. + */ + private int _lastHtmlActionOriginObjId; + + /** Bypass validations */ + @SuppressWarnings("unchecked") + private final List[] _htmlActionCaches = new List[HtmlActionScope.values().length]; + + private Forum _forumMail; + private Forum _forumMemo; + + /** Current skill in use. Note that Creature has _lastSkillCast, but this has the button presses */ + private SkillUseHolder _currentSkill; + private SkillUseHolder _currentPetSkill; + + /** Skills queued because a skill is already in progress */ + private SkillUseHolder _queuedSkill; + + private int _cursedWeaponEquippedId = 0; + + private boolean _canRevive = true; + private int _reviveRequested = 0; + private double _revivePower = 0; + private boolean _revivePet = false; + + private double _cpUpdateIncCheck = .0; + private double _cpUpdateDecCheck = .0; + private double _cpUpdateInterval = .0; + private double _mpUpdateIncCheck = .0; + private double _mpUpdateDecCheck = .0; + private double _mpUpdateInterval = .0; + + private double _originalCp = .0; + private double _originalHp = .0; + private double _originalMp = .0; + + /** Char Coords from Client */ + private int _clientX; + private int _clientY; + private int _clientZ; + private int _clientHeading; + + // During fall validations will be disabled for 1000 ms. + private static final int FALLING_VALIDATION_DELAY = 1000; + private volatile long _fallingTimestamp = 0; + private volatile int _fallingDamage = 0; + private Future _fallingDamageTask = null; + + private int _multiSocialTarget = 0; + private int _multiSociaAction = 0; + + private String _adminConfirmCmd = null; + + private volatile long _lastItemAuctionInfoRequest = 0; + + private long _pvpFlagLasts; + + private long _notMoveUntil = 0; + + /** Map containing all custom skills of this player. */ + private Map _customSkills = null; + + private volatile int _actionMask; + + private Map _servitorShare; + + /** + * Creates a player. + * @param objectId the object ID + * @param template the player template + * @param accountName the account name + * @param app the player appearance + */ + private Player(int objectId, PlayerTemplate template, String accountName, PlayerAppearance app) + { + super(objectId, template); + setInstanceType(InstanceType.Player); + initCharStatusUpdateValues(); + initPcStatusUpdateValues(); + + for (int i = 0; i < _htmlActionCaches.length; ++i) + { + _htmlActionCaches[i] = new ArrayList<>(); + } + + _accountName = accountName; + app.setOwner(this); + _appearance = app; + + // Create an AI. + getAI(); + + // Create a Radar object. + _radar = new Radar(this); + startVitalityTask(); + } + + /** + * Creates a player. + * @param template the player template + * @param accountName the account name + * @param app the player appearance + */ + private Player(PlayerTemplate template, String accountName, PlayerAppearance app) + { + this(IdManager.getInstance().getNextId(), template, accountName, app); + } + + public void setPvpFlagLasts(long time) + { + _pvpFlagLasts = time; + } + + public long getPvpFlagLasts() + { + return _pvpFlagLasts; + } + + public void startPvPFlag() + { + updatePvPFlag(1); + PvpFlagTaskManager.getInstance().add(this); + } + + public void stopPvpRegTask() + { + PvpFlagTaskManager.getInstance().remove(this); + } + + public void stopPvPFlag() + { + stopPvpRegTask(); + updatePvPFlag(0); + } + + // L2JMOD Wedding + private boolean _married = false; + private int _partnerId = 0; + private int _coupleId = 0; + private boolean _engagerequest = false; + private int _engageid = 0; + private boolean _marryrequest = false; + private boolean _marryaccepted = false; + + // Item Mall + private static final String GAME_POINTS_VAR = "PRIME_POINTS"; // Keep compatibility with later clients. + + // Save responder name for log it. + private String _lastPetitionGmName = null; + + private boolean _hasCharmOfCourage = false; + + private final AutoPlaySettingsHolder _autoPlaySettings = new AutoPlaySettingsHolder(); + private final AutoUseSettingsHolder _autoUseSettings = new AutoUseSettingsHolder(); + private final AtomicBoolean _autoPlaying = new AtomicBoolean(); + + private final List _questTimers = new ArrayList<>(); + private final List> _timerHolders = new ArrayList<>(); + + // Selling buffs system + private boolean _isSellingBuffs = false; + private List _sellingBuffs = null; + + public boolean isSellingBuffs() + { + return _isSellingBuffs; + } + + public void setSellingBuffs(boolean value) + { + _isSellingBuffs = value; + } + + public List getSellingBuffs() + { + if (_sellingBuffs == null) + { + _sellingBuffs = new ArrayList<>(); + } + + return _sellingBuffs; + } + + /** + * Create a new Player and add it in the characters table of the database.
    + *
    + * Actions: + *
      + *
    • Create a new Player with an account name
    • + *
    • Set the name, the Hair Style, the Hair Color and the Face type of the Player
    • + *
    • Add the player in the characters table of the database
    • + *
    + * @param template The PlayerTemplate to apply to the Player + * @param accountName The name of the Player + * @param name The name of the Player + * @param app the player's appearance + * @return The Player added to the database or null + */ + public static Player create(PlayerTemplate template, String accountName, String name, PlayerAppearance app) + { + // Create a new Player with an account name. + final Player player = new Player(template, accountName, app); + + // Set the name of the Player. + player.setName(name); + + // Set Character's create time. + player.setCreateDate(Calendar.getInstance()); + + // Set the base class ID to that of the actual class ID. + player.setBaseClass(player.getPlayerClass()); + + // Set the player as a newbie if there are no other characters associated with the current account. + player.setNewbie(PlayerConfig.ALT_GAME_NEW_CHAR_ALWAYS_IS_NEWBIE || (CharInfoTable.getInstance().accountCharNumber(accountName) == 0)); + + // Add the player in the characters table of the database. + return player.createDb() ? player : null; + } + + public String getAccountName() + { + return _client == null ? _accountName : _client.getAccountName(); + } + + public String getAccountNamePlayer() + { + return _accountName; + } + + public Map getAccountChars() + { + return _chars; + } + + public int getRelation(Player target) + { + int result = 0; + + // Karma and pvp may not be required. + if (getPvpFlag() != 0) + { + result |= RelationChanged.RELATION_PVP_FLAG; + } + + if (getKarma() > 0) + { + result |= RelationChanged.RELATION_HAS_KARMA; + } + + if (isClanLeader()) + { + result |= RelationChanged.RELATION_LEADER; + } + + if (getSiegeState() != 0) + { + result |= RelationChanged.RELATION_INSIEGE; + if (getSiegeState() != target.getSiegeState()) + { + result |= RelationChanged.RELATION_ENEMY; + } + else + { + result |= RelationChanged.RELATION_ALLY; + } + + if (getSiegeState() == 1) + { + result |= RelationChanged.RELATION_ATTACKER; + } + } + + if ((getClan() != null) && (target.getClan() != null) && (target.getPledgeType() != Clan.SUBUNIT_ACADEMY) && (getPledgeType() != Clan.SUBUNIT_ACADEMY) && target.getClan().isAtWarWith(getClan().getId())) + { + result |= RelationChanged.RELATION_1SIDED_WAR; + if (getClan().isAtWarWith(target.getClan().getId())) + { + result |= RelationChanged.RELATION_MUTUAL_WAR; + } + } + + return result; + } + + /** + * Retrieve a Player from the characters table of the database and add it in _allObjects of the L2world (call restore method).
    + *
    + * Actions: + *
      + *
    • Retrieve the Player from the characters table of the database
    • + *
    • Add the Player object in _allObjects
    • + *
    • Set the x,y,z position of the Player and make it invisible
    • + *
    • Update the overloaded status of the Player
    • + *
    + * @param objectId Identifier of the object to initialized + * @return The Player loaded from the database + */ + public static Player load(int objectId) + { + return restore(objectId); + } + + private void initPcStatusUpdateValues() + { + _cpUpdateInterval = getMaxCp() / 352.0; + _cpUpdateIncCheck = getMaxCp(); + _cpUpdateDecCheck = getMaxCp() - _cpUpdateInterval; + _mpUpdateInterval = getMaxMp() / 352.0; + _mpUpdateIncCheck = getMaxMp(); + _mpUpdateDecCheck = getMaxMp() - _mpUpdateInterval; + } + + @Override + public PlayerStat getStat() + { + return (PlayerStat) super.getStat(); + } + + @Override + public void initCharStat() + { + setStat(new PlayerStat(this)); + } + + @Override + public PlayerStatus getStatus() + { + return (PlayerStatus) super.getStatus(); + } + + @Override + public void initCharStatus() + { + setStatus(new PlayerStatus(this)); + } + + public PlayerAppearance getAppearance() + { + return _appearance; + } + + /** + * @return the base PlayerTemplate link to the Player. + */ + public PlayerTemplate getBaseTemplate() + { + return PlayerTemplateData.getInstance().getTemplate(_baseClass); + } + + /** + * @return the PlayerTemplate link to the Player. + */ + @Override + public PlayerTemplate getTemplate() + { + return (PlayerTemplate) super.getTemplate(); + } + + /** + * @param newclass + */ + public void setTemplate(PlayerClass newclass) + { + super.setTemplate(PlayerTemplateData.getInstance().getTemplate(newclass)); + } + + @Override + protected CreatureAI initAI() + { + return new PlayerAI(this); + } + + /** Return the Level of the Player. */ + @Override + public int getLevel() + { + return getStat().getLevel(); + } + + /** + * Return the _newbie state of the Player. + * @return true, if is newbie. + */ + public boolean isNewbie() + { + return PlayerConfig.ALT_GAME_NEW_CHAR_ALWAYS_IS_NEWBIE || _newbie; + } + + /** + * Set the _newbie state of the Player. + * @param isNewbie The Identifier of the _newbie state. + */ + public void setNewbie(boolean isNewbie) + { + _newbie = isNewbie; + } + + public void setBaseClass(int baseClass) + { + _baseClass = baseClass; + } + + public void setBaseClass(PlayerClass playerClass) + { + _baseClass = playerClass.getId(); + } + + public boolean isInStoreMode() + { + return _privateStoreType != PrivateStoreType.NONE; + } + + public boolean isCrafting() + { + return _isCrafting; + } + + public void setCrafting(boolean isCrafting) + { + _isCrafting = isCrafting; + } + + /** + * @return a table containing all Common RecipeList of the Player. + */ + public Collection getCommonRecipeBook() + { + return _commonRecipeBook.values(); + } + + /** + * @return a table containing all Dwarf RecipeList of the Player. + */ + public Collection getDwarvenRecipeBook() + { + return _dwarvenRecipeBook.values(); + } + + /** + * Add a new RecipList to the table _commonrecipebook containing all RecipeList of the Player + * @param recipe The RecipeList to add to the _recipebook + * @param saveToDb + */ + public void registerCommonRecipeList(RecipeList recipe, boolean saveToDb) + { + _commonRecipeBook.put(recipe.getId(), recipe); + + if (saveToDb) + { + insertNewRecipeData(recipe.getId(), false); + } + } + + /** + * Add a new RecipList to the table _recipebook containing all RecipeList of the Player + * @param recipe The RecipeList to add to the _recipebook + * @param saveToDb + */ + public void registerDwarvenRecipeList(RecipeList recipe, boolean saveToDb) + { + _dwarvenRecipeBook.put(recipe.getId(), recipe); + + if (saveToDb) + { + insertNewRecipeData(recipe.getId(), true); + } + } + + /** + * @param recipeId The Identifier of the RecipeList to check in the player's recipe books + * @return {@code true}if player has the recipe on Common or Dwarven Recipe book else returns {@code false} + */ + public boolean hasRecipeList(int recipeId) + { + return _dwarvenRecipeBook.containsKey(recipeId) || _commonRecipeBook.containsKey(recipeId); + } + + /** + * Tries to remove a RecipList from the table _DwarvenRecipeBook or from table _CommonRecipeBook, those table contain all RecipeList of the Player + * @param recipeId The Identifier of the RecipeList to remove from the _recipebook + */ + public void unregisterRecipeList(int recipeId) + { + if (_dwarvenRecipeBook.remove(recipeId) != null) + { + deleteRecipeData(recipeId, true); + } + else if (_commonRecipeBook.remove(recipeId) != null) + { + deleteRecipeData(recipeId, false); + } + else + { + LOGGER.warning("Attempted to remove unknown RecipeList: " + recipeId); + } + + for (Shortcut sc : _shortcuts.getAllShortcuts()) + { + if ((sc != null) && (sc.getId() == recipeId) && (sc.getType() == ShortcutType.RECIPE)) + { + deleteShortcut(sc.getSlot(), sc.getPage()); + } + } + } + + private void insertNewRecipeData(int recipeId, boolean isDwarf) + { + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement("INSERT INTO character_recipebook (charId, id, classIndex, type) values(?,?,?,?)")) + { + ps.setInt(1, getObjectId()); + ps.setInt(2, recipeId); + ps.setInt(3, isDwarf ? _classIndex : 0); + ps.setInt(4, isDwarf ? 1 : 0); + ps.execute(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "SQL exception while inserting recipe: " + recipeId + " from character " + getObjectId(), e); + } + } + + private void deleteRecipeData(int recipeId, boolean isDwarf) + { + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement("DELETE FROM character_recipebook WHERE charId=? AND id=? AND classIndex=?")) + { + ps.setInt(1, getObjectId()); + ps.setInt(2, recipeId); + ps.setInt(3, isDwarf ? _classIndex : 0); + ps.execute(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "SQL exception while deleting recipe: " + recipeId + " from character " + getObjectId(), e); + } + } + + /** + * @return the Id for the last talked quest NPC. + */ + public int getLastQuestNpcObject() + { + return _questNpcObject; + } + + public void setLastQuestNpcObject(int npcId) + { + _questNpcObject = npcId; + } + + /** + * @param quest The name of the quest + * @return the QuestState object corresponding to the quest name. + */ + public QuestState getQuestState(String quest) + { + return _quests.get(quest); + } + + /** + * Add a QuestState to the table _quest containing all quests began by the Player. + * @param qs The QuestState to add to _quest + */ + public void setQuestState(QuestState qs) + { + _quests.put(qs.getQuestName(), qs); + } + + /** + * Verify if the player has the quest state. + * @param quest the quest state to check + * @return {@code true} if the player has the quest state, {@code false} otherwise + */ + public boolean hasQuestState(String quest) + { + return _quests.containsKey(quest); + } + + /** + * Remove a QuestState from the table _quest containing all quests began by the Player. + * @param quest The name of the quest + */ + public void delQuestState(String quest) + { + _quests.remove(quest); + } + + /** + * Gets all the active quests. + * @return a list of active quests + */ + public List getAllActiveQuests() + { + final List quests = new ArrayList<>(); + for (QuestState qs : _quests.values()) + { + if ((qs == null) || (qs.getQuest() == null) || !qs.isStarted()) + { + continue; + } + + // Ignore other scripts. + final int questId = qs.getQuest().getId(); + if ((questId > 19999) || (questId < 1)) + { + continue; + } + + quests.add(qs.getQuest()); + } + + return quests; + } + + public void processScriptEvent(String scriptName, String event) + { + final Quest quest = ScriptManager.getInstance().getScript(scriptName); + if ((quest == null) || (event == null) || event.isEmpty()) + { + return; + } + + final Npc target = _lastFolkNpc; + if ((target != null) && isInsideRadius2D(target, Npc.INTERACTION_DISTANCE)) + { + quest.notifyEvent(event, target, this); + } + else if (_questNpcObject > 0) + { + final WorldObject object = World.findObject(getLastQuestNpcObject()); + if ((object != null) && object.isNpc() && isInsideRadius2D(object, Npc.INTERACTION_DISTANCE)) + { + final Npc npc = object.asNpc(); + quest.notifyEvent(event, npc, this); + } + } + } + + /** List of all QuestState instance that needs to be notified of this Player's or its pet's death */ + private final Collection _notifyQuestOfDeathList = ConcurrentHashMap.newKeySet(); + + /** + * Add QuestState instance that is to be notified of Player's death. + * @param qs The QuestState that subscribe to this event + */ + public void addNotifyQuestOfDeath(QuestState qs) + { + if (qs == null) + { + return; + } + + if (!_notifyQuestOfDeathList.contains(qs)) + { + _notifyQuestOfDeathList.add(qs); + } + } + + /** + * Remove QuestState instance that is to be notified of Player's death. + * @param qs The QuestState that subscribe to this event + */ + public void removeNotifyQuestOfDeath(QuestState qs) + { + if (qs == null) + { + return; + } + + _notifyQuestOfDeathList.remove(qs); + } + + /** + * @return a list of QuestStates which registered for notify of death of this Player. + */ + public Collection getNotifyQuestOfDeath() + { + return _notifyQuestOfDeathList; + } + + public boolean isNotifyQuestOfDeathEmpty() + { + return _notifyQuestOfDeathList.isEmpty(); + } + + /** + * Restores all player shortcuts. + */ + private void restoreShortcuts() + { + _shortcuts.restoreMe(); + } + + /** + * @return a collection containing all Shortcut of the Player. + */ + public Collection getAllShortcuts() + { + return _shortcuts.getAllShortcuts(); + } + + /** + * @param slot The slot in which the shortcuts is equipped + * @param page The page of shortcuts containing the slot + * @return the Shortcut of the Player corresponding to the position (page-slot). + */ + public Shortcut getShortcut(int slot, int page) + { + return _shortcuts.getShortcut(slot, page); + } + + /** + * Add a Shortcut to the Player _shortcuts. + * @param shortcut + */ + public void registerShortcut(Shortcut shortcut) + { + _shortcuts.registerShortcut(shortcut); + } + + /** + * Updates the shortcut bars with the new skill. + * @param skillId the skill Id to search and update. + * @param skillLevel the skill level to update. + */ + public void updateShortcuts(int skillId, int skillLevel) + { + _shortcuts.updateShortcuts(skillId, skillLevel); + } + + /** + * Delete the Shortcut corresponding to the position (page-slot) from the Player _shortcuts. + * @param slot + * @param page + */ + public void deleteShortcut(int slot, int page) + { + _shortcuts.deleteShortcut(slot, page); + } + + /** + * @param macro the macro to add to this Player. + */ + public void registerMacro(Macro macro) + { + _macros.registerMacro(macro); + } + + /** + * @param id the macro Id to delete. + */ + public void deleteMacro(int id) + { + _macros.deleteMacro(id); + } + + /** + * @return all Macro of the Player. + */ + public MacroList getMacros() + { + return _macros; + } + + /** + * Set the siege state of the Player. + * @param siegeState 1 = attacker, 2 = defender, 0 = not involved + */ + public void setSiegeState(byte siegeState) + { + _siegeState = siegeState; + } + + /** + * Get the siege state of the Player. + * @return 1 = attacker, 2 = defender, 0 = not involved + */ + @Override + public byte getSiegeState() + { + return _siegeState; + } + + /** + * Set the siege Side of the Player. + * @param value + */ + public void setSiegeSide(int value) + { + _siegeSide = value; + } + + public boolean isRegisteredOnThisSiegeField(int value) + { + return (_siegeSide == value) || ((_siegeSide >= 81) && (_siegeSide <= 89)); + } + + @Override + public int getSiegeSide() + { + return _siegeSide; + } + + public boolean isSiegeFriend(WorldObject target) + { + // If i'm natural or not in siege zone, not friends. + if ((_siegeState == 0) || !isInsideZone(ZoneId.SIEGE)) + { + return false; + } + + final Castle castle = CastleManager.getInstance().getCastleById(_siegeSide); + if (castle == null) + { + return false; + } + + // If target isn't a player, is self. + final Player targetPlayer = target.asPlayer(); + if ((targetPlayer == null) || (targetPlayer == this)) + { + return false; + } + + // If target isn't on same siege or not on same state, not friends. + if ((targetPlayer.getSiegeSide() != _siegeSide) || (_siegeState != targetPlayer.getSiegeState())) + { + return false; + } + + if (_siegeState == 1) + { + // Check first castle mid victory. + if (!castle.isFirstMidVictory() && (_siegeState == targetPlayer.getSiegeState())) + { + return true; + } + + // Attackers are considered friends only if castle has no owner. + return castle.getOwner() == null; + } + + // Both are defenders, friends. + return true; + } + + /** + * Set the PvP Flag of the Player. + * @param pvpFlag + */ + public void setPvpFlag(int pvpFlag) + { + _pvpFlag = (byte) pvpFlag; + } + + @Override + public byte getPvpFlag() + { + return _pvpFlag; + } + + @Override + public void updatePvPFlag(int value) + { + if (_pvpFlag == value) + { + return; + } + + setPvpFlag(value); + + updateUserInfo(); + + // If this player has a pet update the pets pvp flag as well. + if (hasSummon()) + { + sendPacket(new RelationChanged(_summon, getRelation(this), false)); + } + + World.forEachVisibleObject(this, Player.class, target -> + { + target.sendPacket(new RelationChanged(this, getRelation(target), isAutoAttackable(target))); + if (hasSummon()) + { + target.sendPacket(new RelationChanged(_summon, getRelation(target), isAutoAttackable(target))); + } + }); + } + + @Override + public void revalidateZone(boolean force) + { + // Cannot validate if not in a world region (happens during teleport). + if (getWorldRegion() == null) + { + return; + } + + // This function is called too often from movement code. + if (!force && (calculateDistance3D(_lastZoneValidateLocation) < 100)) + { + return; + } + + _lastZoneValidateLocation.setXYZ(this); + + ZoneManager.getInstance().getRegion(this).revalidateZones(this); + + if (GeneralConfig.ALLOW_WATER) + { + checkWaterState(); + } + + if (isInsideZone(ZoneId.ALTERED)) + { + if (_lastCompassZone == ExSetCompassZoneCode.ALTEREDZONE) + { + return; + } + + _lastCompassZone = ExSetCompassZoneCode.ALTEREDZONE; + sendPacket(new ExSetCompassZoneCode(ExSetCompassZoneCode.ALTEREDZONE)); + } + else if (isInsideZone(ZoneId.SIEGE)) + { + if (_lastCompassZone == ExSetCompassZoneCode.SIEGEWARZONE2) + { + return; + } + + _lastCompassZone = ExSetCompassZoneCode.SIEGEWARZONE2; + sendPacket(new ExSetCompassZoneCode(ExSetCompassZoneCode.SIEGEWARZONE2)); + } + else if (isInsideZone(ZoneId.PVP)) + { + if (_lastCompassZone == ExSetCompassZoneCode.PVPZONE) + { + return; + } + + _lastCompassZone = ExSetCompassZoneCode.PVPZONE; + sendPacket(new ExSetCompassZoneCode(ExSetCompassZoneCode.PVPZONE)); + } + else if (_isIn7sDungeon) + { + if (_lastCompassZone == ExSetCompassZoneCode.SEVENSIGNSZONE) + { + return; + } + + _lastCompassZone = ExSetCompassZoneCode.SEVENSIGNSZONE; + sendPacket(new ExSetCompassZoneCode(ExSetCompassZoneCode.SEVENSIGNSZONE)); + } + else if (isInsideZone(ZoneId.PEACE)) + { + if (_lastCompassZone == ExSetCompassZoneCode.PEACEZONE) + { + return; + } + + _lastCompassZone = ExSetCompassZoneCode.PEACEZONE; + sendPacket(new ExSetCompassZoneCode(ExSetCompassZoneCode.PEACEZONE)); + } + else if (isInsideZone(ZoneId.NO_PVP)) + { + if (_lastCompassZone == ExSetCompassZoneCode.PEACEZONE) + { + return; + } + + _lastCompassZone = ExSetCompassZoneCode.PEACEZONE; + sendPacket(new ExSetCompassZoneCode(ExSetCompassZoneCode.PEACEZONE)); + } + else + { + if (_lastCompassZone == ExSetCompassZoneCode.GENERALZONE) + { + return; + } + + if (_lastCompassZone == ExSetCompassZoneCode.SIEGEWARZONE2) + { + updatePvPStatus(); + } + + _lastCompassZone = ExSetCompassZoneCode.GENERALZONE; + sendPacket(new ExSetCompassZoneCode(ExSetCompassZoneCode.GENERALZONE)); + } + } + + /** + * @return True if the Player can Craft Dwarven Recipes. + */ + public boolean hasDwarvenCraft() + { + return getSkillLevel(CommonSkill.CREATE_DWARVEN.getId()) >= 1; + } + + public int getDwarvenCraft() + { + return getSkillLevel(CommonSkill.CREATE_DWARVEN.getId()); + } + + /** + * @return True if the Player can Craft Dwarven Recipes. + */ + public boolean hasCommonCraft() + { + return getSkillLevel(CommonSkill.CREATE_COMMON.getId()) >= 1; + } + + public int getCommonCraft() + { + return getSkillLevel(CommonSkill.CREATE_COMMON.getId()); + } + + /** + * @return the PK counter of the Player. + */ + public int getPkKills() + { + return _pkKills; + } + + /** + * Set the PK counter of the Player. + * @param pkKills + */ + public void setPkKills(int pkKills) + { + if (EventDispatcher.getInstance().hasListener(EventType.ON_PLAYER_PK_CHANGED, this)) + { + EventDispatcher.getInstance().notifyEventAsync(new OnPlayerPKChanged(this, _pkKills, pkKills), this); + } + + _pkKills = pkKills; + } + + /** + * @return the _deleteTimer of the Player. + */ + public long getDeleteTimer() + { + return _deleteTimer; + } + + /** + * Set the _deleteTimer of the Player. + * @param deleteTimer + */ + public void setDeleteTimer(long deleteTimer) + { + _deleteTimer = deleteTimer; + } + + /** + * @return the date of last update of recomPoints. + */ + public long getLastRecomUpdate() + { + return _lastRecomUpdate; + } + + public void setLastRecomUpdate(long date) + { + _lastRecomUpdate = date; + } + + /** + * @return the number of recommendation obtained by the Player. + */ + public int getRecomHave() + { + return _recomHave; + } + + /** + * Increment the number of recommendation obtained by the Player (Max : 255). + */ + protected void incRecomHave() + { + if (_recomHave < 255) + { + _recomHave++; + } + } + + /** + * Set the number of recommendation obtained by the Player (Max : 255). + * @param value + */ + public void setRecomHave(int value) + { + _recomHave = Math.clamp(value, 0, 255); + } + + /** + * @return the number of recommendation that the Player can give. + */ + public int getRecomLeft() + { + return _recomLeft; + } + + /** + * Increment the number of recommendation that the Player can give. + */ + protected void decRecomLeft() + { + if (_recomLeft > 0) + { + _recomLeft--; + } + } + + public void giveRecom(Player target) + { + target.incRecomHave(); + decRecomLeft(); + _recomChars.add(target.getObjectId()); + if (!PlayerConfig.ALT_RECOMMEND) + { + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement(ADD_CHAR_RECOM)) + { + ps.setInt(1, getObjectId()); + ps.setInt(2, target.getObjectId()); + ps.execute(); + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "Failed updating character recommendations for player: " + getObjectId(), e); + } + } + } + + public boolean canRecom(Player target) + { + return !_recomChars.contains(target.getObjectId()); + } + + /** + * Retrieve from the database all Recommendation data of this player, add to _recomChars and calculate stats of the player. + */ + private void restoreRecom() + { + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement(RESTORE_CHAR_RECOMS)) + { + ps.setInt(1, getObjectId()); + final ResultSet rset = ps.executeQuery(); + while (rset.next()) + { + _recomChars.add(rset.getInt("target_id")); + } + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "Could not restore Recommendations for player: " + getObjectId(), e); + } + } + + private void checkRecom(int recsHave, int recsLeft) + { + final Calendar check = Calendar.getInstance(); + check.setTimeInMillis(_lastRecomUpdate); + check.add(Calendar.DAY_OF_MONTH, 1); + + final Calendar min = Calendar.getInstance(); + _recomHave = recsHave; + _recomLeft = recsLeft; + if ((getStat().getLevel() < 10) || check.after(min)) + { + return; + } + + restartRecom(); + } + + public void restartRecom() + { + _recomChars.clear(); + + if (!PlayerConfig.ALT_RECOMMEND) + { + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement(DELETE_CHAR_RECOMS)) + { + ps.setInt(1, getObjectId()); + ps.execute(); + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "Failed cleaning character recommendations for player: " + getObjectId(), e); + } + } + + if (getStat().getLevel() < 20) + { + _recomLeft = 3; + _recomHave--; + } + else if (getStat().getLevel() < 40) + { + _recomLeft = 6; + _recomHave -= 2; + } + else + { + _recomLeft = 9; + _recomHave -= 3; + } + + if (_recomHave < 0) + { + _recomHave = 0; + } + + // If we have to update last update time, but it's now before 13, we should set it to yesterday. + final Calendar update = Calendar.getInstance(); + if (update.get(Calendar.HOUR_OF_DAY) < 13) + { + update.add(Calendar.DAY_OF_MONTH, -1); + } + + update.set(Calendar.HOUR_OF_DAY, 13); + _lastRecomUpdate = update.getTimeInMillis(); + } + + /** + * Set the exp of the Player before a death + * @param exp + */ + public void setExpBeforeDeath(long exp) + { + _expBeforeDeath = exp; + } + + public long getExpBeforeDeath() + { + return _expBeforeDeath; + } + + /** + * Set the Karma of the Player and send a Server->Client packet StatusUpdate (broadcast). + * @param value + */ + @Override + public void setKarma(int value) + { + // Notify to scripts. + if (EventDispatcher.getInstance().hasListener(EventType.ON_PLAYER_KARMA_CHANGED, this)) + { + EventDispatcher.getInstance().notifyEventAsync(new OnPlayerKarmaChanged(this, getKarma(), value), this); + } + + int karma = value; + if (karma < 0) + { + karma = 0; + } + + if ((getKarma() == 0) && (karma > 0)) + { + World.forEachVisibleObject(this, Guard.class, object -> + { + if (object.getAI().getIntention() == Intention.IDLE) + { + object.getAI().setIntentionActive(); + } + }); + } + else if ((getKarma() > 0) && (karma == 0)) + { + // Send a Server->Client StatusUpdate packet with Karma and PvP Flag to the Player and all Player to inform (broadcast). + setKarmaFlag(); + } + + super.setKarma(karma); + broadcastKarma(); + } + + public int getExpertiseArmorPenalty() + { + return _expertiseArmorPenalty; + } + + public int getExpertiseWeaponPenalty() + { + return _expertiseWeaponPenalty; + } + + public int getExpertisePenaltyBonus() + { + return _expertisePenaltyBonus; + } + + public void setExpertisePenaltyBonus(int bonus) + { + _expertisePenaltyBonus = bonus; + } + + public int getWeightPenalty() + { + return _dietMode ? 0 : _curWeightPenalty; + } + + /** + * Update the overloaded status of the Player. + */ + public void refreshOverloaded() + { + final int maxLoad = getMaxLoad(); + if (maxLoad > 0) + { + final long weightproc = ((getCurrentLoad() - getBonusWeightPenalty()) * 1000) / getMaxLoad(); + int newWeightPenalty; + if ((weightproc < 500) || _dietMode) + { + newWeightPenalty = 0; + } + else if (weightproc < 666) + { + newWeightPenalty = 1; + } + else if (weightproc < 800) + { + newWeightPenalty = 2; + } + else if (weightproc < 1000) + { + newWeightPenalty = 3; + } + else + { + newWeightPenalty = 4; + } + + if (_curWeightPenalty != newWeightPenalty) + { + _curWeightPenalty = newWeightPenalty; + if ((newWeightPenalty > 0) && !_dietMode) + { + addSkill(SkillData.getInstance().getSkill(4270, newWeightPenalty)); + setOverloaded(getCurrentLoad() > maxLoad); + } + else + { + removeSkill(getKnownSkill(4270), false, true); + setOverloaded(false); + } + + broadcastUserInfo(); + sendPacket(new EtcStatusUpdate(this)); + } + } + } + + public void refreshExpertisePenalty() + { + if (!PlayerConfig.EXPERTISE_PENALTY) + { + return; + } + + final int expertiseLevel = getExpertiseLevel(); + int armorPenalty = 0; + int weaponPenalty = 0; + int crystaltype; + for (Item item : _inventory.getItems()) + { + if ((item != null) && item.isEquipped() && (item.getItemType() != EtcItemType.ARROW)) + { + crystaltype = item.getTemplate().getCrystalType().getLevel(); + if (crystaltype > expertiseLevel) + { + if (item.isWeapon() && (crystaltype > weaponPenalty)) + { + weaponPenalty = crystaltype; + } + else if (crystaltype > armorPenalty) + { + armorPenalty = crystaltype; + } + } + } + } + + // calc weapon penalty + weaponPenalty = weaponPenalty - expertiseLevel - _expertisePenaltyBonus; + weaponPenalty = Math.clamp(weaponPenalty, 0, 4); + + // calc armor penalty + armorPenalty = armorPenalty - expertiseLevel - _expertisePenaltyBonus; + armorPenalty = Math.clamp(armorPenalty, 0, 4); + + if ((_expertiseWeaponPenalty != weaponPenalty) || (_expertiseArmorPenalty != armorPenalty)) + { + _expertiseWeaponPenalty = weaponPenalty; + _expertiseArmorPenalty = armorPenalty; + if ((_expertiseWeaponPenalty > 0) || (_expertiseArmorPenalty > 0)) + { + addSkill(SkillData.getInstance().getSkill(4267, 1), false); + } + else + { + removeSkill(getKnownSkill(CommonSkill.GRADE_PENALTY.getId()), false, true); + } + + sendPacket(new EtcStatusUpdate(this)); + } + } + + public void useEquippableItem(Item item, boolean abortAttack) + { + // Check if the item is null. + if (item == null) + { + return; + } + + // Check if the item is owned by this player. + if (item.getOwnerId() != getObjectId()) + { + return; + } + + // Check if the item is in the inventory. + final ItemLocation itemLocation = item.getItemLocation(); + if ((itemLocation != ItemLocation.INVENTORY) && (itemLocation != ItemLocation.PAPERDOLL)) + { + return; + } + + // Equip or unEquip. + Collection items = null; + final boolean isEquiped = item.isEquipped(); + final int oldInvLimit = getInventoryLimit(); + SystemMessage sm = null; + if (isEquiped) + { + if (item.isEnchanted()) + { + sm = new SystemMessage(SystemMessageId.THE_EQUIPMENT_S1_S2_HAS_BEEN_REMOVED); + sm.addInt(item.getEnchantLevel()); + } + else + { + sm = new SystemMessage(SystemMessageId.S1_HAS_BEEN_DISARMED); + } + + sm.addItemName(item); + sendPacket(sm); + + final BodyPart bodyPart = BodyPart.fromItem(item); + items = _inventory.unEquipItemInBodySlotAndRecord(bodyPart); + } + else + { + items = _inventory.equipItemAndRecord(item); + if (item.isEquipped()) + { + final BodyPart bodyPart = item.getTemplate().getBodyPart(); + if (item.isEnchanted()) + { + sm = new SystemMessage(SystemMessageId.EQUIPPED_S1_S2); + sm.addInt(item.getEnchantLevel()); + } + else + { + sm = new SystemMessage(SystemMessageId.YOU_HAVE_EQUIPPED_YOUR_S1); + } + + sm.addItemName(item); + sendPacket(sm); + + // Consume mana - will start a task if required; returns if item is not a shadow item. + item.decreaseMana(false); + + if ((bodyPart == BodyPart.R_HAND) || (bodyPart == BodyPart.LR_HAND)) + { + rechargeShots(true, true); + } + + // Notify to scripts. + if (EventDispatcher.getInstance().hasListener(EventType.ON_PLAYER_ITEM_EQUIP, item.getTemplate())) + { + EventDispatcher.getInstance().notifyEventAsync(new OnPlayerItemEquip(this, item), item.getTemplate()); + } + } + else + { + sendPacket(SystemMessageId.YOU_DO_NOT_MEET_THE_REQUIRED_CONDITION_TO_EQUIP_THAT_ITEM); + } + } + + refreshExpertisePenalty(); + broadcastUserInfo(); + + final InventoryUpdate iu = new InventoryUpdate(); + iu.addItems(items); + sendInventoryUpdate(iu); + + if (abortAttack) + { + abortAttack(); + } + + if (getInventoryLimit() != oldInvLimit) + { + sendPacket(new ExStorageMaxCount(this)); + } + } + + /** + * @return the the PvP Kills of the Player (Number of player killed during a PvP). + */ + public int getPvpKills() + { + return _pvpKills; + } + + /** + * Set the the PvP Kills of the Player (Number of player killed during a PvP). + * @param pvpKills + */ + public void setPvpKills(int pvpKills) + { + if (EventDispatcher.getInstance().hasListener(EventType.ON_PLAYER_PVP_CHANGED, this)) + { + EventDispatcher.getInstance().notifyEventAsync(new OnPlayerPvPChanged(this, _pvpKills, pvpKills), this); + } + + _pvpKills = pvpKills; + } + + /** + * @return the Fame of this Player + */ + public int getFame() + { + return _fame; + } + + /** + * Set the Fame of this PlayerInstane + * @param fame + */ + public void setFame(int fame) + { + int newFame = fame; + if (fame > PlayerConfig.MAX_PERSONAL_FAME_POINTS) + { + newFame = PlayerConfig.MAX_PERSONAL_FAME_POINTS; + } + else if (fame < 0) + { + newFame = 0; + } + + if (EventDispatcher.getInstance().hasListener(EventType.ON_PLAYER_FAME_CHANGED, this)) + { + EventDispatcher.getInstance().notifyEventAsync(new OnPlayerFameChanged(this, _fame, newFame), this); + } + + _fame = newFame; + } + + /** + * @return the PlayerClass of the Player contained in PlayerTemplate. + */ + public PlayerClass getPlayerClass() + { + return getTemplate().getPlayerClass(); + } + + /** + * Set the template of the Player. + * @param id The Identifier of the PlayerTemplate to set to the Player + */ + public void setPlayerClass(int id) + { + if (_subclassLock) + { + return; + } + + _subclassLock = true; + + try + { + if ((_lvlJoinedAcademy != 0) && (_clan != null) && CategoryData.getInstance().isInCategory(CategoryType.THIRD_CLASS_GROUP, id)) + { + if (_lvlJoinedAcademy <= 16) + { + _clan.addReputationScore(FeatureConfig.JOIN_ACADEMY_MAX_REP_SCORE); + } + else if (_lvlJoinedAcademy >= 39) + { + _clan.addReputationScore(FeatureConfig.JOIN_ACADEMY_MIN_REP_SCORE); + } + else + { + _clan.addReputationScore(FeatureConfig.JOIN_ACADEMY_MAX_REP_SCORE - ((_lvlJoinedAcademy - 16) * 20)); + } + + setLvlJoinedAcademy(0); + + // Oust pledge member from the academy, cuz he has finished his 2nd class transfer. + final SystemMessage msg = new SystemMessage(SystemMessageId.CLAN_MEMBER_S1_HAS_BEEN_EXPELLED); + msg.addPcName(this); + _clan.broadcastToOnlineMembers(msg); + _clan.broadcastToOnlineMembers(new PledgeShowMemberListDelete(getName())); + _clan.removeClanMember(getObjectId(), 0); + sendPacket(SystemMessageId.CONGRATULATIONS_YOU_WILL_NOW_GRADUATE_FROM_THE_CLAN_ACADEMY_AND_LEAVE_YOUR_CURRENT_CLAN_AS_A_GRADUATE_OF_THE_ACADEMY_YOU_CAN_IMMEDIATELY_JOIN_A_CLAN_AS_A_REGULAR_MEMBER_WITHOUT_BEING_SUBJECT_TO_ANY_PENALTIES); + + // receive graduation gift + _inventory.addItem(ItemProcessType.REWARD, 8181, 1, this, null); // give academy circlet + } + + if (isSubClassActive()) + { + getSubClasses().get(_classIndex).setPlayerClass(id); + } + + setTarget(this); + broadcastSkillPacket(new MagicSkillUse(this, 5103, 1, 0, 0), this); + setClassTemplate(id); + if (getPlayerClass().level() == 3) + { + sendPacket(SystemMessageId.CONGRATULATIONS_YOU_VE_COMPLETED_THE_THIRD_CLASS_TRANSFER_QUEST); + } + else + { + sendPacket(SystemMessageId.CONGRATULATIONS_YOU_VE_COMPLETED_A_CLASS_TRANSFER); + } + + // Remove class permitted hennas. + for (int slot = 1; slot < 4; slot++) + { + final Henna henna = getHenna(slot); + if ((henna != null) && !henna.isAllowedClass(getPlayerClass())) + { + removeHenna(slot); + } + } + + // Update class icon in party and clan. + if (isInParty()) + { + _party.broadcastPacket(new PartySmallWindowUpdate(this)); + } + + if (_clan != null) + { + _clan.broadcastToOnlineMembers(new PledgeShowMemberListUpdate(this)); + } + + // Add AutoGet skills and normal skills and/or learnByFS depending on configurations. + rewardSkills(); + + if (!isGM() && PlayerConfig.DECREASE_SKILL_LEVEL) + { + checkPlayerSkills(); + } + } + finally + { + _subclassLock = false; + + ThreadPool.schedule(() -> + { + getInventory().applyItemSkills(); + sendSkillList(); + }, 100); + } + } + + public boolean isChangingClass() + { + return _subclassLock; + } + + /** + * Used for AltGameSkillLearn to set a custom skill learning class Id. + */ + private PlayerClass _learningClass = getPlayerClass(); + + /** + * @return the custom skill learning class Id. + */ + public PlayerClass getLearningClass() + { + return _learningClass; + } + + /** + * @param learningClass the custom skill learning class Id to set. + */ + public void setLearningClass(PlayerClass learningClass) + { + _learningClass = learningClass; + } + + /** + * @return the Experience of the Player. + */ + public long getExp() + { + return getStat().getExp(); + } + + public void setActiveEnchantItemId(int objectId) + { + // If we don't have a Enchant Item, we are not enchanting. + if (objectId == ID_NONE) + { + setActiveEnchantTimestamp(0); + setEnchanting(false); + } + + _activeEnchantItemId = objectId; + } + + public int getActiveEnchantItemId() + { + return _activeEnchantItemId; + } + + public long getActiveEnchantTimestamp() + { + return _activeEnchantTimestamp; + } + + public void setActiveEnchantTimestamp(long value) + { + _activeEnchantTimestamp = value; + } + + public void setEnchanting(boolean value) + { + _isEnchanting = value; + } + + public boolean isEnchanting() + { + return _isEnchanting; + } + + /** + * Set the fists weapon of the Player (used when no weapon is equiped). + * @param weaponItem The fists Weapon to set to the Player + */ + public void setFistsWeaponItem(Weapon weaponItem) + { + _fistsWeaponItem = weaponItem; + } + + /** + * @return the fists weapon of the Player (used when no weapon is equipped). + */ + public Weapon getFistsWeaponItem() + { + return _fistsWeaponItem; + } + + /** + * Returns the appropriate fist weapon template for a given class ID.
    + * Fist weapons are used as default weapons when a player has no weapon equipped. + * @param classId the class identifier for this Player + * @return the Weapon template for the fist weapon corresponding to the class ID. + */ + public Weapon findFistsWeaponItem(int classId) + { + final Weapon weaponItem; + + if ((classId >= 0x00) && (classId <= 0x09)) + { + weaponItem = (Weapon) ItemData.getInstance().getTemplate(246 /* Human Fighter Fist */); + } + else if ((classId >= 0x0a) && (classId <= 0x11)) + { + weaponItem = (Weapon) ItemData.getInstance().getTemplate(251 /* Human Mystic Fist */); + } + else if ((classId >= 0x12) && (classId <= 0x18)) + { + weaponItem = (Weapon) ItemData.getInstance().getTemplate(244 /* Elven Fighter Fist */); + } + else if ((classId >= 0x19) && (classId <= 0x1e)) + { + weaponItem = (Weapon) ItemData.getInstance().getTemplate(249 /* Elven Mystic Fist */); + } + else if ((classId >= 0x1f) && (classId <= 0x25)) + { + weaponItem = (Weapon) ItemData.getInstance().getTemplate(245 /* Dark Fighter Fist */); + } + else if ((classId >= 0x26) && (classId <= 0x2b)) + { + weaponItem = (Weapon) ItemData.getInstance().getTemplate(250 /* Dark Mystic Fist */); + } + else if ((classId >= 0x2c) && (classId <= 0x30)) + { + weaponItem = (Weapon) ItemData.getInstance().getTemplate(248 /* Orc Fighter Fist */); + } + else if ((classId >= 0x31) && (classId <= 0x34)) + { + weaponItem = (Weapon) ItemData.getInstance().getTemplate(252 /* Orc Shaman Fist */); + } + else if ((classId >= 0x35) && (classId <= 0x39)) + { + weaponItem = (Weapon) ItemData.getInstance().getTemplate(247 /* Dwarven Fighter Fist */); + } + else // Default fist item to avoid NPEs. + { + weaponItem = (Weapon) ItemData.getInstance().getTemplate(246 /* Human Fighter Fist */); + } + + return weaponItem; + } + + /** + * This method reward all AutoGet skills and Normal skills if Auto-Learn configuration is true. + */ + public void rewardSkills() + { + // Give all normal skills if activated Auto-Learn is activated, included AutoGet skills. + if (PlayerConfig.AUTO_LEARN_SKILLS) + { + giveAvailableSkills(PlayerConfig.AUTO_LEARN_FS_SKILLS, true, PlayerConfig.AUTO_LEARN_SKILLS_WITHOUT_ITEMS); + } + else + { + giveAvailableAutoGetSkills(); + } + + if (PlayerConfig.DECREASE_SKILL_LEVEL && !isGM()) + { + checkPlayerSkills(); + } + + if (FreeMountsConfig.ENABLE_FREE_STRIDER) + { + final Skill skill = SkillData.getInstance().getSkill(49991, 1); + if (skill != null) + { + addSkill(skill, false); + } + } + + if (FreeMountsConfig.ENABLE_FREE_WYVERN) + { + final Skill skill = SkillData.getInstance().getSkill(49992, 1); + if (skill != null) + { + addSkill(skill, false); + } + } + + if (RebirthConfig.REBIRTH_ALLOW_REBIRTH) + { + RebirthManager.getInstance().grantRebirthSkills(this); + } + + checkItemRestriction(); + sendSkillList(); + } + + /** + * Re-give all skills which aren't saved to database, like Noble, Hero, Clan Skills. + */ + public void regiveTemporarySkills() + { + // Do not call this on enterworld or char load. + + // Add noble skills if noble. + if (_noble) + { + setNoble(true); + } + + // Add Hero skills if hero. + if (_hero) + { + setHero(true); + } + + // Add clan skills. + if (_clan != null) + { + _clan.addSkillEffects(this); + + if ((_clan.getLevel() >= SiegeManager.getInstance().getSiegeClanMinLevel()) && isClanLeader()) + { + SiegeManager.getInstance().addSiegeSkills(this); + } + + if (_clan.getCastleId() > 0) + { + final Castle castle = CastleManager.getInstance().getCastleByOwner(_clan); + if (castle != null) + { + castle.giveResidentialSkills(this); + } + } + } + + if (RebirthConfig.REBIRTH_ALLOW_REBIRTH) + { + RebirthManager.getInstance().grantRebirthSkills(this); + } + + // Reload passive skills from armors / jewels / weapons. + getInventory().reloadEquippedItems(); + + // Add Death Penalty Buff Level. + restoreDeathPenaltyBuffLevel(); + } + + /** + * Give all available skills to the player. + * @param includeByFs if {@code true} forgotten scroll skills present in the skill tree will be added + * @param includeAutoGet if {@code true} auto-get skills present in the skill tree will be added + * @param includeRequiredItems if {@code true} skills that have required items will be added + * @return the amount of new skills earned + */ + public int giveAvailableSkills(boolean includeByFs, boolean includeAutoGet, boolean includeRequiredItems) + { + int skillCounter = 0; + + // Get available skills. + final Collection skills = SkillTreeData.getInstance().getAllAvailableSkills(this, getPlayerClass(), includeByFs, includeAutoGet, includeRequiredItems); + final List skillsForStore = new ArrayList<>(); + for (Skill skill : skills) + { + if (getKnownSkill(skill.getId()) == skill) + { + continue; + } + + if (getSkillLevel(skill.getId()) == 0) + { + skillCounter++; + } + + // Fix when learning toggle skills. + if (skill.isToggle() && isAffectedBySkill(skill.getId())) + { + stopSkillEffects(SkillFinishType.REMOVED, skill.getId()); + } + + addSkill(skill, false); + skillsForStore.add(skill); + + if (PlayerConfig.AUTO_LEARN_SKILLS) + { + updateShortcuts(skill.getId(), skill.getLevel()); + } + } + + storeSkills(skillsForStore, -1); + + if (PlayerConfig.AUTO_LEARN_SKILLS && (skillCounter > 0)) + { + sendPacket(new ShortcutInit(this)); + sendMessage("You have learned " + skillCounter + " new skills."); + } + + return skillCounter; + } + + /** + * Give all available auto-get skills to the player. + */ + public void giveAvailableAutoGetSkills() + { + // Get available skills. + final List autoGetSkills = SkillTreeData.getInstance().getAvailableAutoGetSkills(this); + final SkillData st = SkillData.getInstance(); + Skill skill; + for (SkillLearn s : autoGetSkills) + { + skill = st.getSkill(s.getSkillId(), s.getSkillLevel()); + if (skill != null) + { + addSkill(skill, true); + } + else + { + LOGGER.warning("Skipping null auto-get skill for " + this); + } + } + } + + /** + * Set the Experience value of the Player. + * @param exp + */ + public void setExp(long exp) + { + getStat().setExp(Math.max(0, exp)); + } + + /** + * @return the Race object of the Player. + */ + @Override + public Race getRace() + { + if (!isSubClassActive()) + { + return getTemplate().getRace(); + } + + return PlayerTemplateData.getInstance().getTemplate(_baseClass).getRace(); + } + + public Radar getRadar() + { + return _radar; + } + + /* Return true if Hellbound minimap allowed */ + public boolean isMinimapAllowed() + { + return _minimapAllowed; + } + + /* Enable or disable minimap on Hellbound */ + public void setMinimapAllowed(boolean value) + { + _minimapAllowed = value; + } + + /** + * @return the SP amount of the Player. + */ + public long getSp() + { + return getStat().getSp(); + } + + /** + * Set the SP amount of the Player. + * @param sp + */ + public void setSp(long sp) + { + super.getStat().setSp(Math.max(0, sp)); + } + + /** + * @param castleId + * @return true if this Player is a clan leader in ownership of the passed castle + */ + public boolean isCastleLord(int castleId) + { + // Player has clan and is the clan leader, check the castle info. + if ((_clan != null) && (_clan.getLeader().getPlayer() == this)) + { + // If the clan has a castle and it is actually the queried castle, return true. + final Castle castle = CastleManager.getInstance().getCastleByOwner(_clan); + if ((castle != null) && (castle == CastleManager.getInstance().getCastleById(castleId))) + { + return true; + } + } + + return false; + } + + /** + * @return the Clan Identifier of the Player. + */ + @Override + public int getClanId() + { + return _clanId; + } + + /** + * @return the Clan Crest Identifier of the Player or 0. + */ + public int getClanCrestId() + { + return _clan != null ? _clan.getCrestId() : 0; + } + + /** + * @return The Clan CrestLarge Identifier or 0 + */ + public int getClanCrestLargeId() + { + if ((_clan != null) && ((_clan.getCastleId() != 0) || (_clan.getHideoutId() != 0))) + { + return _clan.getCrestLargeId(); + } + + return 0; + } + + public long getClanJoinExpiryTime() + { + return _clanJoinExpiryTime; + } + + public void setClanJoinExpiryTime(long time) + { + _clanJoinExpiryTime = time; + } + + public long getClanCreateExpiryTime() + { + return _clanCreateExpiryTime; + } + + public void setClanCreateExpiryTime(long time) + { + _clanCreateExpiryTime = time; + } + + public void setOnlineTime(long time) + { + _onlineTime = time; + _onlineBeginTime = System.currentTimeMillis(); + } + + public int getOnlineTimeMillis() + { + return (int) (System.currentTimeMillis() - _onlineBeginTime); + } + + /** + * Return the PcInventory Inventory of the Player contained in _inventory. + */ + @Override + public PlayerInventory getInventory() + { + return _inventory; + } + + /** + * Delete a Shortcut of the Player _shortcuts. + * @param objectId + */ + public void removeItemFromShortcut(int objectId) + { + _shortcuts.deleteShortcutByObjectId(objectId); + } + + /** + * @return True if the Player is sitting. + */ + public boolean isSitting() + { + return _waitTypeSitting; + } + + /** + * Set _waitTypeSitting to given value. + * @param value + */ + public void setSitting(boolean value) + { + _waitTypeSitting = value; + } + + /** + * Set _sittingInProgress to given value. + * @param value + */ + public void setSittingProgress(boolean value) + { + _sittingInProgress = value; + } + + /** + * Sit down the Player, set the AI Intention to REST and send a Server->Client ChangeWaitType packet (broadcast) + */ + public void sitDown() + { + sitDown(true); + } + + public void sitDown(boolean checkCast) + { + if (_sittingInProgress) + { + return; + } + + if (checkCast && isCastingNow()) + { + sendMessage("Cannot sit while casting"); + return; + } + + if (_waitTypeSitting || isAttackDisabled() || isOutOfControl() || isImmobilized()) + { + return; + } + + breakAttack(); + setSitting(true); + setSittingProgress(true); + getAI().setIntentionRest(); + broadcastPacket(new ChangeWaitType(this, ChangeWaitType.WT_SITTING)); + + // Schedule a sit down task to wait for the animation to finish. + ThreadPool.schedule(new SitDownTask(this), 2500); + } + + /** + * Stand up the Player, set the AI Intention to IDLE and send a Server->Client ChangeWaitType packet (broadcast) + */ + public void standUp() + { + if (_sittingInProgress) + { + return; + } + + if (_waitTypeSitting && !isInStoreMode() && !isAlikeDead()) + { + setSittingProgress(true); + if (getEffectList().isAffected(EffectFlag.RELAXING)) + { + stopEffects(EffectType.RELAXING); + } + + broadcastPacket(new ChangeWaitType(this, ChangeWaitType.WT_STANDING)); + + // Schedule a stand up task to wait for the animation to finish. + ThreadPool.schedule(new StandUpTask(this), 2500); + } + } + + /** + * @return the PlayerWarehouse object of the Player. + */ + public PlayerWarehouse getWarehouse() + { + return _warehouse; + } + + /** + * @return the PlayerFreight object of the Player. + */ + public PlayerFreight getFreight() + { + return _freight; + } + + /** + * @return true if refund list is not empty + */ + public boolean hasRefund() + { + return (_refund != null) && (_refund.getSize() > 0) && GeneralConfig.ALLOW_REFUND; + } + + /** + * @return refund object or create new if not exist + */ + public PlayerRefund getRefund() + { + if (_refund == null) + { + _refund = new PlayerRefund(this); + } + + return _refund; + } + + /** + * Clear refund + */ + public void clearRefund() + { + if (_refund != null) + { + _refund.deleteMe(); + } + + _refund = null; + } + + /** + * @return the Adena amount of the Player. + */ + public int getAdena() + { + return _inventory.getAdena(); + } + + /** + * @return the Ancient Adena amount of the Player. + */ + public int getAncientAdena() + { + return _inventory.getAncientAdena(); + } + + /** + * Add adena to Inventory of the Player and send a Server->Client InventoryUpdate packet to the Player. + * @param process : ItemProcessType identifier of process triggering this action + * @param count : int Quantity of adena to be added + * @param reference : WorldObject Object referencing current action like NPC selling item or previous item in transformation + * @param sendMessage : boolean Specifies whether to send message to Client about this action + */ + public void addAdena(ItemProcessType process, int count, WorldObject reference, boolean sendMessage) + { + final int currentAdena = _inventory.getAdena(); + final int limitRemaining = PlayerConfig.MAX_ADENA - currentAdena; + final int amountToAdd = count; + + if (count > limitRemaining) + { + count = limitRemaining; + } + + if (sendMessage) + { + if ((count == 0) && (amountToAdd > 0)) + { + // Failed adding adena. + final SystemMessage sm = new SystemMessage(SystemMessageId.YOU_HAVE_FAILED_TO_EARN_S1_ADENA); + sm.addInt(amountToAdd); + sendPacket(sm); + } + else + { + // Inform player how much adena they received. + final SystemMessage sm = new SystemMessage(SystemMessageId.YOU_HAVE_EARNED_S1_ADENA); + sm.addInt(count); + sendPacket(sm); + } + } + + if (count > 0) + { + _inventory.addAdena(process, count, this, reference); + + // Send update packet. + if (count == getAdena()) + { + sendItemList(false); + } + else + { + final InventoryUpdate iu = new InventoryUpdate(); + iu.addModifiedItem(_inventory.getAdenaInstance()); + sendInventoryUpdate(iu); + } + } + + // If the player has reached the adena limit, inform them. + if ((_inventory.getAdena() == PlayerConfig.MAX_ADENA) && sendMessage) + { + sendPacket(SystemMessageId.YOU_HAVE_EXCEEDED_YOUR_OUT_OF_POCKET_ADENA_LIMIT); + } + } + + /** + * Reduce adena in Inventory of the Player and send a Server->Client InventoryUpdate packet to the Player. + * @param process : ItemProcessType identifier of process triggering this action + * @param count : long Quantity of adena to be reduced + * @param reference : WorldObject Object referencing current action like NPC selling item or previous item in transformation + * @param sendMessage : boolean Specifies whether to send message to Client about this action + * @return boolean informing if the action was successful + */ + public boolean reduceAdena(ItemProcessType process, int count, WorldObject reference, boolean sendMessage) + { + if (count > _inventory.getAdena()) + { + if (sendMessage) + { + sendPacket(SystemMessageId.YOU_DO_NOT_HAVE_ENOUGH_ADENA); + } + + return false; + } + + if (count > 0) + { + if (!_inventory.reduceAdena(process, count, this, reference)) + { + return false; + } + + // Send update packet. + final Item adenaItem = _inventory.getAdenaInstance(); + final InventoryUpdate iu = new InventoryUpdate(); + iu.addItem(adenaItem); + sendInventoryUpdate(iu); + + if (sendMessage) + { + final SystemMessage sm = new SystemMessage(SystemMessageId.S1_ADENA_DISAPPEARED); + sm.addInt(count); + sendPacket(sm); + } + } + + return true; + } + + /** + * Add ancient adena to Inventory of the Player and send a Server->Client InventoryUpdate packet to the Player. + * @param process : ItemProcessType identifier of process triggering this action + * @param count : int Quantity of ancient adena to be added + * @param reference : WorldObject Object referencing current action like NPC selling item or previous item in transformation + * @param sendMessage : boolean Specifies whether to send message to Client about this action + */ + public void addAncientAdena(ItemProcessType process, int count, WorldObject reference, boolean sendMessage) + { + if (sendMessage) + { + final SystemMessage sm = new SystemMessage(SystemMessageId.YOU_HAVE_EARNED_S2_S1_S); + sm.addItemName(Inventory.ANCIENT_ADENA_ID); + sm.addInt(count); + sendPacket(sm); + } + + if (count <= 0) + { + return; + } + + _inventory.addAncientAdena(process, count, this, reference); + + final InventoryUpdate iu = new InventoryUpdate(); + iu.addItem(_inventory.getAncientAdenaInstance()); + sendInventoryUpdate(iu); + } + + /** + * Reduce ancient adena in Inventory of the Player and send a Server->Client InventoryUpdate packet to the Player. + * @param process : ItemProcessType identifier of process triggering this action + * @param count : long Quantity of ancient adena to be reduced + * @param reference : WorldObject Object referencing current action like NPC selling item or previous item in transformation + * @param sendMessage : boolean Specifies whether to send message to Client about this action + * @return boolean informing if the action was successful + */ + public boolean reduceAncientAdena(ItemProcessType process, int count, WorldObject reference, boolean sendMessage) + { + if (count > _inventory.getAncientAdena()) + { + if (sendMessage) + { + sendPacket(SystemMessageId.YOU_DO_NOT_HAVE_ENOUGH_ADENA); + } + + return false; + } + + if (count > 0) + { + if (!_inventory.reduceAncientAdena(process, count, this, reference)) + { + return false; + } + + final Item ancientAdenaItem = _inventory.getAncientAdenaInstance(); + final InventoryUpdate iu = new InventoryUpdate(); + iu.addItem(ancientAdenaItem); + sendInventoryUpdate(iu); + + if (sendMessage) + { + final SystemMessage sm; + if (count > 1) + { + sm = new SystemMessage(SystemMessageId.S2_S1_HAS_DISAPPEARED); + sm.addItemName(Inventory.ANCIENT_ADENA_ID); + sm.addInt(count); + } + else + { + sm = new SystemMessage(SystemMessageId.S1_HAS_DISAPPEARED); + sm.addItemName(Inventory.ANCIENT_ADENA_ID); + } + + sendPacket(sm); + } + } + + return true; + } + + /** + * Adds item to inventory and send a Server->Client InventoryUpdate packet to the Player. + * @param process : ItemProcessType identifier of process triggering this action + * @param item : Item to be added + * @param reference : WorldObject Object referencing current action like NPC selling item or previous item in transformation + * @param sendMessage : boolean Specifies whether to send message to Client about this action + */ + public void addItem(ItemProcessType process, Item item, WorldObject reference, boolean sendMessage) + { + if (item.getCount() > 0) + { + // Sends message to client if requested. + if (sendMessage) + { + final SystemMessage sm; + if (item.getCount() > 1) + { + sm = new SystemMessage(SystemMessageId.YOU_HAVE_OBTAINED_S2_S1); + sm.addItemName(item); + sm.addInt(item.getCount()); + } + else if (item.isEnchanted()) + { + sm = new SystemMessage(SystemMessageId.YOU_HAVE_OBTAINED_A_S1_S2); + sm.addInt(item.getEnchantLevel()); + sm.addItemName(item); + } + else + { + sm = new SystemMessage(SystemMessageId.YOU_HAVE_OBTAINED_S1); + sm.addItemName(item); + } + + sendPacket(sm); + } + + // Add the item to inventory. + final Item newitem = _inventory.addItem(process, item, this, reference); + + // Send inventory update packet. + final InventoryUpdate playerIU = new InventoryUpdate(); + playerIU.addItem(newitem); + sendInventoryUpdate(playerIU); + + // Update current load as well. + final StatusUpdate su = new StatusUpdate(this); + su.addAttribute(StatusUpdate.CUR_LOAD, getCurrentLoad()); + sendPacket(su); + + // If over capacity, drop the item. + if (!isGM() && !_inventory.validateCapacity(0, item.isQuestItem()) && newitem.isDropable() && (!newitem.isStackable() || (newitem.getLastChange() != Item.MODIFIED))) + { + dropItem(ItemProcessType.DROP, newitem, null, true, true); + } + else if (CursedWeaponsManager.getInstance().isCursed(newitem.getId())) + { + CursedWeaponsManager.getInstance().activate(this, newitem); + } + } + } + + /** + * Adds item to Inventory and send a Server->Client InventoryUpdate packet to the Player. + * @param process : ItemProcessType identifier of process triggering this action + * @param itemId : int Item Identifier of the item to be added + * @param count : long Quantity of items to be added + * @param reference : WorldObject Object referencing current action like NPC selling item or previous item in transformation + * @param sendMessage : boolean Specifies whether to send message to Client about this action + * @return + */ + public Item addItem(ItemProcessType process, int itemId, int count, WorldObject reference, boolean sendMessage) + { + return addItem(process, itemId, count, -1, reference, sendMessage); + } + + /** + * Adds item to Inventory and send a Server->Client InventoryUpdate packet to the Player. + * @param process : ItemProcessType identifier of process triggering this action + * @param itemId : int Item Identifier of the item to be added + * @param count : int Quantity of items to be added + * @param enchantLevel : int EnchantLevel of the item to be added + * @param reference : WorldObject Object referencing current action like NPC selling item or previous item in transformation + * @param sendMessage : boolean Specifies whether to send message to Client about this action + * @return + */ + public Item addItem(ItemProcessType process, int itemId, int count, int enchantLevel, WorldObject reference, boolean sendMessage) + { + if (count > 0) + { + final ItemTemplate item = ItemData.getInstance().getTemplate(itemId); + if (item == null) + { + LOGGER.severe("Item doesn't exist so cannot be added. Item ID: " + itemId); + return null; + } + + // Sends message to client if requested. + if (sendMessage && ((!isCastingNow() && item.hasExImmediateEffect()) || !item.hasExImmediateEffect())) + { + if (count > 1) + { + if ((process == ItemProcessType.SWEEP) || (process == ItemProcessType.QUEST)) + { + final SystemMessage sm = new SystemMessage(SystemMessageId.YOU_HAVE_EARNED_S2_S1_S); + sm.addItemName(itemId); + sm.addInt(count); + sendPacket(sm); + } + else + { + final SystemMessage sm = new SystemMessage(SystemMessageId.YOU_HAVE_OBTAINED_S2_S1); + sm.addItemName(itemId); + sm.addInt(count); + sendPacket(sm); + } + } + else if ((process == ItemProcessType.SWEEP) || (process == ItemProcessType.QUEST)) + { + final SystemMessage sm = new SystemMessage(SystemMessageId.YOU_HAVE_EARNED_S1); + sm.addItemName(itemId); + sendPacket(sm); + } + else + { + final SystemMessage sm = new SystemMessage(SystemMessageId.YOU_HAVE_OBTAINED_S1); + sm.addItemName(itemId); + sendPacket(sm); + } + } + + // Auto-use herbs. + if (item.hasExImmediateEffect() && item.isEtcItem()) + { + for (SkillHolder skillHolder : item.getSkills()) + { + doSimultaneousCast(skillHolder.getSkill()); + } + + broadcastInfo(); + } + else + { + // Add the item to inventory. + final Item createdItem = _inventory.addItem(process, itemId, count, this, reference); + if (enchantLevel > -1) + { + createdItem.setEnchantLevel(enchantLevel); + + // Update with an InventoryUpdate that has the new enchant value. + final InventoryUpdate playerIU = new InventoryUpdate(); + if (createdItem.isStackable() && (createdItem.getCount() > count)) + { + playerIU.addModifiedItem(createdItem); + } + else + { + playerIU.addNewItem(createdItem); + } + sendInventoryUpdate(playerIU); + } + + // If over capacity, drop the item. + if (!isGM() && !_inventory.validateCapacity(0, item.isQuestItem()) && createdItem.isDropable() && (!createdItem.isStackable() || (createdItem.getLastChange() != Item.MODIFIED))) + { + dropItem(ItemProcessType.DROP, createdItem, null, true); + } + else if (CursedWeaponsManager.getInstance().isCursed(createdItem.getId())) + { + CursedWeaponsManager.getInstance().activate(this, createdItem); + } + + return createdItem; + } + } + + return null; + } + + /** + * @param process the ItemProcessType identifier of process triggering this action + * @param item the item holder + * @param reference the reference object + * @param sendMessage if {@code true} a system message will be sent + */ + public void addItem(ItemProcessType process, ItemHolder item, WorldObject reference, boolean sendMessage) + { + addItem(process, item.getId(), item.getCount(), reference, sendMessage); + } + + /** + * Destroy item from inventory and send a Server->Client InventoryUpdate packet to the Player. + * @param process : ItemProcessType identifier of process triggering this action + * @param item : Item to be destroyed + * @param reference : WorldObject Object referencing current action like NPC selling item or previous item in transformation + * @param sendMessage : boolean Specifies whether to send message to Client about this action + * @return boolean informing if the action was successful + */ + public boolean destroyItem(ItemProcessType process, Item item, WorldObject reference, boolean sendMessage) + { + return destroyItem(process, item, item.getCount(), reference, sendMessage); + } + + /** + * Destroy item from inventory and send a Server->Client InventoryUpdate packet to the Player. + * @param process : ItemProcessType identifier of process triggering this action + * @param item : Item to be destroyed + * @param count + * @param reference : WorldObject Object referencing current action like NPC selling item or previous item in transformation + * @param sendMessage : boolean Specifies whether to send message to Client about this action + * @return boolean informing if the action was successful + */ + public boolean destroyItem(ItemProcessType process, Item item, int count, WorldObject reference, boolean sendMessage) + { + final Item destoyedItem = _inventory.destroyItem(process, item, count, this, reference); + if (destoyedItem == null) + { + if (sendMessage) + { + sendPacket(SystemMessageId.INCORRECT_ITEM_COUNT_2); + } + + return false; + } + + // Send inventory update packet. + final InventoryUpdate playerIU = new InventoryUpdate(); + playerIU.addItem(destoyedItem); + sendInventoryUpdate(playerIU); + + // Update current load as well. + final StatusUpdate su = new StatusUpdate(this); + su.addAttribute(StatusUpdate.CUR_LOAD, getCurrentLoad()); + sendPacket(su); + + // Sends message to client if requested. + if (sendMessage) + { + final SystemMessage sm; + if (count > 1) + { + sm = new SystemMessage(SystemMessageId.S2_S1_HAS_DISAPPEARED); + sm.addItemName(destoyedItem); + sm.addInt(count); + } + else + { + sm = new SystemMessage(SystemMessageId.S1_HAS_DISAPPEARED); + sm.addItemName(destoyedItem); + } + + sendPacket(sm); + } + + return true; + } + + /** + * Destroys item from inventory and send a Server->Client InventoryUpdate packet to the Player. + * @param process : ItemProcessType identifier of process triggering this action (if null item will not be logged) + * @param objectId : int Item Instance identifier of the item to be destroyed + * @param count : int Quantity of items to be destroyed + * @param reference : WorldObject Object referencing current action like NPC selling item or previous item in transformation + * @param sendMessage : boolean Specifies whether to send message to Client about this action + * @return boolean informing if the action was successful + */ + @Override + public boolean destroyItem(ItemProcessType process, int objectId, int count, WorldObject reference, boolean sendMessage) + { + final Item item = _inventory.getItemByObjectId(objectId); + if ((item == null) || (item.getCount() < count)) + { + if (sendMessage) + { + sendPacket(SystemMessageId.INCORRECT_ITEM_COUNT_2); + } + + return false; + } + + return destroyItem(process, item, count, reference, sendMessage); + } + + /** + * Destroy item from inventory by using its itemId and send a Server->Client InventoryUpdate packet to the Player. + * @param process : ItemProcessType identifier of process triggering this action + * @param itemId : int Item identifier of the item to be destroyed + * @param count : int Quantity of items to be destroyed + * @param reference : WorldObject Object referencing current action like NPC selling item or previous item in transformation + * @param sendMessage : boolean Specifies whether to send message to Client about this action + * @return boolean informing if the action was successful + */ + @Override + public boolean destroyItemByItemId(ItemProcessType process, int itemId, int count, WorldObject reference, boolean sendMessage) + { + if (itemId == Inventory.ADENA_ID) + { + return reduceAdena(process, count, reference, sendMessage); + } + + final Item item = _inventory.getItemByItemId(itemId); + if (item == null) + { + if (sendMessage) + { + sendPacket(SystemMessageId.INCORRECT_ITEM_COUNT_2); + } + + return false; + } + + final int itemCount = item.isStackable() ? item.getCount() : _inventory.getInventoryItemCount(itemId, -1); + final int removeCount = count < 0 ? itemCount : count; + if ((removeCount <= 0) || (itemCount < removeCount) || (_inventory.destroyItemByItemId(process, itemId, removeCount, this, reference) == null)) + { + if (sendMessage) + { + sendPacket(SystemMessageId.INCORRECT_ITEM_COUNT_2); + } + + return false; + } + + // Send inventory update packet. + final InventoryUpdate playerIU = new InventoryUpdate(); + playerIU.addItem(item); + sendInventoryUpdate(playerIU); + + // Update current load as well. + final StatusUpdate su = new StatusUpdate(this); + su.addAttribute(StatusUpdate.CUR_LOAD, getCurrentLoad()); + sendPacket(su); + + // Sends message to client if requested. + if (sendMessage) + { + final SystemMessage sm; + if (removeCount > 1) + { + sm = new SystemMessage(SystemMessageId.S2_S1_HAS_DISAPPEARED); + sm.addItemName(itemId); + sm.addInt(removeCount); + } + else + { + sm = new SystemMessage(SystemMessageId.S1_HAS_DISAPPEARED); + sm.addItemName(itemId); + } + + sendPacket(sm); + } + + return true; + } + + /** + * Transfers item to another ItemContainer and send a Server->Client InventoryUpdate packet to the Player. + * @param process : ItemProcessType identifier of process triggering this action + * @param objectId : int Item Identifier of the item to be transfered + * @param count : long Quantity of items to be transfered + * @param target + * @param reference : WorldObject Object referencing current action like NPC selling item or previous item in transformation + * @return Item corresponding to the new item or the updated item in inventory + */ + public Item transferItem(ItemProcessType process, int objectId, int count, Inventory target, WorldObject reference) + { + final Item oldItem = checkItemManipulation(objectId, count, "transfer"); + if (oldItem == null) + { + return null; + } + + final Item newItem = _inventory.transferItem(process, objectId, count, target, this, reference); + if (newItem == null) + { + return null; + } + + // Send inventory update packet. + final InventoryUpdate playerIU = new InventoryUpdate(); + if ((oldItem.getCount() > 0) && (oldItem != newItem)) + { + playerIU.addModifiedItem(oldItem); + } + else + { + playerIU.addRemovedItem(oldItem); + } + + sendInventoryUpdate(playerIU); + + // Update current load as well. + StatusUpdate playerSU = new StatusUpdate(this); + playerSU.addAttribute(StatusUpdate.CUR_LOAD, getCurrentLoad()); + sendPacket(playerSU); + + // Send target update packet. + if (target instanceof PlayerInventory) + { + final Player targetPlayer = ((PlayerInventory) target).getOwner(); + final InventoryUpdate targetIU = new InventoryUpdate(); + if (newItem.getCount() > count) + { + targetIU.addModifiedItem(newItem); + } + else + { + targetIU.addNewItem(newItem); + } + + targetPlayer.sendInventoryUpdate(targetIU); + + // Update current load as well. + playerSU = new StatusUpdate(targetPlayer); + playerSU.addAttribute(StatusUpdate.CUR_LOAD, targetPlayer.getCurrentLoad()); + targetPlayer.sendPacket(playerSU); + } + else if (target instanceof PetInventory) + { + final PetInventoryUpdate petIU = new PetInventoryUpdate(); + if (newItem.getCount() > count) + { + petIU.addModifiedItem(newItem); + } + else + { + petIU.addNewItem(newItem); + } + + ((PetInventory) target).getOwner().sendPacket(petIU); + } + + return newItem; + } + + /** + * Use instead of calling {@link #addItem(ItemProcessType, Item, WorldObject, boolean)} and {@link #destroyItemByItemId(ItemProcessType, int, int, WorldObject, boolean)}
    + * This method validates slots and weight limit, for stackable and non-stackable items. + * @param process a ItemProcessType representing the process that is exchanging this items + * @param reference the (probably NPC) reference, could be null + * @param coinId the item Id of the item given on the exchange + * @param cost the amount of items given on the exchange + * @param rewardId the item received on the exchange + * @param count the amount of items received on the exchange + * @param sendMessage if {@code true} it will send messages to the acting player + * @return {@code true} if the player successfully exchanged the items, {@code false} otherwise + */ + public boolean exchangeItemsById(ItemProcessType process, WorldObject reference, int coinId, int cost, int rewardId, int count, boolean sendMessage) + { + if (!_inventory.validateCapacityByItemId(rewardId, count)) + { + if (sendMessage) + { + sendPacket(SystemMessageId.YOUR_INVENTORY_IS_FULL); + } + + return false; + } + + if (!_inventory.validateWeightByItemId(rewardId, count)) + { + if (sendMessage) + { + sendPacket(SystemMessageId.YOU_HAVE_EXCEEDED_THE_WEIGHT_LIMIT); + } + + return false; + } + + if (destroyItemByItemId(process, coinId, cost, reference, sendMessage)) + { + addItem(process, rewardId, count, reference, sendMessage); + return true; + } + + return false; + } + + /** + * Drop item from inventory and send a Server->Client InventoryUpdate packet to the Player. + * @param process ItemProcessType identifier of process triggering this action + * @param item Item to be dropped + * @param reference WorldObject Object referencing current action like NPC selling item or previous item in transformation + * @param sendMessage boolean Specifies whether to send message to Client about this action + * @param protectItem whether or not dropped item must be protected temporary against other players + * @return boolean informing if the action was successful + */ + public boolean dropItem(ItemProcessType process, Item item, WorldObject reference, boolean sendMessage, boolean protectItem) + { + final Item droppedItem = _inventory.dropItem(process, item, this, reference); + if (droppedItem == null) + { + if (sendMessage) + { + sendPacket(SystemMessageId.INCORRECT_ITEM_COUNT_2); + } + + return false; + } + + droppedItem.dropMe(this, (getX() + Rnd.get(50)) - 25, (getY() + Rnd.get(50)) - 25, getZ() + 20); + if ((GeneralConfig.AUTODESTROY_ITEM_AFTER > 0) && GeneralConfig.DESTROY_DROPPED_PLAYER_ITEM && !GeneralConfig.LIST_PROTECTED_ITEMS.contains(droppedItem.getId()) && ((droppedItem.isEquipable() && GeneralConfig.DESTROY_EQUIPABLE_PLAYER_ITEM) || !droppedItem.isEquipable())) + { + ItemsAutoDestroyTaskManager.getInstance().addItem(droppedItem); + } + + // Protection against auto destroy dropped item. + if (GeneralConfig.DESTROY_DROPPED_PLAYER_ITEM) + { + droppedItem.setProtected(droppedItem.isEquipable() && (!droppedItem.isEquipable() || !GeneralConfig.DESTROY_EQUIPABLE_PLAYER_ITEM)); + } + else + { + droppedItem.setProtected(true); + } + + // Retail drop protection. + if (protectItem) + { + droppedItem.getDropProtection().protect(this); + } + + // Send inventory update packet. + final InventoryUpdate playerIU = new InventoryUpdate(); + playerIU.addItem(droppedItem); + sendInventoryUpdate(playerIU); + + // Update current load as well. + final StatusUpdate su = new StatusUpdate(this); + su.addAttribute(StatusUpdate.CUR_LOAD, getCurrentLoad()); + sendPacket(su); + + // Sends message to client if requested. + if (sendMessage) + { + final SystemMessage sm = new SystemMessage(SystemMessageId.YOU_HAVE_DROPPED_S1); + sm.addItemName(droppedItem); + sendPacket(sm); + } + + return true; + } + + public boolean dropItem(ItemProcessType process, Item item, WorldObject reference, boolean sendMessage) + { + return dropItem(process, item, reference, sendMessage, false); + } + + /** + * Drop item from inventory by using its objectID and send a Server->Client InventoryUpdate packet to the Player. + * @param process : ItemProcessType identifier of process triggering this action + * @param objectId : int Item Instance identifier of the item to be dropped + * @param count : long Quantity of items to be dropped + * @param x : int coordinate for drop X + * @param y : int coordinate for drop Y + * @param z : int coordinate for drop Z + * @param reference : WorldObject Object referencing current action like NPC selling item or previous item in transformation + * @param sendMessage : boolean Specifies whether to send message to Client about this action + * @param protectItem + * @return Item corresponding to the new item or the updated item in inventory + */ + public Item dropItem(ItemProcessType process, int objectId, int count, int x, int y, int z, WorldObject reference, boolean sendMessage, boolean protectItem) + { + final Item invitem = _inventory.getItemByObjectId(objectId); + final Item item = _inventory.dropItem(process, objectId, count, this, reference); + if (item == null) + { + if (sendMessage) + { + sendPacket(SystemMessageId.INCORRECT_ITEM_COUNT_2); + } + + return null; + } + + item.dropMe(this, x, y, z); + if ((GeneralConfig.AUTODESTROY_ITEM_AFTER > 0) && GeneralConfig.DESTROY_DROPPED_PLAYER_ITEM && !GeneralConfig.LIST_PROTECTED_ITEMS.contains(item.getId()) && ((item.isEquipable() && GeneralConfig.DESTROY_EQUIPABLE_PLAYER_ITEM) || !item.isEquipable())) + { + ItemsAutoDestroyTaskManager.getInstance().addItem(item); + } + + if (GeneralConfig.DESTROY_DROPPED_PLAYER_ITEM) + { + item.setProtected(!(!item.isEquipable() || (item.isEquipable() && GeneralConfig.DESTROY_EQUIPABLE_PLAYER_ITEM))); + } + else + { + item.setProtected(true); + } + + // Retail drop protection. + if (protectItem) + { + item.getDropProtection().protect(this); + } + + // Send inventory update packet. + final InventoryUpdate playerIU = new InventoryUpdate(); + playerIU.addItem(invitem); + sendInventoryUpdate(playerIU); + + // Update current load as well. + final StatusUpdate su = new StatusUpdate(this); + su.addAttribute(StatusUpdate.CUR_LOAD, getCurrentLoad()); + sendPacket(su); + + // Sends message to client if requested. + if (sendMessage) + { + final SystemMessage sm = new SystemMessage(SystemMessageId.YOU_HAVE_DROPPED_S1); + sm.addItemName(item); + sendPacket(sm); + } + + return item; + } + + public Item checkItemManipulation(int objectId, int count, String action) + { + // TODO: if we remove objects that are not visible from the World, we'll have to remove this check + if (World.findObject(objectId) == null) + { + LOGGER.finest(getObjectId() + ": player tried to " + action + " item not available in World"); + return null; + } + + final Item item = _inventory.getItemByObjectId(objectId); + if ((item == null) || (item.getOwnerId() != getObjectId())) + { + LOGGER.finest(getObjectId() + ": player tried to " + action + " item he is not owner of"); + return null; + } + + if ((count < 0) || ((count > 1) && !item.isStackable())) + { + LOGGER.finest(getObjectId() + ": player tried to " + action + " item with invalid count: " + count); + return null; + } + + if (count > item.getCount()) + { + LOGGER.finest(getObjectId() + ": player tried to " + action + " more items than he owns"); + return null; + } + + // Pet is summoned and not the item that summoned the pet AND not the buggle from strider you're mounting. + if ((hasSummon() && (_summon.getControlObjectId() == objectId)) || (_mountObjectID == objectId)) + { + return null; + } + + if (_activeEnchantItemId == objectId) + { + return null; + } + + // We cannot put a Weapon with Augmention in WH while casting (Possible Exploit). + if (item.isAugmented() && (isCastingNow() || isCastingSimultaneouslyNow())) + { + return null; + } + + return item; + } + + public boolean isSpawnProtected() + { + return (_spawnProtectEndTime != 0) && (_spawnProtectEndTime > System.currentTimeMillis()); + } + + public boolean isTeleportProtected() + { + return (_teleportProtectEndTime != 0) && (_teleportProtectEndTime > System.currentTimeMillis()); + } + + public void setSpawnProtection(boolean protect) + { + _spawnProtectEndTime = protect ? System.currentTimeMillis() + (PlayerConfig.PLAYER_SPAWN_PROTECTION * 1000) : 0; + } + + public void setTeleportProtection(boolean protect) + { + _teleportProtectEndTime = protect ? System.currentTimeMillis() + (PlayerConfig.PLAYER_TELEPORT_PROTECTION * 1000) : 0; + } + + /** + * Set protection from aggro mobs when getting up from fake death, according settings. + * @param protect + */ + public void setRecentFakeDeath(boolean protect) + { + _recentFakeDeathEndTime = protect ? GameTimeTaskManager.getInstance().getGameTicks() + (PlayerConfig.PLAYER_FAKEDEATH_UP_PROTECTION * GameTimeTaskManager.TICKS_PER_SECOND) : 0; + } + + public boolean isRecentFakeDeath() + { + return _recentFakeDeathEndTime > GameTimeTaskManager.getInstance().getGameTicks(); + } + + public boolean isFakeDeath() + { + return _isFakeDeath; + } + + public void setFakeDeath(boolean value) + { + _isFakeDeath = value; + } + + @Override + public boolean isAlikeDead() + { + return super.isAlikeDead() || _isFakeDeath; + } + + /** + * @return the client owner of this char. + */ + public GameClient getClient() + { + return _client; + } + + public void setClient(GameClient client) + { + _client = client; + if ((_client != null) && (_client.getIp() != null)) + { + _ip = _client.getIp(); + } + } + + public String getIPAddress() + { + return _ip; + } + + public Location getCurrentSkillWorldPosition() + { + return _currentSkillWorldPosition; + } + + public void setCurrentSkillWorldPosition(Location worldPosition) + { + _currentSkillWorldPosition = worldPosition; + } + + @Override + public void enableSkill(Skill skill) + { + super.enableSkill(skill); + removeTimeStamp(skill); + } + + @Override + public boolean checkDoCastConditions(Skill skill) + { + if (!super.checkDoCastConditions(skill) || _observerMode) + { + return false; + } + + if (_inOlympiadMode && skill.isBlockedInOlympiad()) + { + sendPacket(SystemMessageId.YOU_CANNOT_USE_THAT_SKILL_IN_A_GRAND_OLYMPIAD_GAMES_MATCH); + return false; + } + + // Check if the spell using charges. + if (_charges.get() < skill.getChargeConsumeCount()) + { + final SystemMessage sm = new SystemMessage(SystemMessageId.S1_CANNOT_BE_USED_DUE_TO_UNSUITABLE_TERMS); + sm.addSkillName(skill); + sendPacket(sm); + return false; + } + + return true; + } + + /** + * Returns true if cp update should be done, false if not. + * @return boolean + */ + private boolean needCpUpdate() + { + final double currentCp = getCurrentCp(); + if ((currentCp <= 1.0) || (getMaxCp() < MAX_HP_BAR_PX)) + { + return true; + } + + if ((currentCp <= _cpUpdateDecCheck) || (currentCp >= _cpUpdateIncCheck)) + { + if (currentCp == getMaxCp()) + { + _cpUpdateIncCheck = currentCp + 1; + _cpUpdateDecCheck = currentCp - _cpUpdateInterval; + } + else + { + final double doubleMulti = currentCp / _cpUpdateInterval; + int intMulti = (int) doubleMulti; + _cpUpdateDecCheck = _cpUpdateInterval * (doubleMulti < intMulti ? intMulti - 1 : intMulti); + _cpUpdateIncCheck = _cpUpdateDecCheck + _cpUpdateInterval; + } + + return true; + } + + return false; + } + + /** + * Returns true if mp update should be done, false if not. + * @return boolean + */ + private boolean needMpUpdate() + { + final double currentMp = getCurrentMp(); + if ((currentMp <= 1.0) || (getMaxMp() < MAX_HP_BAR_PX)) + { + return true; + } + + if ((currentMp <= _mpUpdateDecCheck) || (currentMp >= _mpUpdateIncCheck)) + { + if (currentMp == getMaxMp()) + { + _mpUpdateIncCheck = currentMp + 1; + _mpUpdateDecCheck = currentMp - _mpUpdateInterval; + } + else + { + final double doubleMulti = currentMp / _mpUpdateInterval; + int intMulti = (int) doubleMulti; + _mpUpdateDecCheck = _mpUpdateInterval * (doubleMulti < intMulti ? intMulti - 1 : intMulti); + _mpUpdateIncCheck = _mpUpdateDecCheck + _mpUpdateInterval; + } + + return true; + } + + return false; + } + + /** + * Send packet StatusUpdate with current HP,MP and CP to the Player and only current HP, MP and Level to all other Player of the Party. Actions: + *
  • Send the Server->Client packet StatusUpdate with current HP, MP and CP to this Player
  • + *
  • Send the Server->Client packet PartySmallWindowUpdate with current HP, MP and Level to all other Player of the Party
  • Caution: This method DOESN'T SEND current HP and MP to all Player of the _statusListener + */ + @Override + public void broadcastStatusUpdate() + { + if (_broadcastStatusUpdateTask == null) + { + _broadcastStatusUpdateTask = ThreadPool.schedule(() -> + { + final StatusUpdate su = new StatusUpdate(this); + su.addAttribute(StatusUpdate.MAX_HP, getMaxHp()); + su.addAttribute(StatusUpdate.CUR_HP, (int) getCurrentHp()); + su.addAttribute(StatusUpdate.MAX_MP, getMaxMp()); + su.addAttribute(StatusUpdate.CUR_MP, (int) getCurrentMp()); + su.addAttribute(StatusUpdate.MAX_CP, getMaxCp()); + su.addAttribute(StatusUpdate.CUR_CP, (int) getCurrentCp()); + sendPacket(su); + + final boolean needCpUpdate = needCpUpdate(); + final boolean needHpUpdate = needHpUpdate(); + final Party party = getParty(); + + // Check if a party is in progress and party window update is useful. + if ((party != null) && (needCpUpdate || needHpUpdate || needMpUpdate())) + { + party.broadcastToPartyMembers(this, new PartySmallWindowUpdate(this)); + } + + if (_inOlympiadMode && _olympiadStart && (needCpUpdate || needHpUpdate)) + { + final ExOlympiadUserInfo olyInfo = new ExOlympiadUserInfo(this, 1); + World.forEachVisibleObject(this, Player.class, player -> + { + if ((player != null) && player.isInOlympiadMode() && (player.getOlympiadGameId() == _olympiadGameId)) + { + player.sendPacket(olyInfo); + } + }); + + final Collection players = Olympiad.getInstance().getSpectators(_olympiadGameId); + if ((players != null) && !players.isEmpty()) + { + final ExOlympiadUserInfo olyInfo2 = new ExOlympiadUserInfo(this, getOlympiadSide()); + for (Player spectator : players) + { + if (spectator == null) + { + continue; + } + + spectator.sendPacket(olyInfo2); + } + } + } + + // In duel MP updated only with CP or HP. + if (_isInDuel && (needCpUpdate || needHpUpdate)) + { + DuelManager.getInstance().broadcastToOppositTeam(this, new ExDuelUpdateUserInfo(this)); + } + + _broadcastStatusUpdateTask = null; + }, 50); + } + } + + /** + * Send a Server->Client packet UserInfo to this Player and CharInfo to all known players.
    + * Caution: DON'T SEND UserInfo packet to other players instead of CharInfo packet.
    + * UserInfo packet contains PRIVATE DATA as MaxHP, STR, DEX...
    + */ + public void broadcastUserInfo() + { + // Send user info to the current player. + updateUserInfo(); + + // Broadcast char info to known players. + broadcastCharInfo(); + } + + public void updateUserInfo() + { + sendPacket(new UserInfo(this)); + // sendPacket(new ExBrExtraUserInfo(this)); + } + + public void broadcastCharInfo() + { + // Client is disconnected. + if (isOnlineInt() == 0) + { + return; + } + + if (_broadcastCharInfoTask == null) + { + _broadcastCharInfoTask = ThreadPool.schedule(() -> + { + final CharInfo charInfo = new CharInfo(this, false); + charInfo.sendInBroadcast(); + World.forEachVisibleObject(this, Player.class, player -> + { + if (isVisibleFor(player)) + { + if (isInvisible() && isGM()) + { + player.sendPacket(new CharInfo(this, true)); + } + else + { + player.sendPacket(charInfo); + } + + // player.sendPacket(new ExBrExtraUserInfo(this)); + + // Update relation. + final int relation = getRelation(player); + final boolean isAutoAttackable = isAutoAttackable(player); + final RelationCache oldrelation = getKnownRelations().get(player.getObjectId()); + if ((oldrelation == null) || (oldrelation.getRelation() != relation) || (oldrelation.isAutoAttackable() != isAutoAttackable)) + { + player.sendPacket(new RelationChanged(this, relation, isAutoAttackable)); + if (hasSummon()) + { + player.sendPacket(new RelationChanged(_summon, relation, isAutoAttackable)); + } + + getKnownRelations().put(player.getObjectId(), new RelationCache(relation, isAutoAttackable)); + } + } + }); + _broadcastCharInfoTask = null; + }, 100); + } + } + + public void broadcastTitleInfo() + { + // Send a Server->Client packet UserInfo to this Player. + updateUserInfo(); + + // Send a Server->Client packet TitleUpdate to all known players. + broadcastPacket(new NicknameChanged(this)); + } + + public void broadcastMessage(String text) + { + World.forEachVisibleObject(this, Player.class, player -> + { + if (!isVisibleFor(player)) + { + return; + } + + player.sendMessage(text); + }); + } + + @Override + public void broadcastPacket(ServerPacket packet) + { + packet.sendInBroadcast(); + + final boolean isCharInfo = packet instanceof CharInfo; + if (!isCharInfo) + { + sendPacket(packet); + } + + World.forEachVisibleObject(this, Player.class, player -> + { + if (!isVisibleFor(player)) + { + return; + } + + player.sendPacket(packet); + + if (isCharInfo) + { + final int relation = getRelation(player); + final boolean isAutoAttackable = isAutoAttackable(player); + final RelationCache cache = getKnownRelations().get(player.getObjectId()); + if ((cache == null) || (cache.getRelation() != relation) || (cache.isAutoAttackable() != isAutoAttackable)) + { + player.sendPacket(new RelationChanged(this, relation, isAutoAttackable)); + if (hasSummon()) + { + player.sendPacket(new RelationChanged(_summon, relation, isAutoAttackable)); + } + + getKnownRelations().put(player.getObjectId(), new RelationCache(relation, isAutoAttackable)); + } + } + }); + } + + /** + * @return the Alliance Identifier of the Player. + */ + @Override + public int getAllyId() + { + return _clan == null ? 0 : _clan.getAllyId(); + } + + public int getAllyCrestId() + { + return getAllyId() == 0 ? 0 : _clan.getAllyCrestId(); + } + + @Override + public void sendPacket(ServerPacket packet) + { + if (_client != null) + { + _client.sendPacket(packet); + } + } + + /** + * Send SystemMessage packet. + * @param id SystemMessageId + */ + @Override + public void sendPacket(SystemMessageId id) + { + sendPacket(new SystemMessage(id)); + } + + /** + * Manage Interact Task with another Player. Actions: + *
  • If the private store is a STORE_PRIVATE_SELL, send a Server->Client PrivateBuyListSell packet to the Player
  • + *
  • If the private store is a STORE_PRIVATE_BUY, send a Server->Client PrivateBuyListBuy packet to the Player
  • + *
  • If the private store is a STORE_PRIVATE_MANUFACTURE, send a Server->Client RecipeShopSellList packet to the Player

  • + * @param target The Creature targeted + */ + public void doInteract(Creature target) + { + if (target == null) + { + return; + } + + if (target.isPlayer()) + { + final Player temp = target.asPlayer(); + sendPacket(ActionFailed.STATIC_PACKET); + + if ((temp.getPrivateStoreType() == PrivateStoreType.SELL) || (temp.getPrivateStoreType() == PrivateStoreType.PACKAGE_SELL)) + { + sendPacket(new PrivateStoreListSell(this, temp)); + } + else if (temp.getPrivateStoreType() == PrivateStoreType.BUY) + { + sendPacket(new PrivateStoreListBuy(this, temp)); + } + else if (temp.getPrivateStoreType() == PrivateStoreType.MANUFACTURE) + { + sendPacket(new RecipeShopSellList(this, temp)); + } + } + else // _interactTarget=null should never happen but one never knows ^^; + { + target.onAction(this); + } + } + + /** + * Manages AutoLoot Task.
    + *
      + *
    • Send a system message to the player.
    • + *
    • Add the item to the player's inventory.
    • + *
    • Send a Server->Client packet InventoryUpdate to this player with NewItem (use a new slot) or ModifiedItem (increase amount).
    • + *
    • Send a Server->Client packet StatusUpdate to this player with current weight.
    • + *
    + * Caution: If a party is in progress, distribute the items between the party members! + * @param target the NPC dropping the item + * @param itemId the item ID + * @param itemCount the item count + */ + public void doAutoLoot(Attackable target, int itemId, int itemCount) + { + if (isInParty() && !ItemData.getInstance().getTemplate(itemId).hasExImmediateEffect()) + { + _party.distributeItem(this, itemId, itemCount, false, target); + } + else if (itemId == Inventory.ADENA_ID) + { + addAdena(ItemProcessType.LOOT, itemCount, target, true); + } + else + { + addItem(ItemProcessType.LOOT, itemId, itemCount, target, true); + } + } + + /** + * Method overload for {@link Player#doAutoLoot(Attackable, int, int)} + * @param target the NPC dropping the item + * @param item the item holder + */ + public void doAutoLoot(Attackable target, ItemHolder item) + { + doAutoLoot(target, item.getId(), item.getCount()); + } + + /** + * Manage Pickup Task. Actions: + *
  • Send a Server->Client packet StopMove to this Player
  • + *
  • Remove the Item from the world and send server->client GetItem packets
  • + *
  • Send a System Message to the Player : YOU_PICKED_UP_S1_ADENA or YOU_PICKED_UP_S1_S2
  • + *
  • Add the Item to the Player inventory
  • + *
  • Send a Server->Client packet InventoryUpdate to this Player with NewItem (use a new slot) or ModifiedItem (increase amount)
  • + *
  • Send a Server->Client packet StatusUpdate to this Player with current weight
  • Caution: If a Party is in progress, distribute Items between party members + * @param object The Item to pick up + */ + @Override + public void doPickupItem(WorldObject object) + { + if (isAlikeDead() || _isFakeDeath) + { + return; + } + + if (getActiveTradeList() != null) + { + sendPacket(SystemMessageId.YOU_CANNOT_PICK_UP_OR_USE_ITEMS_WHILE_TRADING); + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + + // Set the AI Intention to IDLE. + getAI().setIntentionIdle(); + + // Check if the WorldObject to pick up is a Item. + if (!object.isItem()) + { + // Do not try to pickup anything that is not an item :) + LOGGER.warning(this + " trying to pickup wrong target." + getTarget()); + return; + } + + final Item target = (Item) object; + sendPacket(new StopMove(this)); + SystemMessage smsg = null; + synchronized (target) + { + // Check if the target to pick up is visible. + if (!target.isSpawned()) + { + // Send a Server->Client packet ActionFailed to this Player. + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + + if (!target.getDropProtection().tryPickUp(this)) + { + sendPacket(ActionFailed.STATIC_PACKET); + smsg = new SystemMessage(SystemMessageId.YOU_HAVE_FAILED_TO_PICK_UP_S1); + smsg.addItemName(target); + sendPacket(smsg); + return; + } + + if (((isInParty() && (_party.getDistributionType() == PartyDistributionType.FINDERS_KEEPERS)) || !isInParty()) && !_inventory.validateCapacity(target)) + { + sendPacket(ActionFailed.STATIC_PACKET); + sendPacket(SystemMessageId.YOUR_INVENTORY_IS_FULL); + return; + } + + if (isInvisible() && !isGM()) + { + return; + } + + if ((target.getOwnerId() != 0) && (target.getOwnerId() != getObjectId()) && !isInLooterParty(target.getOwnerId())) + { + if (target.getId() == Inventory.ADENA_ID) + { + smsg = new SystemMessage(SystemMessageId.YOU_HAVE_FAILED_TO_PICK_UP_S1_ADENA); + smsg.addInt(target.getCount()); + } + else if (target.getCount() > 1) + { + smsg = new SystemMessage(SystemMessageId.YOU_HAVE_FAILED_TO_PICK_UP_S2_S1_S); + smsg.addItemName(target); + smsg.addInt(target.getCount()); + } + else + { + smsg = new SystemMessage(SystemMessageId.YOU_HAVE_FAILED_TO_PICK_UP_S1); + smsg.addItemName(target); + } + + sendPacket(ActionFailed.STATIC_PACKET); + sendPacket(smsg); + return; + } + + if ((target.getItemLootShedule() != null) && ((target.getOwnerId() == getObjectId()) || isInLooterParty(target.getOwnerId()))) + { + target.resetOwnerTimer(); + } + + // Remove the Item from the world and send server->client GetItem packets. + target.pickupMe(this); + if (GeneralConfig.SAVE_DROPPED_ITEM) + { + ItemsOnGroundManager.getInstance().removeObject(target); + } + } + + // Auto use herbs - pick up. + if (target.getTemplate().hasExImmediateEffect()) + { + final IItemHandler handler = ItemHandler.getInstance().getHandler(target.getEtcItem()); + if (handler != null) + { + handler.onItemUse(this, target, false); + } + else + { + LOGGER.warning("No item handler registered for item ID: " + target.getId() + "."); + } + + ItemManager.destroyItem(null, target, this, null); + } + // Cursed Weapons are not distributed. + else if (CursedWeaponsManager.getInstance().isCursed(target.getId())) + { + addItem(ItemProcessType.PICKUP, target, null, true); + } + else + { + // If item is instance of ArmorType or WeaponType broadcast an "Attention" system message. + if ((target.getItemType() instanceof ArmorType) || (target.getItemType() instanceof WeaponType)) + { + if (target.isEnchanted()) + { + smsg = new SystemMessage(SystemMessageId.ATTENTION_S1_PICKED_UP_S2_S3); + smsg.addPcName(this); + smsg.addInt(target.getEnchantLevel()); + } + else + { + smsg = new SystemMessage(SystemMessageId.ATTENTION_S1_PICKED_UP_S2); + smsg.addPcName(this); + } + + smsg.addItemName(target.getId()); + broadcastPacket(smsg); + } + + // Check if a Party is in progress. + if (isInParty()) + { + _party.distributeItem(this, target); + } + else if ((target.getId() == Inventory.ADENA_ID) && (_inventory.getAdenaInstance() != null)) + { + addAdena(ItemProcessType.PICKUP, target.getCount(), null, true); + ItemManager.destroyItem(ItemProcessType.PICKUP, target, this, null); + } + else + { + addItem(ItemProcessType.PICKUP, target, null, true); + + // Auto-Equip arrows/bolts if player has a bow/crossbow and player picks up arrows/bolts. + final Item weapon = _inventory.getPaperdollItem(Inventory.PAPERDOLL_RHAND); + if (weapon != null) + { + final EtcItem etcItem = target.getEtcItem(); + if (etcItem != null) + { + final EtcItemType itemType = etcItem.getItemType(); + if ((weapon.getItemType() == WeaponType.BOW) && (itemType == EtcItemType.ARROW)) + { + checkAndEquipArrows(); + } + } + } + } + } + } + + @Override + public void doAttack(Creature target) + { + super.doAttack(target); + setRecentFakeDeath(false); + if (target.isFakePlayer() && !FakePlayersConfig.FAKE_PLAYER_AUTO_ATTACKABLE) + { + updatePvPStatus(); + } + } + + @Override + public void doCast(Skill skill) + { + if ((_currentSkill != null) && !checkUseMagicConditions(skill, _currentSkill.isCtrlPressed(), _currentSkill.isShiftPressed())) + { + setCastingNow(false); + setCastingSimultaneouslyNow(false); + return; + } + + super.doCast(skill); + setRecentFakeDeath(false); + } + + public boolean canOpenPrivateStore() + { + if ((PrivateStoreRangeConfig.SHOP_MIN_RANGE_FROM_NPC > 0) || (PrivateStoreRangeConfig.SHOP_MIN_RANGE_FROM_PLAYER > 0)) + { + if (World.getFirstVisibleObject(this, Creature.class, creature -> (creature.getMinShopDistance() > 0) && LocationUtil.checkIfInRange(creature.getMinShopDistance(), this, creature, true)) != null) + { + sendPacket(new SystemMessage(SystemMessageId.YOU_CANNOT_OPEN_A_PRIVATE_STORE_HERE)); + return false; + } + } + + return !isAlikeDead() && !_inOlympiadMode && !isMounted() && !isInsideZone(ZoneId.NO_STORE) && !isCastingNow(); + } + + @Override + public int getMinShopDistance() + { + return _waitTypeSitting ? PrivateStoreRangeConfig.SHOP_MIN_RANGE_FROM_PLAYER : 0; + } + + public void tryOpenPrivateBuyStore() + { + // Player shouldn't be able to set stores if he/she is alike dead (dead or fake death). + if (canOpenPrivateStore()) + { + if ((_privateStoreType == PrivateStoreType.BUY) || (_privateStoreType == PrivateStoreType.BUY_MANAGE)) + { + setPrivateStoreType(PrivateStoreType.NONE); + } + + if (_privateStoreType == PrivateStoreType.NONE) + { + if (_waitTypeSitting) + { + standUp(); + } + + setPrivateStoreType(PrivateStoreType.BUY_MANAGE); + sendPacket(new PrivateStoreManageListBuy(this)); + } + } + else + { + if (isInsideZone(ZoneId.NO_STORE)) + { + sendPacket(SystemMessageId.YOU_CANNOT_OPEN_A_PRIVATE_STORE_HERE); + } + + sendPacket(ActionFailed.STATIC_PACKET); + } + } + + public void tryOpenPrivateSellStore(boolean isPackageSale) + { + // Player shouldn't be able to set stores if he/she is alike dead (dead or fake death). + if (canOpenPrivateStore()) + { + if ((_privateStoreType == PrivateStoreType.SELL) || (_privateStoreType == PrivateStoreType.SELL_MANAGE) || (_privateStoreType == PrivateStoreType.PACKAGE_SELL)) + { + setPrivateStoreType(PrivateStoreType.NONE); + } + + if (_privateStoreType == PrivateStoreType.NONE) + { + if (_waitTypeSitting) + { + standUp(); + } + + setPrivateStoreType(PrivateStoreType.SELL_MANAGE); + sendPacket(new PrivateStoreManageListSell(this, isPackageSale)); + } + } + else + { + if ((_privateStoreType != PrivateStoreType.NONE) && !isAlikeDead()) + { + setPrivateStoreType(PrivateStoreType.NONE); + if (_waitTypeSitting) + { + standUp(); + } + return; + } + + if (isInsideZone(ZoneId.NO_STORE)) + { + sendPacket(SystemMessageId.YOU_CANNOT_OPEN_A_PRIVATE_STORE_HERE); + } + + sendPacket(ActionFailed.STATIC_PACKET); + } + } + + public PreparedListContainer getMultiSell() + { + return _currentMultiSell; + } + + public void setMultiSell(PreparedListContainer list) + { + _currentMultiSell = list; + } + + /** + * Set a target. Actions: + *
      + *
    • Remove the Player from the _statusListener of the old target if it was a Creature
    • + *
    • Add the Player to the _statusListener of the new target if it's a Creature
    • + *
    • Target the new WorldObject (add the target to the Player _target, _knownObject and Player to _KnownObject of the WorldObject)
    • + *
    + * @param worldObject The WorldObject to target + */ + @Override + public void setTarget(WorldObject worldObject) + { + WorldObject newTarget = worldObject; + if (newTarget != null) + { + final boolean isInParty = newTarget.isPlayer() && isInParty() && _party.containsPlayer(newTarget.asPlayer()); + + // Prevents /target exploiting. + if (!isInParty && (Math.abs(newTarget.getZ() - getZ()) > 3000)) + { + newTarget = null; + } + + // Check if the new target is visible. + if ((newTarget != null) && !isInParty && !newTarget.isSpawned()) + { + newTarget = null; + } + + // Vehicles cannot be targeted. + if (!isGM() && (newTarget instanceof Vehicle)) + { + newTarget = null; + } + } + + // Get the current target. + final WorldObject oldTarget = getTarget(); + if (oldTarget != null) + { + if (oldTarget.equals(newTarget)) // no target change? + { + // Validate location of the target. + if ((newTarget != null) && (newTarget.getObjectId() != getObjectId())) + { + sendPacket(new ValidateLocation(newTarget)); + } + return; + } + + // Remove the target from the status listener. + oldTarget.removeStatusListener(this); + } + + if ((newTarget != null) && newTarget.isCreature()) + { + final Creature target = newTarget.asCreature(); + + // Validate location of the new target. + if (newTarget.getObjectId() != getObjectId()) + { + sendPacket(new ValidateLocation(target)); + } + + // Show the client his new target. + sendPacket(new MyTargetSelected(this, target)); + + // Register target to listen for hp changes. + target.addStatusListener(this); + + // Send max/current hp. + final StatusUpdate su = new StatusUpdate(target); + su.addAttribute(StatusUpdate.MAX_HP, target.getMaxHp()); + su.addAttribute(StatusUpdate.CUR_HP, (int) target.getCurrentHp()); + sendPacket(su); + + // To others the new target, and not yourself! + World.broadcastToVisiblePlayers(this, new TargetSelected(getObjectId(), newTarget.getObjectId(), getX(), getY(), getZ())); + } + + // Target was removed? + if ((newTarget == null) && (getTarget() != null)) + { + broadcastPacket(new TargetUnselected(this)); + } + + // Target the new WorldObject (add the target to the Player _target, _knownObject and Player to _KnownObject of the WorldObject). + super.setTarget(newTarget); + } + + /** + * Return the active weapon instance (always equipped in the right hand). + */ + @Override + public Item getActiveWeaponInstance() + { + return _inventory.getPaperdollItem(Inventory.PAPERDOLL_RHAND); + } + + /** + * Return the active weapon item (always equipped in the right hand). + */ + @Override + public Weapon getActiveWeaponItem() + { + final Item weapon = getActiveWeaponInstance(); + return weapon == null ? _fistsWeaponItem : (Weapon) weapon.getTemplate(); + } + + public Item getChestArmorInstance() + { + return _inventory.getPaperdollItem(Inventory.PAPERDOLL_CHEST); + } + + public Item getLegsArmorInstance() + { + return _inventory.getPaperdollItem(Inventory.PAPERDOLL_LEGS); + } + + public Armor getActiveChestArmorItem() + { + final Item armor = getChestArmorInstance(); + return armor == null ? null : (Armor) armor.getTemplate(); + } + + public Armor getActiveLegsArmorItem() + { + final Item legs = getLegsArmorInstance(); + return legs == null ? null : (Armor) legs.getTemplate(); + } + + public boolean isWearingHeavyArmor() + { + final Item legs = getLegsArmorInstance(); + final Item armor = getChestArmorInstance(); + if ((armor != null) && (legs != null) && (legs.getItemType() == ArmorType.HEAVY) && (armor.getItemType() == ArmorType.HEAVY)) + { + return true; + } + + if ((armor != null) && (getInventory().getPaperdollItem(Inventory.PAPERDOLL_CHEST).getTemplate().getBodyPart() == BodyPart.FULL_ARMOR) && (armor.getItemType() == ArmorType.HEAVY)) + { + return true; + } + + return false; + } + + public boolean isWearingLightArmor() + { + final Item legs = getLegsArmorInstance(); + final Item armor = getChestArmorInstance(); + if ((armor != null) && (legs != null) && (legs.getItemType() == ArmorType.LIGHT) && (armor.getItemType() == ArmorType.LIGHT)) + { + return true; + } + + if ((armor != null) && (_inventory.getPaperdollItem(Inventory.PAPERDOLL_CHEST).getTemplate().getBodyPart() == BodyPart.FULL_ARMOR) && (armor.getItemType() == ArmorType.LIGHT)) + { + return true; + } + + return false; + } + + public boolean isWearingMagicArmor() + { + final Item legs = getLegsArmorInstance(); + final Item armor = getChestArmorInstance(); + if ((armor != null) && (legs != null) && (legs.getItemType() == ArmorType.MAGIC) && (armor.getItemType() == ArmorType.MAGIC)) + { + return true; + } + + if ((armor != null) && (_inventory.getPaperdollItem(Inventory.PAPERDOLL_CHEST).getTemplate().getBodyPart() == BodyPart.FULL_ARMOR) && (armor.getItemType() == ArmorType.MAGIC)) + { + return true; + } + + return false; + } + + public boolean isWearingFormalWear() + { + final Item formalWear = getInventory().getPaperdollItem(Inventory.PAPERDOLL_CHEST); + return (formalWear != null) && (formalWear.getTemplate().getBodyPart() == BodyPart.ALLDRESS); + } + + public boolean isMarried() + { + return _married; + } + + public void setMarried(boolean value) + { + _married = value; + } + + public boolean isEngageRequest() + { + return _engagerequest; + } + + public void setEngageRequest(boolean state, int playerid) + { + _engagerequest = state; + _engageid = playerid; + } + + public void setMarryRequest(boolean value) + { + _marryrequest = value; + } + + public boolean isMarryRequest() + { + return _marryrequest; + } + + public void setMarryAccepted(boolean value) + { + _marryaccepted = value; + } + + public boolean isMarryAccepted() + { + return _marryaccepted; + } + + public int getEngageId() + { + return _engageid; + } + + public int getPartnerId() + { + return _partnerId; + } + + public void setPartnerId(int partnerid) + { + _partnerId = partnerid; + } + + public int getCoupleId() + { + return _coupleId; + } + + public void setCoupleId(int coupleId) + { + _coupleId = coupleId; + } + + public void engageAnswer(int answer) + { + if (!_engagerequest || (_engageid == 0)) + { + return; + } + + final Player ptarget = World.getPlayer(_engageid); + setEngageRequest(false, 0); + if (ptarget != null) + { + if (answer == 1) + { + CoupleManager.getInstance().createCouple(ptarget, Player.this); + ptarget.sendMessage("Request to Engage has been >ACCEPTED<"); + } + else + { + ptarget.sendMessage("Request to Engage has been >DENIED + * Arrows, Shield.. + */ + @Override + public ItemTemplate getSecondaryWeaponItem() + { + final Item item = _inventory.getPaperdollItem(Inventory.PAPERDOLL_LHAND); + return item != null ? item.getTemplate() : null; + } + + /** + * Kill the Creature, Apply Death Penalty, Manage gain/loss Karma and Item Drop. Actions: + *
  • Reduce the Experience of the Player in function of the calculated Death Penalty
  • + *
  • If necessary, unsummon the Pet of the killed Player
  • + *
  • Manage Karma gain for attacker and Karam loss for the killed Player
  • + *
  • If the killed Player has Karma, manage Drop Item
  • + *
  • Kill the Player

  • + * @param killer + */ + @Override + public boolean doDie(Creature killer) + { + // Kill the Player. + if (!super.doDie(killer)) + { + return false; + } + + if (isMounted()) + { + stopFeed(); + } + + // synchronized (this) + // { + if (_isFakeDeath) + { + stopFakeDeath(true); + } + // } + + if (killer != null) + { + final Player pk = killer.asPlayer(); + final boolean fpcKill = killer.isFakePlayer(); + if ((pk != null) || fpcKill) + { + if (pk != null) + { + if (EventDispatcher.getInstance().hasListener(EventType.ON_PLAYER_PVP_KILL, this)) + { + EventDispatcher.getInstance().notifyEventAsync(new OnPlayerPvPKill(pk, this), this); + } + + // pvp/pk item rewards + if (!(PvpRewardItemConfig.DISABLE_REWARDS_IN_INSTANCES && (getInstanceId() != 0)) && // + !(PvpRewardItemConfig.DISABLE_REWARDS_IN_PVP_ZONES && isInsideZone(ZoneId.PVP))) + { + // pvp + if (PvpRewardItemConfig.REWARD_PVP_ITEM && (_pvpFlag != 0)) + { + pk.addItem(ItemProcessType.REWARD, PvpRewardItemConfig.REWARD_PVP_ITEM_ID, PvpRewardItemConfig.REWARD_PVP_ITEM_AMOUNT, this, PvpRewardItemConfig.REWARD_PVP_ITEM_MESSAGE); + } + + // pk + if (PvpRewardItemConfig.REWARD_PK_ITEM && (_pvpFlag == 0)) + { + pk.addItem(ItemProcessType.REWARD, PvpRewardItemConfig.REWARD_PK_ITEM_ID, PvpRewardItemConfig.REWARD_PK_ITEM_AMOUNT, this, PvpRewardItemConfig.REWARD_PK_ITEM_MESSAGE); + } + } + } + + // announce pvp/pk + if (PvpAnnounceConfig.ANNOUNCE_PK_PVP && (((pk != null) && !pk.isGM()) || fpcKill)) + { + String msg = ""; + if (_pvpFlag == 0) + { + msg = PvpAnnounceConfig.ANNOUNCE_PK_MSG.replace("$killer", pk != null ? pk.getName() : killer.getName()).replace("$target", getName()); + if (PvpAnnounceConfig.ANNOUNCE_PK_PVP_NORMAL_MESSAGE) + { + final SystemMessage sm = new SystemMessage(SystemMessageId.S1); + sm.addString(msg); + World.broadcastToAllOnlinePlayers(sm); + } + else + { + World.broadcastToAllOnlinePlayers(msg, false); + } + } + else if (_pvpFlag != 0) + { + msg = PvpAnnounceConfig.ANNOUNCE_PVP_MSG.replace("$killer", killer.getName()).replace("$target", getName()); + if (PvpAnnounceConfig.ANNOUNCE_PK_PVP_NORMAL_MESSAGE) + { + final SystemMessage sm = new SystemMessage(SystemMessageId.S1); + sm.addString(msg); + World.broadcastToAllOnlinePlayers(sm); + } + else + { + World.broadcastToAllOnlinePlayers(msg, false); + } + } + } + + if (fpcKill && FakePlayersConfig.FAKE_PLAYER_KILL_KARMA && (_pvpFlag == 0) && (getKarma() <= 0)) + { + killer.setKarma(killer.getKarma() + 150); + killer.broadcastInfo(); + } + } + + broadcastStatusUpdate(); + + // Clear resurrect xp calculation. + setExpBeforeDeath(0); + + // Issues drop of Cursed Weapon. + if (isCursedWeaponEquipped()) + { + CursedWeaponsManager.getInstance().drop(_cursedWeaponEquippedId, killer); + } + else + { + final boolean insidePvpZone = isInsideZone(ZoneId.PVP); + final boolean insideSiegeZone = isInsideZone(ZoneId.SIEGE); + if ((pk == null) || !pk.isCursedWeaponEquipped()) + { + onDieDropItem(killer); // Check if any item should be dropped. + if (!insidePvpZone && !insideSiegeZone) + { + if ((pk != null) && (pk.getClan() != null) && (getClan() != null) && !isAcademyMember() && !(pk.isAcademyMember())) + { + if ((_clan.isAtWarWith(pk.getClanId()) && pk.getClan().isAtWarWith(_clan.getId())) || (isInSiege() && pk.isInSiege())) + { + if (AntiFeedManager.getInstance().check(killer, this)) + { + // When your reputation score is 0 or below, the other clan cannot acquire any reputation points. + if (_clan.getReputationScore() > 0) + { + pk.getClan().addReputationScore(FeatureConfig.REPUTATION_SCORE_PER_KILL); + } + + // when the opposing sides reputation score is 0 or below, your clans reputation score does not decrease + if (pk.getClan().getReputationScore() > 0) + { + _clan.takeReputationScore(FeatureConfig.REPUTATION_SCORE_PER_KILL); + } + } + } + } + } + + // Should not penalize player when lucky, in a non siege PvP zone or is in an event. + if (PlayerConfig.PLAYER_DELEVEL && !isLucky() && (insideSiegeZone || !insidePvpZone) && !isOnEvent()) + { + calculateDeathExpPenalty(killer, isAtWarWith(pk)); + } + } + } + } + + // Unsummon Cubics + if (!_cubics.isEmpty()) + { + for (Cubic cubic : _cubics.values()) + { + cubic.stopAction(); + cubic.cancelDisappear(); + } + + _cubics.clear(); + _cubicOrder.clear(); + } + + if (isChannelized()) + { + getSkillChannelized().abortChannelization(); + } + + if (isInParty() && _party.isInDimensionalRift()) + { + _party.getDimensionalRift().getDeadMemberList().add(this); + } + + if (_agathionId != 0) + { + setAgathionId(0); + } + + // calculate death penalty buff + calculateDeathPenaltyBuffLevel(killer); + + if (hasSummon()) + { + if (_summon.isBetrayed()) + { + sendPacket(SystemMessageId.YOUR_PET_SERVITOR_IS_UNRESPONSIVE_AND_WILL_NOT_OBEY_ANY_ORDERS); + } + + _summon.cancelAction(); + } + + stopRentPet(); + stopWaterTask(); + + AntiFeedManager.getInstance().setLastDeathTime(getObjectId()); + + // FIXME: Karma reduction tempfix. + if (getKarma() > 0) // && (killer instanceof GuardInstance)) + { + setKarma(getKarma() < 200 ? 0 : (int) (getKarma() - (getKarma() / 4))); + } + + if (PlayerConfig.DISCONNECT_AFTER_DEATH) + { + DecayTaskManager.getInstance().add(this); + } + + return true; + } + + private void onDieDropItem(Creature killer) + { + if (isOnEvent() || (killer == null)) + { + return; + } + + final Player pk = killer.asPlayer(); + if ((getKarma() <= 0) && (pk != null) && (pk.getClan() != null) && (getClan() != null) && (pk.getClan().isAtWarWith(_clanId) + + // || _clan.isAtWarWith(killer.asPlayer().getClanId()) + )) + { + return; + } + + if ((!isInsideZone(ZoneId.PVP) || (pk == null)) && (!isGM() || PvpConfig.KARMA_DROP_GM)) + { + boolean isKarmaDrop = false; + final boolean isKillerNpc = killer instanceof Npc; + final int pkLimit = PvpConfig.KARMA_PK_LIMIT; + int dropEquip = 0; + int dropEquipWeapon = 0; + int dropItem = 0; + int dropLimit = 0; + int dropPercent = 0; + if ((getKarma() > 0) && (_pkKills >= pkLimit)) + { + isKarmaDrop = true; + dropPercent = RatesConfig.KARMA_RATE_DROP; + dropEquip = RatesConfig.KARMA_RATE_DROP_EQUIP; + dropEquipWeapon = RatesConfig.KARMA_RATE_DROP_EQUIP_WEAPON; + dropItem = RatesConfig.KARMA_RATE_DROP_ITEM; + dropLimit = RatesConfig.KARMA_DROP_LIMIT; + } + else if (isKillerNpc && (getLevel() > 4) && !isFestivalParticipant()) + { + dropPercent = RatesConfig.PLAYER_RATE_DROP; + dropEquip = RatesConfig.PLAYER_RATE_DROP_EQUIP; + dropEquipWeapon = RatesConfig.PLAYER_RATE_DROP_EQUIP_WEAPON; + dropItem = RatesConfig.PLAYER_RATE_DROP_ITEM; + dropLimit = RatesConfig.PLAYER_DROP_LIMIT; + } + + if ((dropPercent > 0) && (Rnd.get(100) < dropPercent)) + { + int dropCount = 0; + int itemDropPercent = 0; + for (Item itemDrop : _inventory.getItems()) + { + // Don't drop + if (itemDrop.isShadowItem() || // do not drop Shadow Items + itemDrop.isTimeLimitedItem() || // do not drop Time Limited Items + !itemDrop.isDropable() || (itemDrop.getId() == Inventory.ADENA_ID) || // Adena + (itemDrop.getTemplate().getType2() == ItemTemplate.TYPE2_QUEST) || // Quest Items + (hasSummon() && (_summon.getControlObjectId() == itemDrop.getId())) || // Control Item of active pet + (Arrays.binarySearch(PvpConfig.KARMA_LIST_NONDROPPABLE_ITEMS, itemDrop.getId()) >= 0) || // Item listed in the non droppable item list + (Arrays.binarySearch(PvpConfig.KARMA_LIST_NONDROPPABLE_PET_ITEMS, itemDrop.getId()) >= 0 // Item listed in the non droppable pet item list + )) + { + continue; + } + + if (itemDrop.isEquipped()) + { + // Set proper chance according to Item type of equipped Item. + itemDropPercent = itemDrop.getTemplate().getType2() == ItemTemplate.TYPE2_WEAPON ? dropEquipWeapon : dropEquip; + } + else + { + itemDropPercent = dropItem; // Item in inventory + } + + // NOTE: Each time an item is dropped, the chance of another item being dropped gets lesser (dropCount * 2) + if (Rnd.get(100) < itemDropPercent) + { + if (itemDrop.isEquipped()) + { + _inventory.unEquipItemInSlot(itemDrop.getLocationSlot()); + } + + dropItem(ItemProcessType.DEATH, itemDrop, killer, true); + sendItemList(false); + + if (isKarmaDrop) + { + LOGGER.warning(getName() + " has karma and dropped id = " + itemDrop.getId() + ", count = " + itemDrop.getCount()); + } + else + { + LOGGER.warning(getName() + " dropped id = " + itemDrop.getId() + ", count = " + itemDrop.getCount()); + } + + if (++dropCount >= dropLimit) + { + break; + } + } + } + } + } + } + + public void onKillUpdatePvPKarma(Creature target) + { + if ((target == null) || !target.isPlayable()) + { + return; + } + + // Avoid nulls && check if player != killedPlayer. + final Player killedPlayer = target.asPlayer(); + if ((killedPlayer == null) || (killedPlayer == this)) + { + return; + } + + // Cursed weapons progress. + if (isCursedWeaponEquipped() && target.isPlayer()) + { + CursedWeaponsManager.getInstance().increaseKills(_cursedWeaponEquippedId); + return; + } + + // Olympiad support + if (isInOlympiadMode() || killedPlayer.isInOlympiadMode()) + { + return; + } + + // Duel support + if (isInDuel() && killedPlayer.isInDuel()) + { + return; + } + + // If both players are in SIEGE zone just increase siege kills/deaths. + if (target.isPlayer() && isInsideZone(ZoneId.SIEGE) && killedPlayer.isInsideZone(ZoneId.SIEGE)) + { + if (!isSiegeFriend(killedPlayer)) + { + final Clan targetClan = killedPlayer.getClan(); + if ((_clan != null) && (targetClan != null)) + { + _clan.addSiegeKill(); + targetClan.addSiegeDeath(); + } + } + return; + } + + // Do nothing when in PVP zone. + if (isInsideZone(ZoneId.PVP) || target.isInsideZone(ZoneId.PVP)) + { + return; + } + + // Check if it's pvp. + if (checkIfPvP(target) && (killedPlayer.getPvpFlag() != 0)) + { + increasePvpKills(target); + } + else + { + // Target player doesn't have pvp flag set. + // check about wars + if ((killedPlayer.getClan() != null) && (getClan() != null) && getClan().isAtWarWith(killedPlayer.getClanId()) && killedPlayer.getClan().isAtWarWith(getClanId()) && (killedPlayer.getPledgeType() != Clan.SUBUNIT_ACADEMY) && (getPledgeType() != Clan.SUBUNIT_ACADEMY)) + { + // 'Both way war' -> 'PvP Kill'. + increasePvpKills(target); + return; + } + + // 'No war' or 'One way war' -> 'Normal PK'. + if (killedPlayer.getKarma() > 0) // Target player has karma. + { + if (PvpConfig.KARMA_AWARD_PK_KILL) + { + increasePvpKills(target); + } + } + else if (killedPlayer.getPvpFlag() == 0) // Target player doesn't have karma. + { + if (FactionSystemConfig.FACTION_SYSTEM_ENABLED) + { + if ((_isGood && killedPlayer.isGood()) || (_isEvil && killedPlayer.isEvil())) + { + increasePkKillsAndKarma(target); + } + } + else + { + increasePkKillsAndKarma(target); + } + + checkItemRestriction(); // Unequip adventurer items + } + } + } + + /** + * Increase the pvp kills count and send the info to the player + * @param target + */ + public void increasePvpKills(Creature target) + { + if (target.isPlayer() && AntiFeedManager.getInstance().check(this, target)) + { + setPvpKills(_pvpKills + 1); + updatePvpTitleAndColor(true); + + // Send a Server->Client UserInfo packet to attacker with its Karma and PK Counter. + updateUserInfo(); + } + } + + /** + * Increase pk count, karma and send the info to the player + * @param target + */ + public void increasePkKillsAndKarma(Creature target) + { + // Only playables can increase karma/pk. + if ((target == null) || !target.isPlayable()) + { + return; + } + + // Calculate new karma. (calculate karma before incrase pk count!). + setKarma(getKarma() + Formulas.calculateKarmaGain(_pkKills, target.isSummon())); + + // PK Points are increased only if you kill a player. + if (target.isPlayer()) + { + setPkKills(_pkKills + 1); + } + + // Update player's UI. + updateUserInfo(); + } + + public void updatePvpTitleAndColor(boolean broadcastInfo) + { + if (PvpTitleColorConfig.PVP_COLOR_SYSTEM_ENABLED && !FactionSystemConfig.FACTION_SYSTEM_ENABLED) // Faction system uses title colors. + { + if ((_pvpKills >= PvpTitleColorConfig.PVP_AMOUNT1) && (_pvpKills < PvpTitleColorConfig.PVP_AMOUNT2)) + { + setTitle("\u00AE " + PvpTitleColorConfig.TITLE_FOR_PVP_AMOUNT1 + " \u00AE"); + _appearance.setTitleColor(PvpTitleColorConfig.NAME_COLOR_FOR_PVP_AMOUNT1); + } + else if ((_pvpKills >= PvpTitleColorConfig.PVP_AMOUNT2) && (_pvpKills < PvpTitleColorConfig.PVP_AMOUNT3)) + { + setTitle("\u00AE " + PvpTitleColorConfig.TITLE_FOR_PVP_AMOUNT2 + " \u00AE"); + _appearance.setTitleColor(PvpTitleColorConfig.NAME_COLOR_FOR_PVP_AMOUNT2); + } + else if ((_pvpKills >= PvpTitleColorConfig.PVP_AMOUNT3) && (_pvpKills < PvpTitleColorConfig.PVP_AMOUNT4)) + { + setTitle("\u00AE " + PvpTitleColorConfig.TITLE_FOR_PVP_AMOUNT3 + " \u00AE"); + _appearance.setTitleColor(PvpTitleColorConfig.NAME_COLOR_FOR_PVP_AMOUNT3); + } + else if ((_pvpKills >= PvpTitleColorConfig.PVP_AMOUNT4) && (_pvpKills < PvpTitleColorConfig.PVP_AMOUNT5)) + { + setTitle("\u00AE " + PvpTitleColorConfig.TITLE_FOR_PVP_AMOUNT4 + " \u00AE"); + _appearance.setTitleColor(PvpTitleColorConfig.NAME_COLOR_FOR_PVP_AMOUNT4); + } + else if (_pvpKills >= PvpTitleColorConfig.PVP_AMOUNT5) + { + setTitle("\u00AE " + PvpTitleColorConfig.TITLE_FOR_PVP_AMOUNT5 + " \u00AE"); + _appearance.setTitleColor(PvpTitleColorConfig.NAME_COLOR_FOR_PVP_AMOUNT5); + } + + if (broadcastInfo) + { + broadcastTitleInfo(); + } + } + } + + public void updatePvPStatus() + { + if (isInsideZone(ZoneId.PVP)) + { + return; + } + + setPvpFlagLasts(System.currentTimeMillis() + PvpConfig.PVP_NORMAL_TIME); + if (_pvpFlag == 0) + { + startPvPFlag(); + } + } + + public void updatePvPStatus(Creature target) + { + final Player targetPlayer = target.asPlayer(); + if (targetPlayer == null) + { + return; + } + + if (this == targetPlayer) + { + return; + } + + if (FactionSystemConfig.FACTION_SYSTEM_ENABLED && target.isPlayer() && ((isGood() && targetPlayer.isEvil()) || (isEvil() && targetPlayer.isGood()))) + { + return; + } + + if (_isInDuel && (targetPlayer.getDuelId() == getDuelId())) + { + return; + } + + if ((!isInsideZone(ZoneId.PVP) || !target.isInsideZone(ZoneId.PVP)) && (targetPlayer.getKarma() == 0)) + { + if (checkIfPvP(targetPlayer)) + { + setPvpFlagLasts(System.currentTimeMillis() + PvpConfig.PVP_PVP_TIME); + } + else + { + setPvpFlagLasts(System.currentTimeMillis() + PvpConfig.PVP_NORMAL_TIME); + } + + if (_pvpFlag == 0) + { + startPvPFlag(); + } + } + } + + /** + * @return {@code true} if player has Lucky effect and is level 9 or less + */ + public boolean isLucky() + { + return (getLevel() <= 9) && isAffectedBySkill(CommonSkill.LUCKY.getId()); + } + + /** + * Restore the specified % of experience this Player has lost and sends a Server->Client StatusUpdate packet. + * @param restorePercent + */ + public void restoreExp(double restorePercent) + { + if (_expBeforeDeath > 0) + { + // Restore the specified % of lost experience. + getStat().addExp(Math.round(((_expBeforeDeath - getExp()) * restorePercent) / 100)); + setExpBeforeDeath(0); + + updateUserInfo(); + } + } + + /** + * Reduce the Experience (and level if necessary) of the Player in function of the calculated Death Penalty.
    + *
    + * Actions: + *
  • Calculate the Experience loss
  • + *
  • Set the value of _expBeforeDeath
  • + *
  • Set the new Experience value of the Player and Decrease its level if necessary
  • + *
  • Send a Server->Client StatusUpdate packet with its new Experience

  • + * @param killer + * @param atWar + */ + public void calculateDeathExpPenalty(Creature killer, boolean atWar) + { + final int level = getLevel(); + double percentLost = ExperienceLossData.getInstance().getPercentLost(getLevel()); + + // Calculate reduce exp lost stats. + if (killer != null) + { + if (killer.isRaid()) + { + percentLost *= calcStat(Stat.REDUCE_EXP_LOST_BY_RAID, 1); + } + else if (killer.isMonster()) + { + percentLost *= calcStat(Stat.REDUCE_EXP_LOST_BY_MOB, 1); + } + else if (killer.isPlayable()) + { + percentLost *= calcStat(Stat.REDUCE_EXP_LOST_BY_PVP, 1); + } + } + + // Calculate karma loss configuration. + if (getKarma() > 0) + { + percentLost *= RatesConfig.RATE_KARMA_EXP_LOST; + } + + // Get the total exp for the current player level. + long currentLevelExp = 0; + if (!isOnEvent()) + { + final int maxLevel = ExperienceData.getInstance().getMaxLevel(); + if (level < maxLevel) + { + currentLevelExp = getStat().getExpForLevel(level + 1) - getStat().getExpForLevel(level); + } + else + { + currentLevelExp = getStat().getExpForLevel(maxLevel) - getStat().getExpForLevel(maxLevel - 1); + } + } + + // Calculate the experience loss. + long lostExp = 0; + if (currentLevelExp > 0) + { + lostExp = Math.round((currentLevelExp * percentLost) / 100); + + // Capping loss to 10% of current level experience. + lostExp = Math.min(lostExp, currentLevelExp / 10); + + // Reduce experience lost for festival and war kills. + if (isFestivalParticipant() || atWar) + { + lostExp /= 4; + } + } + + setExpBeforeDeath(getExp()); + getStat().removeExp(lostExp); + } + + public boolean isPartyWaiting() + { + return PartyMatchWaitingList.getInstance().getPlayers().contains(this); + } + + public void setPartyRoom(int id) + { + _partyroom = id; + } + + public int getPartyRoom() + { + return _partyroom; + } + + public boolean isInPartyMatchRoom() + { + return _partyroom > 0; + } + + /** + * Stop the HP/MP/CP Regeneration task. Actions: + *
  • Set the RegenActive flag to False
  • + *
  • Stop the HP/MP/CP Regeneration task
  • + */ + public void stopAllTimers() + { + stopHpMpRegeneration(); + stopWarnUserTakeBreak(); + stopWaterTask(); + stopFeed(); + storePetFood(_mountNpcId); + stopRentPet(); + stopPvpRegTask(); + stopChargeTask(); + stopFameTask(); + stopVitalityTask(); + } + + /** + * @return {@code true} if the character has a summon, {@code false} otherwise + */ + public boolean hasSummon() + { + return _summon != null; + } + + /** + * @return {@code true} if the character has a pet, {@code false} otherwise + */ + public boolean hasPet() + { + return (_summon != null) && _summon.isPet(); + } + + /** + * @return {@code true} if the character has a servitor, {@code false} otherwise + */ + public boolean hasServitor() + { + return (_summon != null) && _summon.isServitor(); + } + + /** + * @return the Summon of the Player or null. + */ + public Summon getSummon() + { + return _summon; + } + + /** + * @return the Decoy of the Player or null. + */ + public Decoy getDecoy() + { + return _decoy; + } + + /** + * @return the Trap of the Player or null. + */ + public Trap getTrap() + { + return _trap; + } + + /** + * Set the Summon of the Player. + * @param summon + */ + public void setPet(Summon summon) + { + _summon = summon; + } + + /** + * Set the Decoy of the Player. + * @param decoy + */ + public void setDecoy(Decoy decoy) + { + _decoy = decoy; + } + + /** + * Set the Trap of this Player + * @param trap + */ + public void setTrap(Trap trap) + { + _trap = trap; + } + + /** + * @return the Summon of the Player or null. + */ + public Collection getTrainedBeasts() + { + return _tamedBeast; + } + + /** + * Set the Summon of the Player. + * @param tamedBeast + */ + public void addTrainedBeast(TamedBeast tamedBeast) + { + if (_tamedBeast == null) + { + _tamedBeast = ConcurrentHashMap.newKeySet(); + } + + _tamedBeast.add(tamedBeast); + } + + /** + * @return the Player requester of a transaction (ex : FriendInvite, JoinAlly, JoinParty...). + */ + public RequestPartner getRequest() + { + return _request; + } + + /** + * Set the Player requester of a transaction (ex : FriendInvite, JoinAlly, JoinParty...). + * @param requester + */ + public void setActiveRequester(Player requester) + { + _activeRequester = requester; + } + + /** + * @return the Player requester of a transaction (ex : FriendInvite, JoinAlly, JoinParty...). + */ + public Player getActiveRequester() + { + final Player requester = _activeRequester; + if ((requester != null) && requester.isRequestExpired() && (_activeTradeList == null)) + { + _activeRequester = null; + } + + return _activeRequester; + } + + /** + * @return True if a transaction is in progress. + */ + public boolean isProcessingRequest() + { + return (getActiveRequester() != null) || (_requestExpireTime > GameTimeTaskManager.getInstance().getGameTicks()); + } + + /** + * @return True if a transaction is in progress. + */ + public boolean isProcessingTransaction() + { + return (getActiveRequester() != null) || (_activeTradeList != null) || (_requestExpireTime > GameTimeTaskManager.getInstance().getGameTicks()); + } + + /** + * Select the Warehouse to be used in next activity. + * @param partner + */ + public void onTransactionRequest(Player partner) + { + _requestExpireTime = GameTimeTaskManager.getInstance().getGameTicks() + (REQUEST_TIMEOUT * GameTimeTaskManager.TICKS_PER_SECOND); + partner.setActiveRequester(this); + } + + /** + * Return true if last request is expired. + * @return + */ + public boolean isRequestExpired() + { + return _requestExpireTime <= GameTimeTaskManager.getInstance().getGameTicks(); + } + + /** + * Select the Warehouse to be used in next activity. + */ + public void onTransactionResponse() + { + _requestExpireTime = 0; + } + + /** + * Select the Warehouse to be used in next activity. + * @param warehouse + */ + public void setActiveWarehouse(ItemContainer warehouse) + { + _activeWarehouse = warehouse; + } + + /** + * @return active Warehouse. + */ + public ItemContainer getActiveWarehouse() + { + return _activeWarehouse; + } + + /** + * Select the TradeList to be used in next activity. + * @param tradeList + */ + public void setActiveTradeList(TradeList tradeList) + { + _activeTradeList = tradeList; + } + + /** + * @return active TradeList. + */ + public TradeList getActiveTradeList() + { + return _activeTradeList; + } + + public void onTradeStart(Player partner) + { + _activeTradeList = new TradeList(this); + _activeTradeList.setPartner(partner); + + final SystemMessage msg = new SystemMessage(SystemMessageId.YOU_BEGIN_TRADING_WITH_S1); + msg.addPcName(partner); + sendPacket(msg); + sendPacket(new TradeStart(this)); + } + + public void onTradeConfirm(Player partner) + { + final SystemMessage msg = new SystemMessage(SystemMessageId.S1_HAS_CONFIRMED_THE_TRADE); + msg.addPcName(partner); + sendPacket(msg); + sendPacket(TradeOtherDone.STATIC_PACKET); + } + + public void onTradeCancel(Player partner) + { + if (_activeTradeList == null) + { + return; + } + + _activeTradeList.lock(); + _activeTradeList = null; + sendPacket(new TradeDone(0)); + final SystemMessage msg = new SystemMessage(SystemMessageId.S1_HAS_CANCELED_THE_TRADE); + msg.addPcName(partner); + sendPacket(msg); + } + + public void onTradeFinish(boolean successfull) + { + _activeTradeList = null; + sendPacket(new TradeDone(1)); + if (successfull) + { + sendPacket(SystemMessageId.YOUR_TRADE_IS_SUCCESSFUL); + } + } + + public void startTrade(Player partner) + { + onTradeStart(partner); + partner.onTradeStart(this); + } + + public void cancelActiveTrade() + { + if (_activeTradeList == null) + { + return; + } + + final Player partner = _activeTradeList.getPartner(); + if (partner != null) + { + partner.onTradeCancel(this); + } + + onTradeCancel(this); + } + + public boolean hasManufactureShop() + { + return (_manufactureItems != null) && !_manufactureItems.isEmpty(); + } + + /** + * Get the manufacture items map of this player. + * @return the the manufacture items map + */ + public Map getManufactureItems() + { + if (_manufactureItems == null) + { + synchronized (this) + { + _manufactureItems = Collections.synchronizedMap(new LinkedHashMap<>()); + } + } + + return _manufactureItems; + } + + /** + * Get the store name, if any. + * @return the store name + */ + public String getStoreName() + { + return _storeName; + } + + /** + * Set the store name. + * @param name the store name to set + */ + public void setStoreName(String name) + { + _storeName = name == null ? "" : name; + } + + /** + * @return the _buyList object of the Player. + */ + public TradeList getSellList() + { + if (_sellList == null) + { + _sellList = new TradeList(this); + } + + return _sellList; + } + + /** + * @return the _buyList object of the Player. + */ + public TradeList getBuyList() + { + if (_buyList == null) + { + _buyList = new TradeList(this); + } + + return _buyList; + } + + /** + * Set the Private Store type of the Player. Values: + *
  • 0 : STORE_PRIVATE_NONE
  • + *
  • 1 : STORE_PRIVATE_SELL
  • + *
  • 2 : sellmanage
  • + *
  • 3 : STORE_PRIVATE_BUY
  • + *
  • 4 : buymanage
  • + *
  • 5 : STORE_PRIVATE_MANUFACTURE

  • + * @param privateStoreType + */ + public void setPrivateStoreType(PrivateStoreType privateStoreType) + { + _privateStoreType = privateStoreType; + if (OfflineTradeConfig.OFFLINE_DISCONNECT_FINISHED && (privateStoreType == PrivateStoreType.NONE) && ((_client == null) || _client.isDetached())) + { + OfflineTraderTable.getInstance().removeTrader(getObjectId()); + Disconnection.of(this).storeAndDelete(); + } + } + + /** + * Values: + *
  • 0 : STORE_PRIVATE_NONE
  • + *
  • 1 : STORE_PRIVATE_SELL
  • + *
  • 2 : sellmanage
  • + *
  • 3 : STORE_PRIVATE_BUY
  • + *
  • 4 : buymanage
  • + *
  • 5 : STORE_PRIVATE_MANUFACTURE
  • + * @return the Private Store type of the Player. + */ + public PrivateStoreType getPrivateStoreType() + { + return _privateStoreType; + } + + /** + * Set the _clan object, _clanId, _clanLeader Flag and title of the Player. + * @param clan + */ + public void setClan(Clan clan) + { + _clan = clan; + if (clan == null) + { + setTitle(""); + _clanId = 0; + _clanPrivileges = new ClanPrivileges(); + _pledgeType = 0; + _powerGrade = 0; + _lvlJoinedAcademy = 0; + _apprentice = 0; + _sponsor = 0; + _activeWarehouse = null; + return; + } + + if (!clan.isMember(getObjectId())) + { + // Char has been kicked from clan. + setClan(null); + return; + } + + _clanId = clan.getId(); + } + + /** + * @return the _clan object of the Player. + */ + @Override + public Clan getClan() + { + return _clan; + } + + /** + * @return True if the Player is the leader of its clan. + */ + public boolean isClanLeader() + { + return (_clan != null) && (getObjectId() == _clan.getLeaderId()); + } + + /** + * Reduce the number of arrows owned by the Player and send it Server->Client Packet InventoryUpdate or ItemList (to unequip if the last arrow was consummed). + */ + @Override + protected void reduceArrowCount() + { + final Item arrows = _inventory.getPaperdollItem(Inventory.PAPERDOLL_LHAND); + if (arrows == null) + { + _inventory.unEquipItemInSlot(Inventory.PAPERDOLL_LHAND); + _arrowItem = null; + sendItemList(false); + return; + } + + // Adjust item quantity + if (arrows.getCount() > 1) + { + synchronized (arrows) + { + arrows.changeCount(null, -1, this, null); + arrows.setLastChange(Item.MODIFIED); + _inventory.refreshWeight(); + } + } + else + { + // Destroy entire item and save to database. + _inventory.destroyItem(ItemProcessType.NONE, arrows, this, null); + _inventory.unEquipItemInSlot(Inventory.PAPERDOLL_LHAND); + _arrowItem = null; + sendItemList(false); + return; + } + + final InventoryUpdate iu = new InventoryUpdate(); + iu.addModifiedItem(arrows); + sendInventoryUpdate(iu); + } + + /** + * Equip arrows needed in left hand and send a Server->Client packet ItemList to the Player then return True. + */ + @Override + protected boolean checkAndEquipArrows() + { + // Check if nothing is equipped in left hand. + if (_inventory.getPaperdollItem(Inventory.PAPERDOLL_LHAND) == null) + { + // Get the Item of the arrows needed for this bow. + _arrowItem = _inventory.findArrowForBow(getActiveWeaponItem()); + if (_arrowItem != null) + { + // Equip arrows needed in left hand. + _inventory.setPaperdollItem(Inventory.PAPERDOLL_LHAND, _arrowItem); + + // Send a Server->Client packet ItemList to this Player to update left hand equipement. + sendItemList(false); + } + } + else + { + // Get the Item of arrows equipped in left hand. + _arrowItem = _inventory.getPaperdollItem(Inventory.PAPERDOLL_LHAND); + } + + return _arrowItem != null; + } + + /** + * Disarm the player's weapon. + * @return {@code true} if the player was disarmed or doesn't have a weapon to disarm, {@code false} otherwise. + */ + public boolean disarmWeapons() + { + // If there is no weapon to disarm then return true. + final Item wpn = _inventory.getPaperdollItem(Inventory.PAPERDOLL_RHAND); + if (wpn == null) + { + return true; + } + + // Don't allow disarming a cursed weapon. + if (isCursedWeaponEquipped()) + { + return false; + } + + // Don't allow disarming if the weapon is force equip. + if (wpn.getWeaponItem().isForceEquip()) + { + return false; + } + + final Collection unequipped = _inventory.unEquipItemInBodySlotAndRecord(wpn.getTemplate().getBodyPart()); + final InventoryUpdate iu = new InventoryUpdate(); + for (Item itm : unequipped) + { + iu.addModifiedItem(itm); + } + + sendInventoryUpdate(iu); + + abortAttack(); + broadcastUserInfo(); + + // This can be 0 if the user pressed the right mousebutton twice very fast. + if (!unequipped.isEmpty()) + { + final SystemMessage sm; + final Item unequippedItem = unequipped.iterator().next(); + if (unequippedItem.isEnchanted()) + { + sm = new SystemMessage(SystemMessageId.THE_EQUIPMENT_S1_S2_HAS_BEEN_REMOVED); + sm.addInt(unequippedItem.getEnchantLevel()); + } + else + { + sm = new SystemMessage(SystemMessageId.S1_HAS_BEEN_DISARMED); + } + + sm.addItemName(unequippedItem); + sendPacket(sm); + } + + return true; + } + + /** + * Disarm the player's shield. + * @return {@code true}. + */ + public boolean disarmShield() + { + final Item sld = _inventory.getPaperdollItem(Inventory.PAPERDOLL_LHAND); + if (sld != null) + { + final Collection unequipped = _inventory.unEquipItemInBodySlotAndRecord(sld.getTemplate().getBodyPart()); + final InventoryUpdate iu = new InventoryUpdate(); + for (Item itm : unequipped) + { + iu.addModifiedItem(itm); + } + + sendInventoryUpdate(iu); + + abortAttack(); + broadcastUserInfo(); + + // This can be 0 if the user pressed the right mousebutton twice very fast. + if (!unequipped.isEmpty()) + { + SystemMessage sm = null; + final Item unequippedItem = unequipped.iterator().next(); + if (unequippedItem.isEnchanted()) + { + sm = new SystemMessage(SystemMessageId.THE_EQUIPMENT_S1_S2_HAS_BEEN_REMOVED); + sm.addInt(unequippedItem.getEnchantLevel()); + } + else + { + sm = new SystemMessage(SystemMessageId.S1_HAS_BEEN_DISARMED); + } + + sm.addItemName(unequippedItem); + sendPacket(sm); + } + } + + return true; + } + + public boolean mount(Summon pet) + { + // If mounts (excluding wyvern) are disallowed during siege, check if player is in a siege zone. + if (!FeatureConfig.ALLOW_MOUNTS_DURING_SIEGE && isInsideZone(ZoneId.SIEGE)) + { + final MountType type = MountType.findByNpcId(pet.getId()); + + // Only allow mounting if the pet is a Strider and the player is the castle lord of the current siege castle. + final boolean isAllowed = (type == MountType.STRIDER) && (CastleManager.getInstance().getCastle(this) != null) && isClanLeader() && (getClanId() == CastleManager.getInstance().getCastle(this).getOwnerId()); + + if (!isAllowed) + { + sendMessage("You cannot mount during a siege."); + return false; + } + } + + if (!disarmWeapons() || !disarmShield()) + { + return false; + } + + getEffectList().stopAllToggles(); + setMount(pet.getId(), pet.getLevel()); + setMountObjectID(pet.getControlObjectId()); + startFeed(pet.getId()); + broadcastPacket(new Ride(this)); + + // Notify self and others about speed change. + broadcastUserInfo(); + + pet.unSummon(this); + return true; + } + + public boolean mount(int npcId, int controlItemObjId, boolean useFood) + { + if (!disarmWeapons() || !disarmShield()) + { + return false; + } + + getEffectList().stopAllToggles(); + setMount(npcId, getLevel()); + setMountObjectID(controlItemObjId); + broadcastPacket(new Ride(this)); + + // Notify self and others about speed change. + broadcastUserInfo(); + if (useFood) + { + startFeed(npcId); + } + + return true; + } + + public boolean mountPlayer(Summon pet) + { + if ((pet != null) && pet.isMountable() && !isMounted() && !isBetrayed()) + { + if (isDead()) + { + // A strider cannot be ridden when dead. + sendPacket(ActionFailed.STATIC_PACKET); + sendPacket(SystemMessageId.A_STRIDER_CANNOT_BE_RIDDEN_WHEN_DEAD); + return false; + } + else if (pet.isDead()) + { + // A dead strider cannot be ridden. + sendPacket(ActionFailed.STATIC_PACKET); + sendPacket(SystemMessageId.A_DEAD_STRIDER_CANNOT_BE_RIDDEN); + return false; + } + else if (pet.isInCombat() || pet.isRooted()) + { + // A strider in battle cannot be ridden. + sendPacket(ActionFailed.STATIC_PACKET); + sendPacket(SystemMessageId.A_STRIDER_IN_BATTLE_CANNOT_BE_RIDDEN); + return false; + } + else if (isInCombat()) + { + // A strider cannot be ridden while in battle. + sendPacket(ActionFailed.STATIC_PACKET); + sendPacket(SystemMessageId.A_STRIDER_CANNOT_BE_RIDDEN_WHILE_IN_BATTLE); + return false; + } + else if (_waitTypeSitting) + { + // A strider can be ridden only when standing. + sendPacket(ActionFailed.STATIC_PACKET); + sendPacket(SystemMessageId.A_STRIDER_CAN_BE_RIDDEN_ONLY_WHEN_STANDING); + return false; + } + else if (_fishing) + { + // You can't mount, dismount, break and drop items while fishing. + sendPacket(ActionFailed.STATIC_PACKET); + sendPacket(SystemMessageId.YOU_CANNOT_DO_THAT_WHILE_FISHING_2); + return false; + } + else if (isCursedWeaponEquipped()) + { + // No message needed, player while transformed doesn't have mount action. + sendPacket(ActionFailed.STATIC_PACKET); + return false; + } + else if (pet.isHungry()) + { + sendPacket(ActionFailed.STATIC_PACKET); + sendPacket(SystemMessageId.A_HUNGRY_STRIDER_CANNOT_BE_MOUNTED_OR_DISMOUNTED); + return false; + } + else if (!LocationUtil.checkIfInRange(200, this, pet, true)) + { + sendPacket(ActionFailed.STATIC_PACKET); + sendPacket(SystemMessageId.YOU_ARE_TOO_FAR_AWAY_FROM_THE_STRIDER_TO_MOUNT_IT); + return false; + } + else if (!pet.isDead() && !isMounted()) + { + mount(pet); + sendSkillList(); // Refresh skill list after mounting. + } + } + else if (isRentedPet()) + { + stopRentPet(); + } + else if (isMounted()) + { + if ((_mountType == MountType.WYVERN) && isInsideZone(ZoneId.NO_LANDING) && !isGM()) + { + sendPacket(ActionFailed.STATIC_PACKET); + sendPacket(SystemMessageId.YOU_ARE_NOT_ALLOWED_TO_DISMOUNT_AT_THIS_LOCATION); + return false; + } + else if (isHungry()) + { + sendPacket(ActionFailed.STATIC_PACKET); + sendPacket(SystemMessageId.A_HUNGRY_STRIDER_CANNOT_BE_MOUNTED_OR_DISMOUNTED); + return false; + } + else + { + dismount(); + } + } + + return true; + } + + public boolean dismount() + { + if (ZoneManager.getInstance().getZone(getX(), getY(), getZ() - 300, WaterZone.class) == null) + { + if (!isGM() && !isInWater() && (getZ() > 10000)) + { + sendPacket(SystemMessageId.YOU_ARE_NOT_ALLOWED_TO_DISMOUNT_AT_THIS_LOCATION); + sendPacket(ActionFailed.STATIC_PACKET); + return false; + } + + // Skip elevation check only in NoLandingZone. + if (!isInsideZone(ZoneId.NO_LANDING) && ((GeoEngine.getInstance().getHeight(getX(), getY(), getZ()) + 300) < getZ())) + { + sendPacket(SystemMessageId.YOU_CANNOT_DISMOUNT_FROM_THIS_ELEVATION); + sendPacket(ActionFailed.STATIC_PACKET); + return false; + } + } + else + { + ThreadPool.schedule(() -> + { + if (isInWater()) + { + broadcastUserInfo(); + } + }, 1500); + } + + final boolean wasFlying = isFlying(); + sendPacket(new SetupGauge(getObjectId(), 3, 0, 0)); + final int petId = _mountNpcId; + setMount(0, 0); + stopFeed(); + if (wasFlying) + { + removeSkill(CommonSkill.WYVERN_BREATH.getSkill()); + } + + sendSkillList(); // Refresh skill list after dismounting. + broadcastPacket(new Ride(this)); + setMountObjectID(0); + storePetFood(petId); + + // Notify self and others about speed change. + broadcastUserInfo(); + + return true; + } + + public void setUptime(long time) + { + _uptime = time; + } + + public long getUptime() + { + return System.currentTimeMillis() - _uptime; + } + + /** + * Return True if the Player is invulnerable. + */ + @Override + public boolean isInvul() + { + return super.isInvul() || isTeleportProtected(); + } + + /** + * Return True if the Player has a Party in progress. + */ + @Override + public boolean isInParty() + { + return _party != null; + } + + /** + * Set the _party object of the Player (without joining it). + * @param party + */ + public void setParty(Party party) + { + _party = party; + } + + /** + * Set the _party object of the Player AND join it. + * @param party + */ + public void joinParty(Party party) + { + if (party != null) + { + // First set the party otherwise this wouldn't be considered. + // as in a party into the Creature.updateEffectIcons() call. + _party = party; + party.addPartyMember(this); + } + } + + /** + * Manage the Leave Party task of the Player. + */ + public void leaveParty() + { + if (isInParty()) + { + _party.removePartyMember(this, PartyMessageType.DISCONNECTED); + _party = null; + } + } + + /** + * Return the _party object of the Player. + */ + @Override + public Party getParty() + { + return _party; + } + + public void setPartyDistributionType(PartyDistributionType pdt) + { + _partyDistributionType = pdt; + } + + public PartyDistributionType getPartyDistributionType() + { + return _partyDistributionType; + } + + /** + * Return True if the Player is a GM. + */ + @Override + public boolean isGM() + { + return getAccessLevel().isGm(); + } + + /** + * Set the _accessLevel of the Player. + * @param level + */ + public void setAccessLevel(int level) + { + _accessLevel = AdminData.getInstance().getAccessLevel(level); + _appearance.setNameColor(_accessLevel.getNameColor()); + _appearance.setTitleColor(_accessLevel.getTitleColor()); + broadcastUserInfo(); + + CharInfoTable.getInstance().addName(this); + + if (!AdminData.getInstance().hasAccessLevel(level)) + { + LOGGER.warning("Tried to set unregistered access level " + level + " for " + this + ". Setting access level without privileges!"); + } + else if (level > 0) + { + LOGGER.warning(_accessLevel.getName() + " access level set for character " + getName() + "! Just a warning to be careful ;)"); + } + } + + public void setAccountAccesslevel(int level) + { + LoginServerThread.getInstance().sendAccessLevel(getAccountName(), level); + } + + /** + * @return the _accessLevel of the Player. + */ + @Override + public AccessLevel getAccessLevel() + { + if (GeneralConfig.EVERYBODY_HAS_ADMIN_RIGHTS) + { + return AdminData.getInstance().getMasterAccessLevel(); + } + + if (_accessLevel == null) + { + setAccessLevel(0); + } + + return _accessLevel; + } + + /** + * Update Stats of the Player client side by sending Server->Client packet UserInfo/StatusUpdate to this Player and CharInfo/StatusUpdate to all known players (broadcast). + */ + public void updateAndBroadcastStatus() + { + if (_updateAndBroadcastStatusTask == null) + { + _updateAndBroadcastStatusTask = ThreadPool.schedule(() -> + { + refreshOverloaded(); + refreshExpertisePenalty(); + + // Send a Server->Client packet UserInfo to this Player and CharInfo to all known players (broadcast). + broadcastUserInfo(); + + _updateAndBroadcastStatusTask = null; + }, 50); + } + } + + /** + * Send a Server->Client StatusUpdate packet with Karma and PvP Flag to the Player and all Player to inform (broadcast). + */ + public void setKarmaFlag() + { + updateUserInfo(); + World.forEachVisibleObject(this, Player.class, player -> + { + player.sendPacket(new RelationChanged(this, getRelation(player), isAutoAttackable(player))); + if (hasSummon()) + { + player.sendPacket(new RelationChanged(_summon, getRelation(player), isAutoAttackable(player))); + } + }); + } + + /** + * Send a Server->Client StatusUpdate packet with Karma to the Player and all Player to inform (broadcast). + */ + public void broadcastKarma() + { + final StatusUpdate su = new StatusUpdate(this); + su.addAttribute(StatusUpdate.KARMA, getKarma()); + sendPacket(su); + + World.forEachVisibleObject(this, Player.class, player -> + { + player.sendPacket(new RelationChanged(this, getRelation(player), isAutoAttackable(player))); + if (hasSummon()) + { + player.sendPacket(new RelationChanged(_summon, getRelation(player), isAutoAttackable(player))); + } + }); + } + + /** + * Set the online Flag to True or False and update the characters table of the database with online status and lastAccess (called when login and logout). + * @param isOnline + * @param updateInDb + */ + public void setOnlineStatus(boolean isOnline, boolean updateInDb) + { + if (_isOnline != isOnline) + { + _isOnline = isOnline; + } + + // Update the characters table of the database with online status and lastAccess (called when login and logout). + if (updateInDb) + { + updateOnlineStatus(); + } + } + + public void setIn7sDungeon(boolean isIn7sDungeon) + { + _isIn7sDungeon = isIn7sDungeon; + } + + /** + * Update the characters table of the database with online status and lastAccess of this Player (called when login and logout). + */ + public void updateOnlineStatus() + { + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement("UPDATE characters SET online=?, lastAccess=? WHERE charId=?")) + { + ps.setInt(1, isOnlineInt()); + ps.setLong(2, System.currentTimeMillis()); + ps.setInt(3, getObjectId()); + ps.execute(); + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "Failed updating character online status.", e); + } + } + + /** + * Create a new player in the characters table of the database. + * @return + */ + private boolean createDb() + { + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement(INSERT_CHARACTER)) + { + ps.setString(1, _accountName); + ps.setInt(2, getObjectId()); + ps.setString(3, getName()); + ps.setInt(4, getLevel()); + ps.setInt(5, getMaxHp()); + ps.setDouble(6, getCurrentHp()); + ps.setInt(7, getMaxCp()); + ps.setDouble(8, getCurrentCp()); + ps.setInt(9, getMaxMp()); + ps.setDouble(10, getCurrentMp()); + ps.setInt(11, _appearance.getFace()); + ps.setInt(12, _appearance.getHairStyle()); + ps.setInt(13, _appearance.getHairColor()); + ps.setInt(14, _appearance.isFemale() ? 1 : 0); + ps.setLong(15, getExp()); + ps.setLong(16, getSp()); + ps.setInt(17, getKarma()); + ps.setInt(18, _fame); + ps.setInt(19, _pvpKills); + ps.setInt(20, _pkKills); + ps.setInt(21, _clanId); + ps.setInt(22, getRace().ordinal()); + ps.setInt(23, getPlayerClass().getId()); + ps.setLong(24, _deleteTimer); + ps.setInt(25, hasDwarvenCraft() ? 1 : 0); + ps.setString(26, getTitle()); + ps.setInt(27, _appearance.getTitleColor()); + ps.setInt(28, getAccessLevel().getLevel()); + ps.setInt(29, isOnlineInt()); + ps.setInt(30, _isIn7sDungeon ? 1 : 0); + ps.setInt(31, _clanPrivileges.getMask()); + ps.setInt(32, _wantsPeace); + ps.setInt(33, _baseClass); + ps.setInt(34, _newbie ? 1 : 0); + ps.setInt(35, _noble ? 1 : 0); + ps.setLong(36, 0); + ps.setTimestamp(37, new Timestamp(_createDate.getTimeInMillis())); + ps.setLong(38, System.currentTimeMillis()); // lastAccess + ps.setLong(39, System.currentTimeMillis()); // last_recom_date + ps.executeUpdate(); + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "Could not insert char data: " + e.getMessage(), e); + return false; + } + + return true; + } + + /** + * Retrieve a Player from the characters table of the database and add it in _allObjects of the L2world. Actions: + *
  • Retrieve the Player from the characters table of the database
  • + *
  • Add the Player object in _allObjects
  • + *
  • Set the x,y,z position of the Player and make it invisible
  • + *
  • Update the overloaded status of the Player

  • + * @param objectId Identifier of the object to initialized + * @return The Player loaded from the database + */ + private static Player restore(int objectId) + { + Player player = null; + double currentCp = 0; + double currentHp = 0; + double currentMp = 0; + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement(RESTORE_CHARACTER)) + { + // Retrieve the Player from the characters table of the database. + ps.setInt(1, objectId); + try (ResultSet rset = ps.executeQuery()) + { + if (rset.next()) + { + final int activeClassId = rset.getInt("classid"); + final boolean female = rset.getInt("sex") != Sex.MALE.ordinal(); + final PlayerTemplate template = PlayerTemplateData.getInstance().getTemplate(activeClassId); + final PlayerAppearance app = new PlayerAppearance(rset.getByte("face"), rset.getByte("hairColor"), rset.getByte("hairStyle"), female); + player = new Player(objectId, template, rset.getString("account_name"), app); + player.setName(rset.getString("char_name")); + player._lastAccess = rset.getLong("lastAccess"); + final PlayerStat stat = player.getStat(); + stat.setExp(rset.getLong("exp")); + player.setExpBeforeDeath(rset.getLong("expBeforeDeath")); + stat.setLevel(rset.getByte("level")); + stat.setSp(rset.getLong("sp")); + + player.setWantsPeace(rset.getInt("wantspeace")); + + player.setHeading(rset.getInt("heading")); + + player.setKarma(rset.getInt("karma")); + player.setFame(rset.getInt("fame")); + player.setPvpKills(rset.getInt("pvpkills")); + player.setPkKills(rset.getInt("pkkills")); + player.setOnlineTime(rset.getLong("onlinetime")); + player.setNewbie(rset.getInt("newbie") == 1); + player.setNoble(rset.getInt("nobless") == 1); + + final int factionId = rset.getInt("faction"); + if (factionId == 1) + { + player.setGood(); + } + + if (factionId == 2) + { + player.setEvil(); + } + + player.setClanJoinExpiryTime(rset.getLong("clan_join_expiry_time")); + if (player.getClanJoinExpiryTime() < System.currentTimeMillis()) + { + player.setClanJoinExpiryTime(0); + } + + player.setClanCreateExpiryTime(rset.getLong("clan_create_expiry_time")); + if (player.getClanCreateExpiryTime() < System.currentTimeMillis()) + { + player.setClanCreateExpiryTime(0); + } + + player.setPcCafePoints(rset.getInt("pccafe_points")); + player.setPowerGrade(rset.getInt("power_grade")); + player.setPledgeType(rset.getInt("subpledge")); + // player.setApprentice(rs.getInt("apprentice")); + player.setDeleteTimer(rset.getLong("deletetime")); + player.setTitle(rset.getString("title")); + player.setAccessLevel(rset.getInt("accesslevel")); + final int titleColor = rset.getInt("title_color"); + if (titleColor != PlayerAppearance.DEFAULT_TITLE_COLOR) + { + player.getAppearance().setTitleColor(titleColor); + } + + player.setFistsWeaponItem(player.findFistsWeaponItem(activeClassId)); + player.setUptime(System.currentTimeMillis()); + + currentHp = rset.getDouble("curHp"); + currentCp = rset.getDouble("curCp"); + currentMp = rset.getDouble("curMp"); + + // Check recommendations + player.setLastRecomUpdate(rset.getLong("last_recom_date")); + player.checkRecom(rset.getInt("rec_have"), rset.getInt("rec_left")); + player._classIndex = 0; + try + { + player.setBaseClass(rset.getInt("base_class")); + } + catch (Exception e) + { + // TODO: Should this be logged? + player.setBaseClass(activeClassId); + } + + // Restore Subclass Data (cannot be done earlier in function). + if (restoreSubClassData(player) && (activeClassId != player.getBaseClass())) + { + for (SubClassHolder subClass : player.getSubClasses().values()) + { + if (subClass.getId() == activeClassId) + { + player._classIndex = subClass.getClassIndex(); + } + } + } + + if ((player.getClassIndex() == 0) && (activeClassId != player.getBaseClass())) + { + // Subclass in use but doesn't exist in DB - + // A possible restart-while-modifysubclass cheat has been attempted. + // Switching to use base class. + player.setPlayerClass(player.getBaseClass()); + LOGGER.warning(player + " reverted to base class. Possibly has tried a relogin exploit while subclassing."); + } + else + { + player._activeClass = activeClassId; + } + + player.setApprentice(rset.getInt("apprentice")); + player.setSponsor(rset.getInt("sponsor")); + player.setLvlJoinedAcademy(rset.getInt("lvl_joined_academy")); + player.setIn7sDungeon(rset.getInt("isin7sdungeon") == 1); + CursedWeaponsManager.getInstance().checkPlayer(player); + + player.setDeathPenaltyBuffLevel(rset.getInt("death_penalty_level")); + + player.setVitalityPoints(rset.getInt("vitality_points"), true); + + // Set the x,y,z position of the Player and make it invisible. + final int x = rset.getInt("x"); + final int y = rset.getInt("y"); + final int z = GeoEngine.getInstance().getHeight(x, y, rset.getInt("z")); + player.setXYZInvisible(x, y, z); + player.setLastServerPosition(x, y, z); + + // Character creation Time. + player.getCreateDate().setTimeInMillis(rset.getTimestamp("createDate").getTime()); + + // Language + player.setLang(rset.getString("language")); + + // Set Hero status if it applies + player.setHero(Hero.getInstance().isHero(objectId)); + + final int clanId = rset.getInt("clanid"); + Clan clan = null; + if (clanId > 0) + { + clan = ClanTable.getInstance().getClan(clanId); + player.setClan(clan); + if ((clan != null) && clan.isMember(objectId)) + { + if (clan.getLeaderId() != player.getObjectId()) + { + if (player.getPowerGrade() == 0) + { + player.setPowerGrade(5); + } + + player.setClanPrivileges(clan.getRankPrivs(player.getPowerGrade())); + } + else + { + player.getClanPrivileges().enableAll(); + player.setPowerGrade(1); + } + + player.setPledgeClass(ClanMember.calculatePledgeClass(player)); + } + } + + if (clan == null) + { + if (player.isNoble()) + { + player.setPledgeClass(5); + } + + if (player.isHero()) + { + player.setPledgeClass(8); + } + + player.getClanPrivileges().disableAll(); + } + + // Retrieve the name and ID of the other characters assigned to this account. + try (PreparedStatement stmt = con.prepareStatement("SELECT charId, char_name FROM characters WHERE account_name=? AND charId<>?")) + { + stmt.setString(1, player._accountName); + stmt.setInt(2, objectId); + try (ResultSet chars = stmt.executeQuery()) + { + while (chars.next()) + { + player._chars.put(chars.getInt("charId"), chars.getString("char_name")); + } + } + } + } + } + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "Failed loading character.", e); + } + + if (player == null) + { + return null; + } + + // Retrieve from the database all items of this Player and add them to _inventory. + player.getInventory().restore(); + player.getWarehouse().restore(); + player.getFreight().restore(); + + // Retrieve from the database all skills of this Player and add them to _skills. + player.restoreSkills(); + + // Retrieve from the database all macros of this Player and add them to _macros. + player.getMacros().restoreMe(); + + // Retrieve from the database all shortcuts of this Player and add them to _shortcuts. + player.restoreShortcuts(); + + // Retrieve from the database all henna of this Player and add them to _henna. + player.restoreHenna(); + + // Retrieve from the database all recom data of this Player and add to _recomChars. + if (!PlayerConfig.ALT_RECOMMEND) + { + player.restoreRecom(); + } + + // Retrieve from the database the recipe book of this Player. + player.restoreRecipeBook(true); + + // Restore Recipe Shop list. + if (PlayerConfig.STORE_RECIPE_SHOPLIST) + { + player.restoreRecipeShopList(); + } + + // Restore items in pet inventory. + player.restorePetInventoryItems(); + + // Reward auto-get skills and all available skills if auto-learn skills is true. + player.rewardSkills(); + + player.restoreItemReuse(); + + // Restore current Cp, HP and MP values. + player.setCurrentCp(currentCp); + player.setCurrentHp(currentHp); + player.setCurrentMp(currentMp); + + player.setOriginalCpHpMp(currentCp, currentHp, currentMp); + if (currentHp < 0.5) + { + player.setDead(true); + player.stopHpMpRegeneration(); + } + + // Restore pet if exists in the world. + player.setPet(World.getPet(player.getObjectId())); + if (player.hasSummon()) + { + player.getSummon().setOwner(player); + } + + // Update the overloaded status of the Player. + player.refreshOverloaded(); + + // Update the expertise status of the Player. + player.refreshExpertisePenalty(); + + player.restoreFriendList(); + + player.setOnlineStatus(true, false); + + PlayerAutoSaveTaskManager.getInstance().add(player); + + return player; + } + + public int getMailPosition() + { + return _mailPosition; + } + + public void setMailPosition(int mailPosition) + { + _mailPosition = mailPosition; + } + + public int getMailForumPosition() + { + final Forum mailForum = getForumMail(); + if (mailForum == null) + { + return 0; + } + + return mailForum.getTopicSize(); + } + + public Forum getForumMail() + { + if (_forumMail == null) + { + setForumMail(ForumsBBSManager.getInstance().getForumByName("MailRoot").getChildByName(getName())); + + if (_forumMail == null) + { + ForumsBBSManager.getInstance().createNewForum(getName(), ForumsBBSManager.getInstance().getForumByName("MailRoot"), Forum.MAIL, Forum.OWNERONLY, getObjectId()); + setForumMail(ForumsBBSManager.getInstance().getForumByName("MailRoot").getChildByName(getName())); + } + } + + return _forumMail; + } + + public void setForumMail(Forum forum) + { + _forumMail = forum; + } + + public Forum getMemo() + { + if (_forumMemo == null) + { + setMemo(ForumsBBSManager.getInstance().getForumByName("MemoRoot").getChildByName(_accountName)); + + if (_forumMemo == null) + { + ForumsBBSManager.getInstance().createNewForum(_accountName, ForumsBBSManager.getInstance().getForumByName("MemoRoot"), Forum.MEMO, Forum.OWNERONLY, getObjectId()); + setMemo(ForumsBBSManager.getInstance().getForumByName("MemoRoot").getChildByName(_accountName)); + } + } + + return _forumMemo; + } + + public void setMemo(Forum forum) + { + _forumMemo = forum; + } + + /** + * Restores sub-class data for the Player, used to check the current class index for the character. + * @param player + * @return + */ + private static boolean restoreSubClassData(Player player) + { + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement(RESTORE_CHAR_SUBCLASSES)) + { + ps.setInt(1, player.getObjectId()); + try (ResultSet rs = ps.executeQuery()) + { + while (rs.next()) + { + final SubClassHolder subClass = new SubClassHolder(); + subClass.setPlayerClass(rs.getInt("class_id")); + subClass.setLevel(rs.getByte("level")); + subClass.setExp(rs.getLong("exp")); + subClass.setSp(rs.getLong("sp")); + subClass.setClassIndex(rs.getInt("class_index")); + + // Enforce the correct indexing of _subClasses against their class indexes. + player.getSubClasses().put(subClass.getClassIndex(), subClass); + } + } + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Could not restore classes for " + player.getName() + ": " + e.getMessage(), e); + } + + return true; + } + + /** + * Restore recipe book data for this Player. + * @param loadCommon + */ + private void restoreRecipeBook(boolean loadCommon) + { + final String sql = loadCommon ? "SELECT id, type, classIndex FROM character_recipebook WHERE charId=?" : "SELECT id FROM character_recipebook WHERE charId=? AND classIndex=? AND type = 1"; + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement(sql)) + { + ps.setInt(1, getObjectId()); + if (!loadCommon) + { + ps.setInt(2, _classIndex); + } + + try (ResultSet rs = ps.executeQuery()) + { + _dwarvenRecipeBook.clear(); + + RecipeList recipe; + final RecipeData rd = RecipeData.getInstance(); + while (rs.next()) + { + recipe = rd.getRecipeList(rs.getInt("id")); + if (loadCommon) + { + if (rs.getInt(2) == 1) + { + if (rs.getInt(3) == _classIndex) + { + registerDwarvenRecipeList(recipe, false); + } + } + else + { + registerCommonRecipeList(recipe, false); + } + } + else + { + registerDwarvenRecipeList(recipe, false); + } + } + } + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "Could not restore recipe book data:" + e.getMessage(), e); + } + } + + /** + * Update Player stats in the characters table of the database. + * @param storeActiveEffects + */ + public synchronized void store(boolean storeActiveEffects) + { + storeCharBase(); + storeCharSub(); + storeEffect(storeActiveEffects); + storeItemReuseDelay(); + if (PlayerConfig.STORE_RECIPE_SHOPLIST) + { + storeRecipeShopList(); + } + + SevenSigns.getInstance().saveSevenSignsData(getObjectId()); + + final PlayerVariables vars = getScript(PlayerVariables.class); + if (vars != null) + { + vars.storeMe(); + } + + final AccountVariables aVars = getScript(AccountVariables.class); + if (aVars != null) + { + aVars.storeMe(); + } + + getInventory().updateDatabase(); + getWarehouse().updateDatabase(); + getFreight().updateDatabase(); + } + + @Override + public void storeMe() + { + store(true); + } + + private void storeCharBase() + { + // Get the exp, level, and sp of base class to store in base table. + final long exp = getStat().getBaseExp(); + final int level = getStat().getBaseLevel(); + final long sp = getStat().getBaseSp(); + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement(UPDATE_CHARACTER)) + { + ps.setInt(1, level); + ps.setInt(2, getMaxHp()); + ps.setDouble(3, getCurrentHp()); + ps.setInt(4, getMaxCp()); + ps.setDouble(5, getCurrentCp()); + ps.setInt(6, getMaxMp()); + ps.setDouble(7, getCurrentMp()); + ps.setInt(8, _appearance.getFace()); + ps.setInt(9, _appearance.getHairStyle()); + ps.setInt(10, _appearance.getHairColor()); + ps.setInt(11, _appearance.isFemale() ? 1 : 0); + ps.setInt(12, getHeading()); + ps.setInt(13, _observerMode ? _lastLoc.getX() : getX()); + ps.setInt(14, _observerMode ? _lastLoc.getY() : getY()); + ps.setInt(15, _observerMode ? _lastLoc.getZ() : getZ()); + ps.setLong(16, exp); + ps.setLong(17, _expBeforeDeath); + ps.setLong(18, sp); + ps.setInt(19, getKarma()); + ps.setInt(20, _fame); + ps.setInt(21, _pvpKills); + ps.setInt(22, _pkKills); + ps.setInt(23, _clanId); + ps.setInt(24, getRace().ordinal()); + ps.setInt(25, getPlayerClass().getId()); + ps.setLong(26, _deleteTimer); + ps.setString(27, getTitle()); + ps.setInt(28, _appearance.getTitleColor()); + ps.setInt(29, getAccessLevel().getLevel()); + ps.setInt(30, isOnlineInt()); + ps.setInt(31, _isIn7sDungeon ? 1 : 0); + ps.setInt(32, _clanPrivileges.getMask()); + ps.setInt(33, _wantsPeace); + ps.setInt(34, _baseClass); + long totalOnlineTime = _onlineTime; + if (_onlineBeginTime > 0) + { + totalOnlineTime += (System.currentTimeMillis() - _onlineBeginTime) / 1000; + } + + ps.setLong(35, _offlineShopStart > 0 ? _onlineTime : totalOnlineTime); + ps.setInt(36, _newbie ? 1 : 0); + ps.setInt(37, _noble ? 1 : 0); + ps.setInt(38, _powerGrade); + ps.setInt(39, _pledgeType); + ps.setInt(40, _lvlJoinedAcademy); + ps.setLong(41, _apprentice); + ps.setLong(42, _sponsor); + ps.setLong(43, _clanJoinExpiryTime); + ps.setLong(44, _clanCreateExpiryTime); + ps.setString(45, getName()); + ps.setLong(46, _deathPenaltyBuffLevel); + ps.setInt(47, 0); // _bookmarkSlot + ps.setInt(48, getVitalityPoints()); + ps.setString(49, _lang); + int factionId = 0; + if (_isGood) + { + factionId = 1; + } + + if (_isEvil) + { + factionId = 2; + } + + ps.setInt(50, factionId); + ps.setInt(51, _pcCafePoints); + ps.setLong(52, getLastRecomUpdate()); + ps.setInt(53, getRecomHave()); + ps.setInt(54, getRecomLeft()); + ps.setInt(55, getObjectId()); + ps.execute(); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Could not store char base data: " + this + " - " + e.getMessage(), e); + } + } + + private void storeCharSub() + { + if (getTotalSubClasses() <= 0) + { + return; + } + + // TODO(Zoey76): Refactor this to use batch. + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement(UPDATE_CHAR_SUBCLASS)) + { + for (SubClassHolder subClass : getSubClasses().values()) + { + ps.setLong(1, subClass.getExp()); + ps.setLong(2, subClass.getSp()); + ps.setInt(3, subClass.getLevel()); + ps.setInt(4, subClass.getId()); + ps.setInt(5, getObjectId()); + ps.setInt(6, subClass.getClassIndex()); + ps.addBatch(); + } + + ps.executeBatch(); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Could not store sub class data for " + getName() + ": " + e.getMessage(), e); + } + } + + @Override + public void storeEffect(boolean storeEffects) + { + if (!PlayerConfig.STORE_SKILL_COOLTIME) + { + return; + } + + try (Connection con = DatabaseFactory.getConnection()) + { + // Delete all current stored effects for char to avoid dupe. + try (PreparedStatement delete = con.prepareStatement(DELETE_SKILL_SAVE)) + { + delete.setInt(1, getObjectId()); + delete.setInt(2, _classIndex); + delete.execute(); + } + + int buffIndex = 0; + final List storedSkills = new ArrayList<>(); + final long currentTime = System.currentTimeMillis(); + + // Store all effect data along with calulated remaining. + // reuse delays for matching skills. 'restore_type'= 0. + try (PreparedStatement statement = con.prepareStatement(ADD_SKILL_SAVE)) + { + if (storeEffects) + { + for (BuffInfo info : getEffectList().getEffects()) + { + if (info == null) + { + continue; + } + + final Skill skill = info.getSkill(); + + // Do not save heals. + if (skill.getAbnormalType() == AbnormalType.LIFE_FORCE_OTHERS) + { + continue; + } + + // Toggles are skipped, unless they are necessary to be always on. + if (skill.isToggle() && !PlayerConfig.ALT_STORE_TOGGLES) + { + continue; + } + + // Dances and songs are not kept in retail. + if (skill.isDance() && !PlayerConfig.ALT_STORE_DANCES) + { + continue; + } + + if (storedSkills.contains(skill.getReuseHashCode())) + { + continue; + } + + storedSkills.add(skill.getReuseHashCode()); + + statement.setInt(1, getObjectId()); + statement.setInt(2, skill.getId()); + statement.setInt(3, skill.getLevel()); + statement.setInt(4, info.getTime()); + + final TimeStamp t = getSkillReuseTimeStamp(skill.getReuseHashCode()); + statement.setLong(5, (t != null) && (currentTime < t.getStamp()) ? t.getReuse() : 0); + statement.setLong(6, (t != null) && (currentTime < t.getStamp()) ? t.getStamp() : 0); + statement.setInt(7, 0); // Store type 0, active buffs/debuffs. + statement.setInt(8, _classIndex); + statement.setInt(9, ++buffIndex); + statement.addBatch(); + } + } + + // Skills under reuse. + for (Entry ts : getSkillReuseTimeStamps().entrySet()) + { + final int hash = ts.getKey(); + if (storedSkills.contains(hash)) + { + continue; + } + + final TimeStamp t = ts.getValue(); + if ((t != null) && (currentTime < t.getStamp())) + { + storedSkills.add(hash); + + statement.setInt(1, getObjectId()); + statement.setInt(2, t.getSkillId()); + statement.setInt(3, t.getSkillLevel()); + statement.setInt(4, -1); + statement.setLong(5, t.getReuse()); + statement.setLong(6, t.getStamp()); + statement.setInt(7, 1); // Restore type 1, skill reuse. + statement.setInt(8, _classIndex); + statement.setInt(9, ++buffIndex); + statement.addBatch(); + } + } + + statement.executeBatch(); + } + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Could not store char effect data: ", e); + } + } + + private void storeItemReuseDelay() + { + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps1 = con.prepareStatement(DELETE_ITEM_REUSE_SAVE); + PreparedStatement ps2 = con.prepareStatement(ADD_ITEM_REUSE_SAVE)) + { + ps1.setInt(1, getObjectId()); + ps1.execute(); + + final long currentTime = System.currentTimeMillis(); + for (TimeStamp ts : getItemReuseTimeStamps().values()) + { + if ((ts != null) && (currentTime < ts.getStamp())) + { + ps2.setInt(1, getObjectId()); + ps2.setInt(2, ts.getItemId()); + ps2.setInt(3, ts.getItemObjectId()); + ps2.setLong(4, ts.getReuse()); + ps2.setLong(5, ts.getStamp()); + ps2.addBatch(); + } + } + + ps2.executeBatch(); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Could not store char item reuse data: ", e); + } + } + + /** + * @return True if the Player is online. + */ + public boolean isOnline() + { + return _isOnline; + } + + public int isOnlineInt() + { + if (_isOnline && (_client != null)) + { + return _client.isDetached() ? 2 : 1; + } + + return 0; + } + + public void startOfflinePlay() + { + if (hasPremiumStatus() && (DualboxCheckConfig.DUALBOX_CHECK_MAX_OFFLINEPLAY_PREMIUM_PER_IP > 0) && !AntiFeedManager.getInstance().tryAddPlayer(AntiFeedManager.OFFLINE_PLAY, this, DualboxCheckConfig.DUALBOX_CHECK_MAX_OFFLINEPLAY_PREMIUM_PER_IP)) + { + String limit = String.valueOf(AntiFeedManager.getInstance().getLimit(this, DualboxCheckConfig.DUALBOX_CHECK_MAX_OFFLINEPLAY_PER_IP)); + sendMessage("Only " + limit + " offline players allowed per IP."); + return; + } + else if ((DualboxCheckConfig.DUALBOX_CHECK_MAX_OFFLINEPLAY_PER_IP > 0) && !AntiFeedManager.getInstance().tryAddPlayer(AntiFeedManager.OFFLINE_PLAY, this, DualboxCheckConfig.DUALBOX_CHECK_MAX_OFFLINEPLAY_PER_IP)) + { + String limit = String.valueOf(AntiFeedManager.getInstance().getLimit(this, DualboxCheckConfig.DUALBOX_CHECK_MAX_OFFLINEPLAY_PER_IP)); + sendMessage("Only " + limit + " offline players allowed per IP."); + return; + } + + AntiFeedManager.getInstance().removePlayer(AntiFeedManager.GAME_ID, this); + + sendPacket(LeaveWorld.STATIC_PACKET); + + if (OfflinePlayConfig.OFFLINE_PLAY_SET_NAME_COLOR) + { + getAppearance().setNameColor(OfflinePlayConfig.OFFLINE_PLAY_NAME_COLOR); + } + + if (!OfflinePlayConfig.OFFLINE_PLAY_ABNORMAL_EFFECTS.isEmpty()) + { + startAbnormalVisualEffect(true, OfflinePlayConfig.OFFLINE_PLAY_ABNORMAL_EFFECTS.get(Rnd.get(OfflinePlayConfig.OFFLINE_PLAY_ABNORMAL_EFFECTS.size()))); + } + + broadcastUserInfo(); + + _offlinePlay = true; + _client.setDetached(true); + + if (OfflinePlayConfig.RESTORE_AUTO_PLAY_OFFLINERS) + { + OfflinePlayTable.getInstance().storeOfflinePlay(this); + } + } + + public boolean isOfflinePlay() + { + return _offlinePlay; + } + + public void setOfflinePlay(boolean value) + { + _offlinePlay = value; + } + + public void setEnteredWorld() + { + _enteredWorld = true; + } + + public boolean hasEnteredWorld() + { + return _enteredWorld; + } + + /** + * Verifies if the player is in offline mode.
    + * The offline mode may happen for different reasons:
    + * Abnormally: Player gets abruptly disconnected from server.
    + * Normally: The player gets into offline shop mode, only available by enabling the offline shop mod. + * @return {@code true} if the player is in offline mode, {@code false} otherwise + */ + public boolean isInOfflineMode() + { + return (_client == null) || _client.isDetached(); + } + + public boolean isIn7sDungeon() + { + return _isIn7sDungeon; + } + + @Override + public Skill addSkill(Skill newSkill) + { + addCustomSkill(newSkill); + return super.addSkill(newSkill); + } + + /** + * Add a skill to the Player _skills and its Func objects to the calculator set of the Player and save update in the character_skills table of the database. Concept: All skills own by a Player are identified in _skills Actions: + *
  • Replace oldSkill by newSkill or Add the newSkill
  • + *
  • If an old skill has been replaced, remove all its Func objects of Creature calculator set
  • + *
  • Add Func objects of newSkill to the calculator set of the Creature

  • + * @param newSkill The Skill to add to the Creature + * @param store + * @return The Skill replaced or null if just added a new Skill + */ + public Skill addSkill(Skill newSkill, boolean store) + { + // Add a skill to the Player _skills and its Func objects to the calculator set of the Player. + final Skill oldSkill = addSkill(newSkill); + + // Add or update a Player skill in the character_skills table of the database. + if (store) + { + storeSkill(newSkill, oldSkill, -1); + } + + return oldSkill; + } + + @Override + public Skill removeSkill(Skill skill, boolean store) + { + removeCustomSkill(skill); + return store ? removeSkill(skill) : super.removeSkill(skill, true); + } + + public Skill removeSkill(Skill skill, boolean store, boolean cancelEffect) + { + removeCustomSkill(skill); + return store ? removeSkill(skill) : super.removeSkill(skill, cancelEffect); + } + + /** + * Remove a skill from the Creature and its Func objects from calculator set of the Creature and save update in the character_skills table of the database. Concept: All skills own by a Creature are identified in _skills Actions: + *
  • Remove the skill from the Creature _skills
  • + *
  • Remove all its Func objects from the Creature calculator set
  • Overridden in: + *
  • Player : Save update in the character_skills table of the database

  • + * @param skill The Skill to remove from the Creature + * @return The Skill removed + */ + public Skill removeSkill(Skill skill) + { + removeCustomSkill(skill); + + // Remove a skill from the Creature and its Func objects from calculator set of the Creature. + final Skill oldSkill = super.removeSkill(skill, true); + if (oldSkill != null) + { + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement(DELETE_SKILL_FROM_CHAR)) + { + // Remove or update a Player skill from the character_skills table of the database. + ps.setInt(1, oldSkill.getId()); + ps.setInt(2, getObjectId()); + ps.setInt(3, _classIndex); + ps.execute(); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Error could not delete skill: " + e.getMessage(), e); + } + } + + if (isCursedWeaponEquipped()) + { + return oldSkill; + } + + if (skill != null) + { + for (Shortcut sc : _shortcuts.getAllShortcuts()) + { + if ((sc != null) && (sc.getId() == skill.getId()) && (sc.getType() == ShortcutType.SKILL) && ((skill.getId() < 3080) || (skill.getId() > 3259))) + { + deleteShortcut(sc.getSlot(), sc.getPage()); + } + } + } + + return oldSkill; + } + + /** + * Add or update a Player skill in the character_skills table of the database.
    + * If newClassIndex > -1, the skill will be stored with that class index, not the current one. + * @param newSkill + * @param oldSkill + * @param newClassIndex + */ + private void storeSkill(Skill newSkill, Skill oldSkill, int newClassIndex) + { + final int classIndex = (newClassIndex > -1) ? newClassIndex : _classIndex; + try (Connection con = DatabaseFactory.getConnection()) + { + if ((oldSkill != null) && (newSkill != null)) + { + try (PreparedStatement ps = con.prepareStatement(UPDATE_CHARACTER_SKILL_LEVEL)) + { + ps.setInt(1, newSkill.getLevel()); + ps.setInt(2, oldSkill.getId()); + ps.setInt(3, getObjectId()); + ps.setInt(4, classIndex); + ps.execute(); + } + } + else if (newSkill != null) + { + try (PreparedStatement ps = con.prepareStatement(ADD_NEW_SKILLS)) + { + ps.setInt(1, getObjectId()); + ps.setInt(2, newSkill.getId()); + ps.setInt(3, newSkill.getLevel()); + ps.setInt(4, classIndex); + ps.execute(); + } + } + // else + // { + // LOGGER.warning("Could not store new skill, it's null!"); + // } + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Error could not store char skills: " + e.getMessage(), e); + } + } + + /** + * Adds or updates player's skills in the database. + * @param newSkills the list of skills to store + * @param newClassIndex if newClassIndex > -1, the skills will be stored for that class index, not the current one + */ + private void storeSkills(List newSkills, int newClassIndex) + { + if (newSkills.isEmpty()) + { + return; + } + + final int classIndex = (newClassIndex > -1) ? newClassIndex : _classIndex; + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement(ADD_NEW_SKILLS)) + { + for (Skill addSkill : newSkills) + { + ps.setInt(1, getObjectId()); + ps.setInt(2, addSkill.getId()); + ps.setInt(3, addSkill.getLevel()); + ps.setInt(4, classIndex); + ps.addBatch(); + } + + ps.executeBatch(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "Error could not store char skills: " + e.getMessage(), e); + } + } + + /** + * Retrieve from the database all skills of this Player and add them to _skills. + */ + private void restoreSkills() + { + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement(RESTORE_SKILLS_FOR_CHAR)) + { + // Retrieve all skills of this Player from the database. + ps.setInt(1, getObjectId()); + ps.setInt(2, _classIndex); + try (ResultSet rs = ps.executeQuery()) + { + while (rs.next()) + { + final int id = rs.getInt("skill_id"); + final int level = rs.getInt("skill_level"); + + // Create a Skill object for each record. + final Skill skill = SkillData.getInstance().getSkill(id, level); + if (skill == null) + { + LOGGER.warning("Skipped null skill Id: " + id + " Level: " + level + " while restoring player skills for playerObjId: " + getObjectId()); + continue; + } + + // Add the Skill object to the Creature _skills and its Func objects to the calculator set of the Creature. + addSkill(skill); + + if (GeneralConfig.SKILL_CHECK_ENABLE && (!isGM() || GeneralConfig.SKILL_CHECK_GM) && !SkillTreeData.getInstance().isSkillAllowed(this, skill)) + { + PunishmentManager.handleIllegalPlayerAction(this, "Player " + getName() + " has invalid skill " + skill.getName() + " (" + skill.getId() + "/" + skill.getLevel() + "), class:" + ClassListData.getInstance().getClass(getPlayerClass()).getClassName(), IllegalActionPunishmentType.BROADCAST); + if (GeneralConfig.SKILL_CHECK_REMOVE) + { + removeSkill(skill); + } + } + } + } + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Could not restore character " + this + " skills: " + e.getMessage(), e); + } + } + + /** + * Retrieve from the database all skill effects of this Player and add them to the player. + */ + @Override + public void restoreEffects() + { + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement(RESTORE_SKILL_SAVE)) + { + ps.setInt(1, getObjectId()); + ps.setInt(2, _classIndex); + try (ResultSet rs = ps.executeQuery()) + { + final long currentTime = System.currentTimeMillis(); + while (rs.next()) + { + final Skill skill = SkillData.getInstance().getSkill(rs.getInt("skill_id"), rs.getInt("skill_level")); + if (skill == null) + { + continue; + } + + final long systime = rs.getLong("systime"); + final long time = systime - currentTime; + if (time > 10) + { + disableSkill(skill, time); + addTimeStamp(skill, rs.getLong("reuse_delay"), systime); + } + + // Restore Type 1 The remaning skills lost effect upon logout but were still under a high reuse delay. + if (rs.getInt("restore_type") > 0) + { + continue; + } + + // Restore Type 0 These skill were still in effect on the character upon logout. + // Some of which were self casted and might still have had a long reuse delay which also is restored. + skill.applyEffects(this, this, false, rs.getInt("remaining_time")); + } + } + + // Remove previously restored skills. + try (PreparedStatement delete = con.prepareStatement(DELETE_SKILL_SAVE)) + { + delete.setInt(1, getObjectId()); + delete.setInt(2, _classIndex); + delete.executeUpdate(); + } + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Could not restore " + this + " active effect data: " + e.getMessage(), e); + } + } + + /** + * Retrieve from the database all Item Reuse Time of this Player and add them to the player. + */ + private void restoreItemReuse() + { + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement(RESTORE_ITEM_REUSE_SAVE); + PreparedStatement delete = con.prepareStatement(DELETE_ITEM_REUSE_SAVE);) + { + ps.setInt(1, getObjectId()); + try (ResultSet rs = ps.executeQuery()) + { + int itemId; + long reuseDelay; + long systime; + boolean isInInventory; + long remainingTime; + final long currentTime = System.currentTimeMillis(); + while (rs.next()) + { + itemId = rs.getInt("itemId"); + reuseDelay = rs.getLong("reuseDelay"); + systime = rs.getLong("systime"); + isInInventory = true; + + // Using item Id + Item item = _inventory.getItemByItemId(itemId); + if (item == null) + { + item = getWarehouse().getItemByItemId(itemId); + isInInventory = false; + } + + if ((item != null) && (item.getId() == itemId) && (item.getReuseDelay() > 0)) + { + remainingTime = systime - currentTime; + if (remainingTime > 10) + { + addTimeStampItem(item, reuseDelay, systime); + if (isInInventory && item.isEtcItem()) + { + final int group = item.getSharedReuseGroup(); + if (group > 0) + { + sendPacket(new ExUseSharedGroupItem(itemId, group, (int) remainingTime, (int) reuseDelay)); + } + } + } + } + } + } + + // Delete item reuse. + delete.setInt(1, getObjectId()); + delete.executeUpdate(); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Could not restore " + this + " Item Reuse data: " + e.getMessage(), e); + } + } + + /** + * Retrieve from the database all Henna of this Player, add them to _henna and calculate stats of the Player. + */ + private void restoreHenna() + { + for (int i = 0; i < 3; i++) + { + _henna[i] = null; + } + + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement(RESTORE_CHAR_HENNAS)) + { + ps.setInt(1, getObjectId()); + ps.setInt(2, _classIndex); + try (ResultSet rs = ps.executeQuery()) + { + int slot; + int symbolId; + while (rs.next()) + { + slot = rs.getInt("slot"); + if ((slot < 1) || (slot > 3)) + { + continue; + } + + symbolId = rs.getInt("symbol_id"); + if (symbolId == 0) + { + continue; + } + + _henna[slot - 1] = HennaData.getInstance().getHenna(symbolId); + } + } + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "Failed restoing character " + this + " hennas.", e); + } + + // Calculate henna modifiers of this player. + recalcHennaStats(); + } + + /** + * @return the number of Henna empty slot of the Player. + */ + public int getHennaEmptySlots() + { + int totalSlots = 0; + if (getPlayerClass().level() == 1) + { + totalSlots = 2; + } + else if (getPlayerClass().level() > 1) + { + totalSlots = 3; + } + + for (int i = 0; i < 3; i++) + { + if (_henna[i] != null) + { + totalSlots--; + } + } + + if (totalSlots <= 0) + { + return 0; + } + + return totalSlots; + } + + /** + * Remove a Henna of the Player, save update in the character_hennas table of the database and send Server->Client HennaInfo/UserInfo packet to this Player. + * @param slot + * @return + */ + public boolean removeHenna(int slot) + { + if ((slot < 1) || (slot > 3)) + { + return false; + } + + final Henna henna = _henna[slot - 1]; + if (henna == null) + { + return false; + } + + _henna[slot - 1] = null; + + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement statement = con.prepareStatement(DELETE_CHAR_HENNA)) + { + statement.setInt(1, getObjectId()); + statement.setInt(2, slot); + statement.setInt(3, _classIndex); + statement.execute(); + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "Failed removing character henna.", e); + } + + // Calculate Henna modifiers of this Player. + recalcHennaStats(); + + // Send Server->Client HennaInfo packet to this Player. + sendPacket(new HennaInfo(this)); + + // Send Server->Client UserInfo packet to this Player. + updateUserInfo(); + + // Add the recovered dyes to the player's inventory and notify them. + _inventory.addItem(ItemProcessType.RESTORE, henna.getDyeItemId(), henna.getCancelCount(), this, null); + reduceAdena(ItemProcessType.FEE, henna.getCancelFee(), this, false); + + final SystemMessage sm = new SystemMessage(SystemMessageId.YOU_HAVE_EARNED_S2_S1_S); + sm.addItemName(henna.getDyeItemId()); + sm.addInt(henna.getCancelCount()); + sendPacket(sm); + sendPacket(SystemMessageId.THE_SYMBOL_HAS_BEEN_DELETED); + + // Notify to scripts. + if (EventDispatcher.getInstance().hasListener(EventType.ON_PLAYER_HENNA_REMOVE, this)) + { + EventDispatcher.getInstance().notifyEventAsync(new OnPlayerHennaRemove(this, henna), this); + } + + return true; + } + + /** + * Add a Henna to the Player, save update in the character_hennas table of the database and send Server->Client HennaInfo/UserInfo packet to this Player. + * @param henna the henna to add to the player. + * @return {@code true} if the henna is added to the player, {@code false} otherwise. + */ + public boolean addHenna(Henna henna) + { + for (int i = 0; i < 3; i++) + { + if (_henna[i] == null) + { + _henna[i] = henna; + + // Calculate Henna modifiers of this Player. + recalcHennaStats(); + + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement(ADD_CHAR_HENNA)) + { + ps.setInt(1, getObjectId()); + ps.setInt(2, henna.getDyeId()); + ps.setInt(3, i + 1); + ps.setInt(4, _classIndex); + ps.execute(); + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "Failed saving character henna.", e); + } + + // Send Server->Client HennaInfo packet to this Player. + sendPacket(new HennaInfo(this)); + + // Send Server->Client UserInfo packet to this Player. + updateUserInfo(); + + // Notify to scripts. + if (EventDispatcher.getInstance().hasListener(EventType.ON_PLAYER_HENNA_REMOVE, this)) + { + EventDispatcher.getInstance().notifyEventAsync(new OnPlayerHennaRemove(this, henna), this); + } + + return true; + } + } + + return false; + } + + /** + * Calculate Henna modifiers of this Player. + */ + private void recalcHennaStats() + { + _hennaINT = 0; + _hennaSTR = 0; + _hennaCON = 0; + _hennaMEN = 0; + _hennaWIT = 0; + _hennaDEX = 0; + for (Henna h : _henna) + { + if (h == null) + { + continue; + } + + _hennaINT += ((_hennaINT + h.getStatINT()) > 5) ? 5 - _hennaINT : h.getStatINT(); + _hennaSTR += ((_hennaSTR + h.getStatSTR()) > 5) ? 5 - _hennaSTR : h.getStatSTR(); + _hennaMEN += ((_hennaMEN + h.getStatMEN()) > 5) ? 5 - _hennaMEN : h.getStatMEN(); + _hennaCON += ((_hennaCON + h.getStatCON()) > 5) ? 5 - _hennaCON : h.getStatCON(); + _hennaWIT += ((_hennaWIT + h.getStatWIT()) > 5) ? 5 - _hennaWIT : h.getStatWIT(); + _hennaDEX += ((_hennaDEX + h.getStatDEX()) > 5) ? 5 - _hennaDEX : h.getStatDEX(); + } + } + + /** + * @param slot the character inventory henna slot. + * @return the Henna of this Player corresponding to the selected slot. + */ + public Henna getHenna(int slot) + { + return (slot < 1) || (slot > 3) ? null : _henna[slot - 1]; + } + + /** + * @return {@code true} if player has at least 1 henna symbol, {@code false} otherwise. + */ + public boolean hasHennas() + { + for (Henna henna : _henna) + { + if (henna != null) + { + return true; + } + } + + return false; + } + + /** + * @return the henna holder for this player. + */ + public Henna[] getHennaList() + { + return _henna; + } + + /** + * @return the INT Henna modifier of this Player. + */ + public int getHennaStatINT() + { + return _hennaINT; + } + + /** + * @return the STR Henna modifier of this Player. + */ + public int getHennaStatSTR() + { + return _hennaSTR; + } + + /** + * @return the CON Henna modifier of this Player. + */ + public int getHennaStatCON() + { + return _hennaCON; + } + + /** + * @return the MEN Henna modifier of this Player. + */ + public int getHennaStatMEN() + { + return _hennaMEN; + } + + /** + * @return the WIT Henna modifier of this Player. + */ + public int getHennaStatWIT() + { + return _hennaWIT; + } + + /** + * @return the DEX Henna modifier of this Player. + */ + public int getHennaStatDEX() + { + return _hennaDEX; + } + + public void autoSave() + { + storeMe(); + + if (GeneralConfig.UPDATE_ITEMS_ON_CHAR_STORE) + { + getInventory().updateDatabase(); + getWarehouse().updateDatabase(); + getFreight().updateDatabase(); + } + } + + public boolean canLogout() + { + if (_subclassLock) + { + LOGGER.warning("Player " + getName() + " tried to restart/logout during class change."); + return false; + } + + if (_activeEnchantItemId != ID_NONE) + { + return false; + } + + if (AttackStanceTaskManager.getInstance().hasAttackStanceTask(this) && !(isGM() && GeneralConfig.GM_RESTART_FIGHTING)) + { + sendPacket(SystemMessageId.YOU_CANNOT_EXIT_WHILE_IN_COMBAT); + return false; + } + + if (isRegisteredOnEvent()) + { + sendMessage("A superior power doesn't allow you to leave."); + return false; + } + + // Prevent player from logging out if they are a festival participant + // and it is in progress, otherwise notify party members that the player + // is not longer a participant. + if (isFestivalParticipant()) + { + if (SevenSignsFestival.getInstance().isFestivalInitialized()) + { + sendMessage("You cannot log out while you are a participant in a Festival."); + return false; + } + + if (isInParty()) + { + _party.broadcastPacket(new SystemMessage(getName() + " has been removed from the upcoming Festival.")); + } + } + + return true; + } + + /** + * Return True if the Player is autoAttackable.
    + *
    + * Actions: + *
      + *
    • Check if the attacker isn't the Player Pet
    • + *
    • Check if the attacker is Monster
    • + *
    • If the attacker is a Player, check if it is not in the same party
    • + *
    • Check if the Player has Karma
    • + *
    • If the attacker is a Player, check if it is not in the same siege clan (Attacker, Defender)
    • + *
    + */ + @Override + public boolean isAutoAttackable(Creature attacker) + { + if (attacker == null) + { + return false; + } + + // Invisible GM players should not be attackable. + if (isInvisible() && isGM()) + { + return false; + } + + // Check if the attacker isn't the Player Pet. + if ((attacker == this) || (attacker == _summon)) + { + return false; + } + + // Friendly mobs do not attack players. + if (attacker instanceof FriendlyMob) + { + return false; + } + + // Check if the attacker is a Monster. + if (attacker.isMonster()) + { + return true; + } + + // is AutoAttackable if both players are in the same duel and the duel is still going on + final Player attackerPlayer = attacker.asPlayer(); + if (attacker.isPlayable() && (_duelState == Duel.DUELSTATE_DUELLING) && (getDuelId() == attackerPlayer.getDuelId())) + { + return true; + } + + // Check if the attacker is not in the same party. NOTE: Party checks goes before oly checks in order to prevent patry member autoattack at oly. + if (isInParty() && _party.getMembers().contains(attacker)) + { + return false; + } + + // Check if the attacker is in olympia and olympia start. + if (attacker.isPlayer() && attackerPlayer.isInOlympiadMode()) + { + return _inOlympiadMode && _olympiadStart && (attacker.asPlayer().getOlympiadGameId() == getOlympiadGameId()); + } + + // Check if the attacker is in an event. + if (isOnEvent()) + { + return isOnSoloEvent() || (getTeam() != attacker.getTeam()); + } + + // Check if the attacker is a Playable. + if (attacker.isPlayable()) + { + if (isInsideZone(ZoneId.PEACE) || isInsideZone(ZoneId.NO_PVP)) + { + return false; + } + + // Get Player + final Clan clan = getClan(); + final Clan attackerClan = attackerPlayer.getClan(); + if ((clan != null) && (attackerClan != null)) + { + if (clan != attackerClan) + { + final Siege siege = SiegeManager.getInstance().getSiege(getX(), getY(), getZ()); + if (siege != null) + { + // Check if a siege is in progress and if attacker and the Player aren't in the Defender clan. + if (siege.checkIsDefender(attackerClan) && siege.checkIsDefender(clan)) + { + return false; + } + + // Check if a siege is in progress and if attacker and the Player aren't in the Attacker clan. + if (siege.checkIsAttacker(attackerClan) && siege.checkIsAttacker(clan)) + { + // If first mid victory is achieved, attackers can attack attackers. + final Castle castle = CastleManager.getInstance().getCastleById(_siegeSide); + return (castle != null) && castle.isFirstMidVictory(); + } + } + } + + // Check if clan is at war. + if ((getWantsPeace() == 0) && (attackerPlayer.getWantsPeace() == 0) && !isAcademyMember() && clan.isAtWarWith(attackerPlayer.getClanId()) && attackerClan.isAtWarWith(getClanId())) + { + return true; + } + } + + // Check if the Player is in an arena, but NOT siege zone. NOTE: This check comes before clan/ally checks, but after party checks. + // This is done because in arenas, clan/ally members can autoattack if they are not in party. + if ((isInsideZone(ZoneId.PVP) && attackerPlayer.isInsideZone(ZoneId.PVP)) && !(isInsideZone(ZoneId.SIEGE) && attackerPlayer.isInsideZone(ZoneId.SIEGE))) + { + return true; + } + + // Check if the attacker is not in the same clan. + if ((clan != null) && clan.isMember(attacker.getObjectId())) + { + return false; + } + + // Check if the attacker is not in the same ally. + if (attacker.isPlayer() && (getAllyId() != 0) && (getAllyId() == attackerPlayer.getAllyId())) + { + return false; + } + + // Now check again if the Player is in pvp zone, but this time at siege PvP zone, applying clan/ally checks. + if (isInsideZone(ZoneId.PVP) && attackerPlayer.isInsideZone(ZoneId.PVP) && isInsideZone(ZoneId.SIEGE) && attackerPlayer.isInsideZone(ZoneId.SIEGE)) + { + return true; + } + + if (FactionSystemConfig.FACTION_SYSTEM_ENABLED && ((isGood() && attackerPlayer.isEvil()) || (isEvil() && attackerPlayer.isGood()))) + { + return true; + } + } + + if ((attacker instanceof Defender) && (_clan != null)) + { + final Siege siege = SiegeManager.getInstance().getSiege(this); + return (siege != null) && siege.checkIsAttacker(_clan); + } + + if (attacker instanceof Guard) + { + if (FactionSystemConfig.FACTION_SYSTEM_ENABLED && FactionSystemConfig.FACTION_GUARDS_ENABLED && ((_isGood && attacker.asNpc().getTemplate().isClan(FactionSystemConfig.FACTION_EVIL_TEAM_NAME)) || (_isEvil && attacker.asNpc().getTemplate().isClan(FactionSystemConfig.FACTION_GOOD_TEAM_NAME)))) + { + return true; + } + + return (getKarma() > 0); // Guards attack only PK players. + } + + // Check if the Player has Karma. + if ((getKarma() > 0) || (_pvpFlag > 0)) + { + return true; + } + + return false; + } + + /** + * Check if the active Skill can be casted.
    + *
    + * Actions: + *
      + *
    • Check if the skill isn't toggle and is offensive
    • + *
    • Check if the target is in the skill cast range
    • + *
    • Check if the skill is Spoil type and if the target isn't already spoiled
    • + *
    • Check if the caster owns enought consummed Item, enough HP and MP to cast the skill
    • + *
    • Check if the caster isn't sitting
    • + *
    • Check if all skills are enabled and this skill is enabled
    • + *
    • Check if the caster own the weapon needed
    • + *
    • Check if the skill is active
    • + *
    • Check if all casting conditions are completed
    • + *
    • Notify the AI with CAST and target
    • + *
    + * @param skill The Skill to use + * @param forceUse used to force ATTACK on players + * @param dontMove used to prevent movement, if not in range + */ + @Override + public boolean useMagic(Skill skill, boolean forceUse, boolean dontMove) + { + // Check if the skill is active. + if (skill.isPassive()) + { + // just ignore the passive skill request. why does the client send it anyway ?? + // Send a Server->Client packet ActionFailed to the Player. + sendPacket(ActionFailed.STATIC_PACKET); + return false; + } + + // ************************************* Check Casting in Progress ******************************************* + + // If a skill is currently being used, queue this one if this is not the same. + if (isCastingNow()) + { + // Check if new skill different from current skill in progress. + if ((_currentSkill != null) && (skill.getId() == _currentSkill.getSkillId())) + { + sendPacket(ActionFailed.STATIC_PACKET); + return false; + } + else if (isSkillDisabled(skill)) + { + sendPacket(ActionFailed.STATIC_PACKET); + return false; + } + + // Create a new SkillUseHolder object and queue it in the player _queuedSkill. + setQueuedSkill(skill, forceUse, dontMove); + sendPacket(ActionFailed.STATIC_PACKET); + return false; + } + + setCastingNow(true); + + // Create a new SkillUseHolder object and set the player _currentSkill. + // This is used mainly to save & queue the button presses, since Creature has. + // _lastSkillCast which could otherwise replace it + setCurrentSkill(skill, forceUse, dontMove); + if (_queuedSkill != null) + { + setQueuedSkill(null, false, false); + } + + if (!checkUseMagicConditions(skill, forceUse, dontMove)) + { + setCastingNow(false); + return false; + } + + // Check if the target is correct and Notify the AI with CAST and target. + WorldObject target = null; + switch (skill.getTargetType()) + { + case AURA: // AURA, SELF should be cast even if no target has been found + case FRONT_AURA: + case BEHIND_AURA: + case GROUND: + case SELF: + case AURA_CORPSE_MOB: + case COMMAND_CHANNEL: + case AURA_FRIENDLY: + { + target = this; + break; + } + default: + { + // Get the first target of the list. + target = skill.getFirstOfTargetList(this); + break; + } + } + + // Notify the AI with CAST and target. + getAI().setIntentionCast(skill, target); + return true; + } + + private boolean checkUseMagicConditions(Skill skill, boolean forceUse, boolean dontMove) + { + // ************************************* Check Player State ******************************************* + + // Abnormal effects(ex : Stun, Sleep...) are checked in Creature useMagic(). + if (isOutOfControl() || isParalyzed() || isStunned() || isSleeping()) + { + sendPacket(ActionFailed.STATIC_PACKET); + return false; + } + + // Check if the player is dead. + if (isDead()) + { + // Send a Server->Client packet ActionFailed to the Player. + sendPacket(ActionFailed.STATIC_PACKET); + return false; + } + + // Block skill usage based on Formal Wear, Cursed Weapon, or Mount. + final int skillId = skill.getId(); + if (isWearingFormalWear()) // Check if player is wearing formal wear. + { + sendPacket(SystemMessageId.WHILE_DRESSED_IN_FORMAL_WEAR_YOU_CAN_T_USE_ITEMS_THAT_REQUIRE_ALL_SKILLS_AND_CASTING_OPERATIONS); + return false; + } + else if (isCursedWeaponEquipped()) + { + if ((skillId != CommonSkill.VOID_BURST.getId()) && (skillId != CommonSkill.VOID_FLOW.getId())) + { + return false; + } + } + else if (isMounted()) + { + switch (getMountType()) + { + case STRIDER: + { + // Allow magic skills and Strider Siege Assault. + if (!skill.isMagic() && (skillId != CommonSkill.STRIDER_SIEGE_ASSAULT.getId())) + { + return false; + } + break; + } + case WYVERN: + { + // Allow only Wyvern Breath and Wyvern Aegis. + if ((skillId != CommonSkill.WYVERN_BREATH.getId()) && (skillId != CommonSkill.WYVERN_AEGIS.getId())) + { + return false; + } + break; + } + } + } + + if (_fishing && !skill.hasEffectType(EffectType.FISHING, EffectType.FISHING_START)) + { + // Only fishing skills are available + sendPacket(SystemMessageId.ONLY_FISHING_SKILLS_MAY_BE_USED_AT_THIS_TIME); + return false; + } + + if (_observerMode) + { + sendPacket(SystemMessageId.OBSERVERS_CANNOT_PARTICIPATE); + abortCast(); + sendPacket(ActionFailed.STATIC_PACKET); + return false; + } + + // Check if the caster is sitting. + if (_waitTypeSitting) + { + // Send a System Message to the caster. + sendPacket(SystemMessageId.YOU_CANNOT_MOVE_WHILE_SITTING); + + // Send a Server->Client packet ActionFailed to the Player. + sendPacket(ActionFailed.STATIC_PACKET); + return false; + } + + // Check if the skill type is toggle. + if (skill.isToggle() && isAffectedBySkill(skill.getId())) + { + stopSkillEffects(SkillFinishType.REMOVED, skill.getId()); + sendPacket(ActionFailed.STATIC_PACKET); + return false; + } + + // Check if the player uses "Fake Death" skill. + // Note: do not check this before TOGGLE reset + if (_isFakeDeath) + { + // Send a Server->Client packet ActionFailed to the Player. + sendPacket(ActionFailed.STATIC_PACKET); + return false; + } + + // ************************************* Check Target ******************************************* + // Create and set a WorldObject containing the target of the skill. + WorldObject target = null; + final TargetType sklTargetType = skill.getTargetType(); + if ((sklTargetType == TargetType.GROUND) && (_currentSkillWorldPosition == null)) + { + sendPacket(ActionFailed.STATIC_PACKET); + return false; + } + + switch (sklTargetType) + { + // Target the player if skill type is AURA, PARTY, CLAN or SELF. + case AURA: + case FRONT_AURA: + case BEHIND_AURA: + case PARTY: + case CLAN: + case PARTY_CLAN: + case GROUND: + case SELF: + case AREA_SUMMON: + case AURA_CORPSE_MOB: + case COMMAND_CHANNEL: + case AURA_FRIENDLY: + { + target = this; + break; + } + case PET: + case SERVITOR: + case SUMMON: + { + target = _summon; + break; + } + default: + { + target = getTarget(); + break; + } + } + + // Check the validity of the target. + if (target == null) + { + sendPacket(ActionFailed.STATIC_PACKET); + return false; + } + + // Skills can be used on walls and doors only during siege. + if (target.isDoor()) + { + // Check if the door belongs to a castle and if the siege is in progress. + final Door door = target.asDoor(); + if (door.isInsideZone(ZoneId.SIEGE)) + { + final Castle castle = door.getCastle(); + if ((castle != null) && (castle.getResidenceId() > 0) && !castle.getSiege().isInProgress()) + { + sendPacket(SystemMessageId.INVALID_TARGET); + return false; + } + } + else if (skill.getTargetType() != TargetType.UNLOCKABLE) + { + sendPacket(SystemMessageId.INVALID_TARGET); + return false; + } + } + + // Are the target and the player in the same duel? + if (_isInDuel && target.isPlayable()) + { + // Get Player + final Player cha = target.asPlayer(); + if (cha.getDuelId() != getDuelId()) + { + sendMessage("You cannot do this while duelling."); + sendPacket(ActionFailed.STATIC_PACKET); + return false; + } + } + + // ************************************* Check skill availability ******************************************* + + // Check if this skill is enabled (ex : reuse time). + if (isSkillDisabled(skill)) + { + if (hasSkillReuse(skill.getReuseHashCode())) + { + final int remainingTime = (int) (getSkillRemainingReuseTime(skill.getReuseHashCode()) / 1000); + final int hours = remainingTime / 3600; + final int minutes = (remainingTime % 3600) / 60; + final int seconds = remainingTime % 60; + if (hours > 0) + { + sendMessage("There are " + hours + " hour(s), " + minutes + " minute(s), and " + seconds + " second(s) remaining in " + skill.getName() + "'s re-use time."); + } + else if (minutes > 0) + { + sendMessage("There are " + minutes + " minute(s), " + seconds + " second(s) remaining in " + skill.getName() + "'s re-use time."); + } + else + { + sendMessage("There are " + seconds + " second(s) remaining in " + skill.getName() + "'s re-use time."); + } + } + else + { + final SystemMessage sm = new SystemMessage(SystemMessageId.S1_IS_NOT_AVAILABLE_AT_THIS_TIME_BEING_PREPARED_FOR_REUSE); + sm.addSkillName(skill); + sendPacket(sm); + } + + return false; + } + + // ************************************* Check casting conditions ******************************************* + + // Check if all casting conditions are completed. + if (!skill.checkCondition(this, target, false)) + { + // Send a Server->Client packet ActionFailed to the Player. + sendPacket(ActionFailed.STATIC_PACKET); + return false; + } + + // ************************************* Check Skill Type ******************************************* + + // Check if this is bad magic skill. + if (skill.hasNegativeEffect()) + { + if (isInsidePeaceZone(this, target) && !getAccessLevel().allowPeaceAttack()) + { + // If Creature or target is in a peace zone, send a system message TARGET_IN_PEACEZONE a Server->Client packet ActionFailed. + sendPacket(SystemMessageId.YOU_MAY_NOT_ATTACK_THIS_TARGET_IN_A_PEACEFUL_ZONE); + sendPacket(ActionFailed.STATIC_PACKET); + return false; + } + + if (_inOlympiadMode && !_olympiadStart) + { + // If Player is in Olympia and the match isn't already start, send a Server->Client packet ActionFailed. + sendPacket(ActionFailed.STATIC_PACKET); + return false; + } + + if (isSiegeFriend(target)) + { + sendMessage("Force attack is impossible against a temporary allied member during a siege."); + sendPacket(ActionFailed.STATIC_PACKET); + return false; + } + + if (!target.canBeAttacked() && !getAccessLevel().allowPeaceAttack() && !target.isDoor()) + { + // If target is not attackable, send a Server->Client packet ActionFailed. + sendPacket(ActionFailed.STATIC_PACKET); + return false; + } + + // Check for Event Mob's + if ((target instanceof EventMonster) && ((EventMonster) target).eventSkillAttackBlocked()) + { + sendPacket(ActionFailed.STATIC_PACKET); + return false; + } + + // Check if a Forced ATTACK is in progress on non-attackable target. + if (!target.isAutoAttackable(this) && !forceUse) + { + switch (sklTargetType) + { + case AURA: + case FRONT_AURA: + case BEHIND_AURA: + case AURA_CORPSE_MOB: + case CLAN: + case PARTY: + case SELF: + case GROUND: + case AREA_SUMMON: + case UNLOCKABLE: + case AURA_FRIENDLY: + { + break; + } + default: // Send a Server->Client packet ActionFailed to the Player. + { + sendPacket(ActionFailed.STATIC_PACKET); + return false; + } + } + } + + // Check if the target is in the skill cast range. + if (dontMove) + { + // Calculate the distance between the Player and the target. + if (sklTargetType == TargetType.GROUND) + { + if (!isInsideRadius2D(_currentSkillWorldPosition.getX(), _currentSkillWorldPosition.getY(), _currentSkillWorldPosition.getZ(), skill.getCastRange() + getTemplate().getCollisionRadius())) + { + // Send a System Message to the caster. + sendPacket(SystemMessageId.YOUR_TARGET_IS_OUT_OF_RANGE); + + // Send a Server->Client packet ActionFailed to the Player. + sendPacket(ActionFailed.STATIC_PACKET); + return false; + } + } + else if ((skill.getCastRange() > 0) && !isInsideRadius2D(target, skill.getCastRange() + getTemplate().getCollisionRadius())) + { + // Send a System Message to the caster. + sendPacket(SystemMessageId.YOUR_TARGET_IS_OUT_OF_RANGE); + + // Send a Server->Client packet ActionFailed to the Player. + sendPacket(ActionFailed.STATIC_PACKET); + return false; + } + } + } + + // Check if the skill is a good magic, target is a monster and if force attack is set, if not then we don't want to cast. + if ((skill.getEffectPoint() > 0) && target.isMonster() && !forceUse) + { + sendPacket(ActionFailed.STATIC_PACKET); + return false; + } + + // Check if this is a Pvp skill and target isn't a non-flagged/non-karma player. + switch (sklTargetType) + { + case PARTY: + case CLAN: // For such skills, checkPvpSkill() is called from Skill.getTargetList(). + case PARTY_CLAN: // For such skills, checkPvpSkill() is called from Skill.getTargetList(). + case AURA: + case FRONT_AURA: + case BEHIND_AURA: + case AREA_SUMMON: + case GROUND: + case SELF: + { + break; + } + default: + { + // Verify that player can attack a player or summon. + if (target.isPlayable() && !getAccessLevel().allowPeaceAttack() && !checkPvpSkill(target, skill)) + { + // Send a System Message to the player. + sendPacket(SystemMessageId.THAT_IS_THE_INCORRECT_TARGET); + + // Send a Server->Client packet ActionFailed to the player. + sendPacket(ActionFailed.STATIC_PACKET); + return false; + } + } + } + + // GeoData Los Check here + if (skill.getCastRange() > 0) + { + if (sklTargetType == TargetType.GROUND) + { + if (!GeoEngine.getInstance().canSeeTarget(this, _currentSkillWorldPosition)) + { + sendPacket(SystemMessageId.CANNOT_SEE_TARGET); + sendPacket(ActionFailed.STATIC_PACKET); + return false; + } + } + else if (!GeoEngine.getInstance().canSeeTarget(this, target)) + { + sendPacket(SystemMessageId.CANNOT_SEE_TARGET); + sendPacket(ActionFailed.STATIC_PACKET); + return false; + } + } + + // finally, after passing all conditions + return true; + } + + public boolean isInLooterParty(int looterId) + { + final Player looter = World.getPlayer(looterId); + + // If Player is in a CommandChannel. + if (isInParty() && _party.isInCommandChannel() && (looter != null)) + { + return _party.getCommandChannel().getMembers().contains(looter); + } + + return isInParty() && (looter != null) && _party.getMembers().contains(looter); + } + + /** + * Check if the requested casting is a Pc->Pc skill cast and if it's a valid pvp condition + * @param target WorldObject instance containing the target + * @param skill Skill instance with the skill being casted + * @return {@code false} if the skill is a pvpSkill and target is not a valid pvp target, {@code true} otherwise. + */ + public boolean checkPvpSkill(WorldObject target, Skill skill) + { + if ((skill == null) || (target == null)) + { + return false; + } + + if (!target.isPlayable()) + { + return true; + } + + if (skill.isDebuff() || skill.hasEffectType(EffectType.STEAL_ABNORMAL) || skill.hasNegativeEffect()) + { + final Player targetPlayer = target.asPlayer(); + if ((targetPlayer == null) || (this == target)) + { + return false; + } + + // Duel + if (isInDuel() && targetPlayer.isInDuel() && (getDuelId() == targetPlayer.getDuelId())) + { + return true; + } + + // Olympiad + if (isInOlympiadMode() && targetPlayer.isInOlympiadMode() && (getOlympiadGameId() == targetPlayer.getOlympiadGameId())) + { + return true; + } + + // Peace Zone + if (target.isInsideZone(ZoneId.PEACE)) + { + return false; + } + + // PvP Skills + if (skill.isPvPOnly() && ((targetPlayer.getPvpFlag() == 0) && (targetPlayer.getKarma() == 0)) && !isInsideZone(ZoneId.PVP) && !isInsideZone(ZoneId.SIEGE)) + { + return false; + } + + // Siege + if (isSiegeFriend(targetPlayer)) + { + sendMessage("Force attack is impossible against a temporary allied member during a siege."); + return false; + } + + final boolean isCtrlPressed = (_currentSkill != null) && _currentSkill.isCtrlPressed(); + // Party + if ((isInParty() && targetPlayer.isInParty()) // + && ((getParty().getLeader() == targetPlayer.getParty().getLeader()) || ((_party.getCommandChannel() != null) && _party.getCommandChannel().containsPlayer(targetPlayer)))) + { + return (skill.getEffectRange() > 0) && isCtrlPressed && (getTarget() == target) && skill.isDamage(); + } + + // You can debuff anyone except party members while in an arena... + if (isInsideZone(ZoneId.PVP) && targetPlayer.isInsideZone(ZoneId.PVP)) + { + return true; + } + + final Clan tClan = targetPlayer.getClan(); + if ((_clan != null) && (tClan != null)) + { + if (_clan.isAtWarWith(tClan.getId()) && tClan.isAtWarWith(_clan.getId())) + { + // Always return true at war. + return true; + } + else if ((getClanId() == targetPlayer.getClanId()) || ((getAllyId() > 0) && (getAllyId() == targetPlayer.getAllyId()))) + { + // Check if skill can do dmg. + return (skill.getEffectRange() > 0) && isCtrlPressed && (getTarget() == target) && skill.isDamage(); + } + } + + // On retail, it is impossible to debuff a "peaceful" player. + if ((targetPlayer.getPvpFlag() == 0) && (targetPlayer.getKarma() == 0)) + { + // Check if skill can do dmg. + return (skill.getEffectRange() > 0) && isCtrlPressed && (getTarget() == target) && skill.isDamage(); + } + + return (targetPlayer.getPvpFlag() > 0) || (targetPlayer.getKarma() > 0); + } + + return true; + } + + /** + * @return True if the Player is a Mage. + */ + public boolean isMageClass() + { + return getPlayerClass().isMage(); + } + + public boolean isMounted() + { + return _mountType != MountType.NONE; + } + + public boolean checkLandingState() + { + // Check if char is in a no landing zone. + if (isInsideZone(ZoneId.NO_LANDING)) + { + return true; + } + else + // If this is a castle that is currently being sieged, and the rider is NOT a castle owner. + // he cannot land. + // Castle owner is the leader of the clan that owns the castle where the pc is. + if (isInsideZone(ZoneId.SIEGE) && ((getClan() == null) || (CastleManager.getInstance().getCastle(this) != CastleManager.getInstance().getCastleByOwner(getClan())) || (this != getClan().getLeader().getPlayer()))) + { + return true; + } + + return false; + } + + // Returns false if the change of mount type fails. + public void setMount(int npcId, int npcLevel) + { + final MountType type = MountType.findByNpcId(npcId); + switch (type) + { + case NONE: // None + { + if (isFlying()) + { + removeSkill(CommonSkill.WYVERN_BREATH.getSkill().getId(), false); + setFlying(false); + sendSkillList(); + } + break; + } + case WYVERN: // Wyvern + { + setFlying(true); + addSkill(CommonSkill.WYVERN_BREATH.getSkill(), false); + sendSkillList(); + break; + } + } + + _mountType = type; + _mountNpcId = npcId; + _mountLevel = npcLevel; + } + + /** + * @return the type of Pet mounted (0 : none, 1 : Strider, 2 : Wyvern, 3: Wolf). + */ + public MountType getMountType() + { + return _mountType; + } + + @Override + public void stopAllEffects() + { + super.stopAllEffects(); + updateAndBroadcastStatus(); + } + + @Override + public void stopAllEffectsExceptThoseThatLastThroughDeath() + { + super.stopAllEffectsExceptThoseThatLastThroughDeath(); + updateAndBroadcastStatus(); + } + + public void stopAllEffectsNotStayOnSubclassChange() + { + getEffectList().stopAllEffectsNotStayOnSubclassChange(); + updateAndBroadcastStatus(); + } + + public void stopCubics() + { + if (_cubics.isEmpty()) + { + return; + } + + for (Cubic cubic : _cubics.values()) + { + cubic.stopAction(); + cubic.cancelDisappear(); + } + + _cubics.clear(); + _cubicOrder.clear(); + broadcastUserInfo(); + } + + public void stopCubicsByOthers() + { + if (_cubics.isEmpty()) + { + return; + } + + boolean broadcast = false; + for (Cubic cubic : _cubics.values()) + { + if (cubic.givenByOther()) + { + removeCubic(cubic.getId()); + broadcast = true; + } + } + + if (broadcast) + { + broadcastUserInfo(); + } + } + + /** + * Send a Server->Client packet UserInfo to this Player and CharInfo to all known players. + */ + @Override + public void updateAbnormalEffect() + { + broadcastUserInfo(); + } + + /** + * Disable the Inventory and create a new task to enable it after 1.5s. + * @param value + */ + public void setInventoryBlockingStatus(boolean value) + { + _inventoryDisable = value; + if (value) + { + ThreadPool.schedule(new InventoryEnableTask(this), 1500); + } + } + + /** + * @return True if the Inventory is disabled. + */ + public boolean isInventoryDisabled() + { + return _inventoryDisable; + } + + /** + * Add a cubic to this player. + * @param cubicId the cubic ID + * @param level + * @param cubicPower + * @param cubicDelay + * @param cubicSkillChance + * @param cubicMaxCount + * @param cubicDuration + * @param givenByOther + * @return the old cubic for this cubic ID if any, otherwise {@code null} + */ + public Cubic addCubic(int cubicId, int level, double cubicPower, int cubicDelay, int cubicSkillChance, int cubicMaxCount, int cubicDuration, boolean givenByOther) + { + final Cubic cubic = new Cubic(this, cubicId, level, (int) cubicPower, cubicDelay, cubicSkillChance, cubicMaxCount, cubicDuration, givenByOther); + final Cubic oldCubic = _cubics.put(cubicId, cubic); + _cubicOrder.remove(cubicId); + _cubicOrder.add(cubicId); + return oldCubic; + } + + /** + * Remove a cubic and cancel its scheduled tasks. + * @param cubicId the cubic ID + * @return the removed cubic, {@code null} otherwise + */ + public Cubic removeCubic(int cubicId) + { + final Cubic cubic = _cubics.remove(cubicId); + _cubicOrder.remove(cubicId); + if (cubic != null) + { + cubic.stopAction(); + cubic.cancelDisappear(); + } + return cubic; + } + + /** + * Remove the oldest cubic by summon order. + * @return the removed cubic, {@code null} otherwise + */ + public Cubic removeFirstCubic() + { + while (!_cubicOrder.isEmpty()) + { + final Integer cubicId = _cubicOrder.poll(); + if (cubicId == null) + { + return null; + } + + final Cubic cubic = _cubics.remove(cubicId); + if (cubic != null) + { + cubic.stopAction(); + cubic.cancelDisappear(); + return cubic; + } + } + return null; + } + + /** + * Get the player's cubics. + * @return the cubics + */ + public Map getCubics() + { + return Collections.unmodifiableMap(_cubics); + } + + /** + * Get the player cubic by cubic ID, if any. + * @param cubicId the cubic ID + * @return the cubic with the given cubic ID, {@code null} otherwise + */ + public Cubic getCubicById(int cubicId) + { + return _cubics.get(cubicId); + } + + /** + * @return the modifier corresponding to the Enchant Effect of the Active Weapon (Min : 127). + */ + public int getEnchantEffect() + { + final Item wpn = getActiveWeaponInstance(); + return wpn == null ? 0 : Math.min(127, wpn.getEnchantLevel()); + } + + /** + * Set the _lastFolkNpc of the Player corresponding to the last Folk wich one the player talked. + * @param folkNpc + */ + public void setLastFolkNPC(Npc folkNpc) + { + _lastFolkNpc = folkNpc; + } + + /** + * @return the _lastFolkNpc of the Player corresponding to the last Folk wich one the player talked. + */ + public Npc getLastFolkNPC() + { + return _lastFolkNpc; + } + + /** + * @return True if Player is a participant in the Festival of Darkness. + */ + public boolean isFestivalParticipant() + { + return SevenSignsFestival.getInstance().isParticipant(this); + } + + public void addAutoSoulShot(int itemId) + { + _activeSoulShots.add(itemId); + } + + public boolean removeAutoSoulShot(int itemId) + { + return _activeSoulShots.remove(itemId); + } + + public Set getAutoSoulShot() + { + return _activeSoulShots; + } + + @Override + public void rechargeShots(boolean physical, boolean magic) + { + Item item; + IItemHandler handler; + if ((_activeSoulShots == null) || _activeSoulShots.isEmpty()) + { + return; + } + + for (int itemId : _activeSoulShots) + { + item = _inventory.getItemByItemId(itemId); + if (item != null) + { + if (magic && (item.getTemplate().getDefaultAction() == ActionType.SPIRITSHOT)) + { + handler = ItemHandler.getInstance().getHandler(item.getEtcItem()); + if (handler != null) + { + handler.onItemUse(this, item, false); + } + } + + if (physical && (item.getTemplate().getDefaultAction() == ActionType.SOULSHOT)) + { + handler = ItemHandler.getInstance().getHandler(item.getEtcItem()); + if (handler != null) + { + handler.onItemUse(this, item, false); + } + } + } + else + { + removeAutoSoulShot(itemId); + } + } + } + + /** + * Cancel autoshot for all shots matching crystaltype {@link ItemTemplate#getCrystalType()}. + * @param crystalType int type to disable + */ + public void disableAutoShotByCrystalType(int crystalType) + { + for (int itemId : _activeSoulShots) + { + if (ItemData.getInstance().getTemplate(itemId).getCrystalType().getLevel() == crystalType) + { + disableAutoShot(itemId); + } + } + } + + /** + * Cancel autoshot use for shot itemId + * @param itemId int id to disable + * @return true if canceled. + */ + public boolean disableAutoShot(int itemId) + { + if (_activeSoulShots.contains(itemId)) + { + removeAutoSoulShot(itemId); + sendPacket(new ExAutoSoulShot(itemId, 0)); + + final SystemMessage sm = new SystemMessage(SystemMessageId.THE_AUTOMATIC_USE_OF_S1_HAS_BEEN_DEACTIVATED); + sm.addItemName(itemId); + sendPacket(sm); + return true; + } + + return false; + } + + /** + * Cancel all autoshots for player + */ + public void disableAutoShotsAll() + { + for (int itemId : _activeSoulShots) + { + sendPacket(new ExAutoSoulShot(itemId, 0)); + final SystemMessage sm = new SystemMessage(SystemMessageId.THE_AUTOMATIC_USE_OF_S1_HAS_BEEN_DEACTIVATED); + sm.addItemName(itemId); + sendPacket(sm); + } + + _activeSoulShots.clear(); + } + + public ClanPrivileges getClanPrivileges() + { + return _clanPrivileges; + } + + public void setClanPrivileges(ClanPrivileges clanPrivileges) + { + _clanPrivileges = clanPrivileges.clone(); + } + + public boolean hasAccess(ClanAccess access) + { + return _clanPrivileges.hasMinimumPrivileges(access); + } + + // baron etc + public void setPledgeClass(int id) + { + _pledgeClass = id; + checkItemRestriction(); + } + + public int getPledgeClass() + { + return _pledgeClass; + } + + public void setPledgeType(int typeId) + { + _pledgeType = typeId; + } + + @Override + public int getPledgeType() + { + return _pledgeType; + } + + public int getApprentice() + { + return _apprentice; + } + + public void setApprentice(int apprenticeId) + { + _apprentice = apprenticeId; + } + + public int getSponsor() + { + return _sponsor; + } + + public void setSponsor(int sponsorId) + { + _sponsor = sponsorId; + } + + @Override + public void sendMessage(String message) + { + sendPacket(new SystemMessage(SendMessageLocalisationData.getLocalisation(this, message))); + } + + /** + * Sends a system message to the player. + *

    + * If the GM startup builder hide configuration is enabled, the message will be sent using a localized say packet. Otherwise, the message will be sent using the standard sendMessage method. + *

    + * @param message the message to send to the player. + */ + public void sendSysMessage(String message) + { + if (GeneralConfig.GM_STARTUP_BUILDER_HIDE) + { + sendPacket(new CreatureSay(null, ChatType.GENERAL, "SYS", SendMessageLocalisationData.getLocalisation(this, message))); + } + else + { + sendMessage(message); + } + } + + /** + * Toggles the hiding state for GM characters. + *

    + * Only applicable for GM characters. This method sets the player's invisibility, invulnerability and silence mode based on the provided hiding state. It also updates the player's abnormal visual effects and broadcasts user info. + *

    + * @param hide {@code true} to enable hiding (invisibility, silence, invulnerability), {@code false} to disable hiding. + * @return {@code true} if the hiding state was changed, {@code false} if the state was already set or the player is not a GM. + */ + public boolean setHiding(boolean hide) + { + if (!isGM()) + { + return false; + } + + if (hasEnteredWorld()) + { + if (isInvisible() && hide) + { + // Already hiding. + return false; + } + + if (!isInvisible() && !hide) + { + // Already visible. + return false; + } + } + + setSilenceMode(hide); + setInvul(hide); + setInvisible(hide); + + if (hide && hasEnteredWorld()) + { + World.forEachVisibleObject(this, Player.class, player -> + { + if (player.getTarget() == this) + { + player.setTarget(null); + } + }); + } + + broadcastUserInfo(); + return true; + } + + public void enterObserverMode(Location loc) + { + setLastLocation(); + + // Remove Hide. + getEffectList().stopSkillEffects(SkillFinishType.REMOVED, AbnormalType.HIDE); + _observerMode = true; + stopCubics(); + setTarget(null); + setParalyzed(true); + startParalyze(); + setInvul(true); + setInvisible(true); + sendPacket(new ObservationEnter(loc)); + teleToLocation(loc, false); + broadcastUserInfo(); + } + + public void setLastLocation() + { + _lastLoc.setXYZ(getX(), getY(), getZ()); + } + + public void unsetLastLocation() + { + _lastLoc.setXYZ(0, 0, 0); + } + + public void enterOlympiadObserverMode(Location loc, int id, boolean storeCoords) + { + if (hasSummon()) + { + _summon.unSummon(this); + } + + // Remove Hide. + getEffectList().stopSkillEffects(SkillFinishType.REMOVED, AbnormalType.HIDE); + if (!_cubics.isEmpty()) + { + for (Cubic cubic : _cubics.values()) + { + cubic.stopAction(); + cubic.cancelDisappear(); + } + + _cubics.clear(); + _cubicOrder.clear(); + } + + if (_party != null) + { + _party.removePartyMember(this, PartyMessageType.EXPELLED); + } + + _olympiadGameId = id; + if (_waitTypeSitting) + { + standUp(); + } + + if (storeCoords) + { + setLastLocation(); + } + + _observerMode = true; + setTarget(null); + setInvul(true); + setInvisible(true); + teleToLocation(loc, 0); + sendPacket(new ExOlympiadMode(3)); + broadcastUserInfo(); + } + + public void leaveObserverMode() + { + setTarget(null); + + teleToLocation(_lastLoc, false); + unsetLastLocation(); + sendPacket(new ObservationExit(getLocation())); + setParalyzed(false); + if (!isGM()) + { + setInvisible(false); + setInvul(false); + } + + if (hasAI()) + { + getAI().setIntentionIdle(); + } + + stopPvPFlag(); + setFalling(); // Prevent receive falling damage. + _observerMode = false; + broadcastUserInfo(); + } + + public void leaveOlympiadObserverMode() + { + if (_olympiadGameId == -1) + { + return; + } + + Olympiad.removeSpectator(_olympiadGameId, this); + _olympiadGameId = -1; + _observerMode = false; + setTarget(null); + sendPacket(new ExOlympiadMode(0)); + setInstanceId(0); + teleToLocation(_lastLoc, true); + if (!isGM()) + { + setInvisible(false); + setInvul(false); + } + + if (hasAI()) + { + getAI().setIntentionIdle(); + } + + unsetLastLocation(); + broadcastUserInfo(); + } + + public void setOlympiadSide(int i) + { + _olympiadSide = i; + } + + public int getOlympiadSide() + { + return _olympiadSide; + } + + public void setOlympiadGameId(int id) + { + _olympiadGameId = id; + } + + public int getOlympiadGameId() + { + return _olympiadGameId; + } + + /** + * Gets the player's olympiad buff count. + * @return the olympiad's buff count + */ + public int getOlympiadBuffCount() + { + return _olyBuffsCount; + } + + /** + * Sets the player's olympiad buff count. + * @param buffs the olympiad's buff count + */ + public void setOlympiadBuffCount(int buffs) + { + _olyBuffsCount = buffs; + } + + public Location getLastLocation() + { + return _lastLoc; + } + + public boolean inObserverMode() + { + return _observerMode; + } + + public int getTeleMode() + { + return _telemode; + } + + public void setTeleMode(int mode) + { + _telemode = mode; + } + + public void setLoto(int i, int value) + { + _loto[i] = value; + } + + public int getLoto(int i) + { + return _loto[i]; + } + + public void setRaceTicket(int i, int value) + { + _raceTickets[i] = value; + } + + public int getRaceTicket(int i) + { + return _raceTickets[i]; + } + + public boolean getMessageRefusal() + { + return _messageRefusal; + } + + public void setMessageRefusal(boolean mode) + { + _messageRefusal = mode; + sendPacket(new EtcStatusUpdate(this)); + } + + public void setDietMode(boolean mode) + { + _dietMode = mode; + } + + public boolean getDietMode() + { + return _dietMode; + } + + public void setTradeRefusal(boolean mode) + { + _tradeRefusal = mode; + } + + public boolean getTradeRefusal() + { + return _tradeRefusal; + } + + public void setExchangeRefusal(boolean mode) + { + _exchangeRefusal = mode; + } + + public boolean getExchangeRefusal() + { + return _exchangeRefusal; + } + + public BlockList getBlockList() + { + return _blockList; + } + + public void setHero(boolean hero) + { + if (hero && (_baseClass == _activeClass)) + { + for (Skill skill : SkillTreeData.getInstance().getHeroSkillTree().values()) + { + addSkill(skill, false); // Don't persist hero skills into database. + } + } + else + { + for (Skill skill : SkillTreeData.getInstance().getHeroSkillTree().values()) + { + removeSkill(skill, false, true); // Just remove skills from non-hero players. + } + } + + _hero = hero; + sendSkillList(); + } + + public void setInOlympiadMode(boolean value) + { + _inOlympiadMode = value; + } + + public void setOlympiadStart(boolean value) + { + _olympiadStart = value; + } + + public boolean isOlympiadStart() + { + return _olympiadStart; + } + + public boolean isHero() + { + return _hero; + } + + public boolean isInOlympiadMode() + { + return _inOlympiadMode; + } + + @Override + public boolean isInDuel() + { + return _isInDuel; + } + + public void setStartingDuel() + { + _startingDuel = true; + } + + @Override + public int getDuelId() + { + return _duelId; + } + + public void setDuelState(int mode) + { + _duelState = mode; + } + + public int getDuelState() + { + return _duelState; + } + + /** + * Sets up the duel state using a non 0 duelId. + * @param duelId 0=not in a duel + */ + public void setInDuel(int duelId) + { + if (duelId > 0) + { + _isInDuel = true; + _duelState = Duel.DUELSTATE_DUELLING; + _duelId = duelId; + } + else + { + if (_duelState == Duel.DUELSTATE_DEAD) + { + enableAllSkills(); + getStatus().startHpMpRegeneration(); + } + + _isInDuel = false; + _duelState = Duel.DUELSTATE_NODUEL; + _duelId = 0; + } + + _startingDuel = false; + } + + /** + * This returns a SystemMessage stating why the player is not available for duelling. + * @return S1_CANNOT_DUEL... message + */ + public SystemMessage getNoDuelReason() + { + final SystemMessage sm = new SystemMessage(_noDuelReason); + sm.addPcName(this); + _noDuelReason = SystemMessageId.THERE_IS_NO_OPPONENT_TO_RECEIVE_YOUR_CHALLENGE_FOR_A_DUEL; + return sm; + } + + /** + * Checks if this player might join / start a duel.
    + * To get the reason use getNoDuelReason() after calling this function. + * @return true if the player might join/start a duel. + */ + public boolean canDuel() + { + if (isInCombat() || isJailed()) + { + _noDuelReason = SystemMessageId.S1_CANNOT_DUEL_BECAUSE_S1_IS_CURRENTLY_ENGAGED_IN_BATTLE; + return false; + } + + if (isDead() || isAlikeDead() || ((getCurrentHp() < (getMaxHp() / 2)) || (getCurrentMp() < (getMaxMp() / 2)))) + { + _noDuelReason = SystemMessageId.S1_CANNOT_DUEL_BECAUSE_S1_S_HP_OR_MP_IS_BELOW_50_PERCENT; + return false; + } + + if (_isInDuel || _startingDuel) + { + _noDuelReason = SystemMessageId.S1_CANNOT_DUEL_BECAUSE_S1_IS_ALREADY_ENGAGED_IN_A_DUEL; + return false; + } + + if (_inOlympiadMode) + { + _noDuelReason = SystemMessageId.S1_CANNOT_DUEL_BECAUSE_S1_IS_PARTICIPATING_IN_THE_OLYMPIAD; + return false; + } + + if (isCursedWeaponEquipped()) + { + _noDuelReason = SystemMessageId.S1_CANNOT_DUEL_BECAUSE_S1_IS_IN_A_CHAOTIC_STATE; + return false; + } + + if (_privateStoreType != PrivateStoreType.NONE) + { + _noDuelReason = SystemMessageId.S1_CANNOT_DUEL_BECAUSE_S1_IS_CURRENTLY_ENGAGED_IN_A_PRIVATE_STORE_OR_MANUFACTURE; + return false; + } + + if (isMounted() || isInBoat()) + { + _noDuelReason = SystemMessageId.S1_CANNOT_DUEL_BECAUSE_S1_IS_CURRENTLY_RIDING_A_BOAT_WYVERN_OR_STRIDER; + return false; + } + + if (_fishing) + { + _noDuelReason = SystemMessageId.S1_CANNOT_DUEL_BECAUSE_S1_IS_CURRENTLY_FISHING; + return false; + } + + if (isInsideZone(ZoneId.PVP) || isInsideZone(ZoneId.PEACE) || isInsideZone(ZoneId.SIEGE) || isInsideZone(ZoneId.NO_PVP)) + { + _noDuelReason = SystemMessageId.S1_CANNOT_MAKE_A_CHALLENGE_TO_A_DUEL_BECAUSE_S1_IS_CURRENTLY_IN_A_DUEL_PROHIBITED_AREA_PEACEFUL_ZONE_SEVEN_SIGNS_ZONE_NEAR_WATER_RESTART_PROHIBITED_AREA; + return false; + } + + return true; + } + + public boolean isNoble() + { + return _noble; + } + + public void setNoble(boolean value) + { + final Collection nobleSkillTree = SkillTreeData.getInstance().getNobleSkillTree().values(); + if (value) + { + for (Skill skill : nobleSkillTree) + { + addSkill(skill, false); + } + } + else + { + for (Skill skill : nobleSkillTree) + { + removeSkill(skill, false, true); + } + } + + _noble = value; + sendSkillList(); + } + + public void setLvlJoinedAcademy(int lvl) + { + _lvlJoinedAcademy = lvl; + } + + public int getLvlJoinedAcademy() + { + return _lvlJoinedAcademy; + } + + @Override + public boolean isAcademyMember() + { + return _lvlJoinedAcademy > 0; + } + + @Override + public void setTeam(Team team) + { + super.setTeam(team); + broadcastUserInfo(); + if (hasSummon()) + { + _summon.broadcastStatusUpdate(); + } + } + + public void setWantsPeace(int wantsPeace) + { + _wantsPeace = wantsPeace; + } + + public int getWantsPeace() + { + return _wantsPeace; + } + + public boolean isFishing() + { + return _fishing; + } + + public void setFishing(boolean fishing) + { + _fishing = fishing; + } + + public synchronized void sendSkillList() + { + if (_skillListTask == null) + { + _skillListTask = ThreadPool.schedule(() -> + { + final SkillList skillList = new SkillList(); + for (Skill skill : getAllSkills()) + { + if (skill == null) + { + continue; + } + + final int skillId = skill.getId(); + boolean isDisabled = false; + + // Disable clan skills if clan reputation is negative. + if ((_clan != null) && skill.isClanSkill() && (_clan.getReputationScore() < 0)) + { + isDisabled = true; + } + + if (!skill.isPassive()) // Skip passive skills. + { + if (isWearingFormalWear()) + { + isDisabled = true; + } + else if (isCursedWeaponEquipped()) + { + if ((skillId != CommonSkill.VOID_BURST.getId()) && (skillId != CommonSkill.VOID_FLOW.getId())) + { + isDisabled = true; + } + } + else if (isMounted()) + { + switch (getMountType()) + { + case STRIDER: + { + // Allow magic skills and Strider Siege Assault. + if (!skill.isMagic() && (skillId != CommonSkill.STRIDER_SIEGE_ASSAULT.getId())) + { + isDisabled = true; + } + break; + } + case WYVERN: + { + // Allow only Wyvern Breath and Wyvern Aegis. + if ((skillId != CommonSkill.WYVERN_BREATH.getId()) && (skillId != CommonSkill.WYVERN_AEGIS.getId())) + { + isDisabled = true; + } + break; + } + } + } + } + + skillList.addSkill(skill.getDisplayId(), skill.getDisplayLevel(), skill.isPassive(), isDisabled); + } + + sendPacket(skillList); + _skillListTask = null; + }, 300); + } + } + + /** + * 1. Add the specified class ID as a subclass (up to the maximum number of three) for this character.
    + * 2. This method no longer changes the active _classIndex of the player. This is only done by the calling of setActiveClass() method as that should be the only way to do so. + * @param classId + * @param classIndex + * @return boolean subclassAdded + */ + public boolean addSubClass(int classId, int classIndex) + { + if (_subclassLock) + { + return false; + } + + _subclassLock = true; + + try + { + if ((getTotalSubClasses() == PlayerConfig.MAX_SUBCLASS) || (classIndex == 0)) + { + return false; + } + + if (getSubClasses().containsKey(classIndex)) + { + return false; + } + + // Note: Never change _classIndex in any method other than setActiveClass(). + + final SubClassHolder newClass = new SubClassHolder(); + newClass.setPlayerClass(classId); + newClass.setClassIndex(classIndex); + + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement(ADD_CHAR_SUBCLASS)) + { + // Store the basic info about this new sub-class. + ps.setInt(1, getObjectId()); + ps.setInt(2, newClass.getId()); + ps.setLong(3, newClass.getExp()); + ps.setLong(4, newClass.getSp()); + ps.setInt(5, newClass.getLevel()); + ps.setInt(6, newClass.getClassIndex()); // <-- Added + ps.execute(); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "WARNING: Could not add character sub class for " + getName() + ": " + e.getMessage(), e); + return false; + } + + // Commit after database INSERT incase exception is thrown. + getSubClasses().put(newClass.getClassIndex(), newClass); + + final PlayerClass subTemplate = PlayerClass.getPlayerClass(classId); + final Map skillTree = SkillTreeData.getInstance().getCompleteClassSkillTree(subTemplate); + final Map prevSkillList = new HashMap<>(); + for (SkillLearn skillInfo : skillTree.values()) + { + if (skillInfo.getGetLevel() <= 40) + { + final Skill prevSkill = prevSkillList.get(skillInfo.getSkillId()); + final Skill newSkill = SkillData.getInstance().getSkill(skillInfo.getSkillId(), skillInfo.getSkillLevel()); + if ((prevSkill != null) && (prevSkill.getLevel() > newSkill.getLevel())) + { + continue; + } + + prevSkillList.put(newSkill.getId(), newSkill); + storeSkill(newSkill, prevSkill, classIndex); + } + } + + return true; + } + finally + { + _subclassLock = false; + } + } + + /** + * 1. Completely erase all existance of the subClass linked to the classIndex.
    + * 2. Send over the newClassId to addSubClass() to create a new instance on this classIndex.
    + * 3. Upon Exception, revert the player to their BaseClass to avoid further problems. + * @param classIndex the class index to delete + * @param newClassId the new class Id + * @return {@code true} if the sub-class was modified, {@code false} otherwise + */ + public boolean modifySubClass(int classIndex, int newClassId) + { + final SubClassHolder subClass = getSubClasses().get(classIndex); + if (subClass == null) + { + return false; + } + + // Notify to scripts before class is removed. + if (EventDispatcher.getInstance().hasListener(EventType.ON_PLAYER_PROFESSION_CANCEL, this)) + { + EventDispatcher.getInstance().notifyEventAsync(new OnPlayerProfessionCancel(this, subClass.getId()), this); + } + + getSubClasses().remove(classIndex); + + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement deleteHennas = con.prepareStatement(DELETE_CHAR_HENNA); + PreparedStatement deleteShortcuts = con.prepareStatement(DELETE_CHAR_SHORTCUTS); + PreparedStatement deleteSkillReuse = con.prepareStatement(DELETE_SKILL_SAVE); + PreparedStatement deleteSkills = con.prepareStatement(DELETE_CHAR_SKILLS); + PreparedStatement deleteSubclass = con.prepareStatement(DELETE_CHAR_SUBCLASS)) + { + // Remove class permitted hennas. + for (int slot = 1; slot < 4; slot++) + { + final Henna henna = getHenna(slot); + if (henna != null) + { + deleteHennas.setInt(1, getObjectId()); + deleteHennas.setInt(2, slot); + deleteHennas.setInt(3, classIndex); + deleteHennas.execute(); + } + } + + // Remove all shortcuts info stored for this sub-class. + deleteShortcuts.setInt(1, getObjectId()); + deleteShortcuts.setInt(2, classIndex); + deleteShortcuts.execute(); + + // Remove all effects info stored for this sub-class. + deleteSkillReuse.setInt(1, getObjectId()); + deleteSkillReuse.setInt(2, classIndex); + deleteSkillReuse.execute(); + + // Remove all skill info stored for this sub-class. + deleteSkills.setInt(1, getObjectId()); + deleteSkills.setInt(2, classIndex); + deleteSkills.execute(); + + // Remove all basic info stored about this sub-class. + deleteSubclass.setInt(1, getObjectId()); + deleteSubclass.setInt(2, classIndex); + deleteSubclass.execute(); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Could not modify sub class for " + getName() + " to class index " + classIndex + ": " + e.getMessage(), e); + return false; + } + + return addSubClass(newClassId, classIndex); + } + + public boolean isSubClassActive() + { + return _classIndex > 0; + } + + public Map getSubClasses() + { + return _subClasses; + } + + public int getTotalSubClasses() + { + return getSubClasses().size(); + } + + public int getBaseClass() + { + return _baseClass; + } + + public int getActiveClass() + { + return _activeClass; + } + + public int getClassIndex() + { + return _classIndex; + } + + private void setClassTemplate(int classId) + { + _activeClass = classId; + + final PlayerTemplate pcTemplate = PlayerTemplateData.getInstance().getTemplate(classId); + if (pcTemplate == null) + { + LOGGER.severe("Missing template for classId: " + classId); + throw new Error(); + } + + // Set the template of the Player. + setTemplate(pcTemplate); + + // Notify to scripts. + if (EventDispatcher.getInstance().hasListener(EventType.ON_PLAYER_PROFESSION_CHANGE, this)) + { + EventDispatcher.getInstance().notifyEventAsync(new OnPlayerProfessionChange(this, pcTemplate, isSubClassActive()), this); + } + } + + /** + * Changes the character's class based on the given class index.
    + * An index of zero specifies the character's original (base) class, while indexes 1-3 specifies the character's sub-classes respectively.
    + * WARNING: Use only on subclase change + * @param classIndex + */ + public void setActiveClass(int classIndex) + { + if (_subclassLock) + { + return; + } + + _subclassLock = true; + + try + { + // Remove active item skills before saving char to database. + // because next time when choosing this class, weared items can + // be different + for (Item item : _inventory.getAugmentedItems()) + { + if ((item != null) && item.isEquipped()) + { + item.getAugmentation().removeBonus(this); + } + } + + // Abort any kind of cast. + abortCast(); + + if (isChannelized()) + { + getSkillChannelized().abortChannelization(); + } + + // 1. Call store() before modifying _classIndex to avoid skill effects rollover. + // 2. Register the correct _classId against applied 'classIndex'. + store(PlayerConfig.SUBCLASS_STORE_SKILL_COOLTIME); + + resetTimeStamps(); + + // Clear charges. + _charges.set(0); + stopChargeTask(); + + if (hasServitor()) + { + _summon.unSummon(this); + } + + if (classIndex == 0) + { + setClassTemplate(_baseClass); + } + else + { + try + { + setClassTemplate(getSubClasses().get(classIndex).getId()); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Could not switch " + getName() + "'s sub class to class index " + classIndex + ": " + e.getMessage(), e); + return; + } + } + + _classIndex = classIndex; + setLearningClass(getPlayerClass()); + + if (isInParty()) + { + _party.recalculatePartyLevel(); + } + + // Update the character's change in class status. + // 1. Remove any active cubics from the player. + // 2. Renovate the characters table in the database with the new class info, storing also buff/effect data. + // 3. Remove all existing skills. + // 4. Restore all the learned skills for the current class from the database. + // 5. Restore effect/buff data for the new class. + // 6. Restore henna data for the class, applying the new stat modifiers while removing existing ones. + // 7. Reset HP/MP/CP stats and send Server->Client character status packet to reflect changes. + // 8. Restore shortcut data related to this class. + // 9. Resend a class change animation effect to broadcast to all nearby players. + _autoUseSettings.getAutoSkills().clear(); + _autoUseSettings.getAutoBuffs().clear(); + for (Skill oldSkill : getAllSkills()) + { + removeSkill(oldSkill, false, true); + } + + stopAllEffectsExceptThoseThatLastThroughDeath(); + stopAllEffectsNotStayOnSubclassChange(); + getEffectList().stopAllToggles(); + stopCubics(); + restoreRecipeBook(false); + + // Restore any Death Penalty Buff. + restoreDeathPenaltyBuffLevel(); + + restoreSkills(); + rewardSkills(); + regiveTemporarySkills(); + getInventory().applyItemSkills(); + + // Prevents some issues when changing between subclases that shares skills. + resetDisabledSkills(); + + restoreEffects(); + + sendPacket(new EtcStatusUpdate(this)); + + // if player has quest 422: Repent Your Sins, remove it + final QuestState st = getQuestState("Q00422_RepentYourSins"); + if (st != null) + { + st.exitQuest(true); + } + + for (int i = 0; i < 3; i++) + { + _henna[i] = null; + } + + restoreHenna(); + sendPacket(new HennaInfo(this)); + if (getCurrentHp() > getMaxHp()) + { + setCurrentHp(getMaxHp()); + } + + if (getCurrentMp() > getMaxMp()) + { + setCurrentMp(getMaxMp()); + } + + if (getCurrentCp() > getMaxCp()) + { + setCurrentCp(getMaxCp()); + } + + refreshOverloaded(); + refreshExpertisePenalty(); + broadcastUserInfo(); + + // Clear resurrect xp calculation. + setExpBeforeDeath(0); + + _shortcuts.restoreMe(); + sendPacket(new ShortcutInit(this)); + broadcastPacket(new SocialAction(getObjectId(), SocialAction.LEVEL_UP)); + sendPacket(new SkillCoolTime(this)); + sendPacket(new ExStorageMaxCount(this)); + } + finally + { + _subclassLock = false; + } + } + + public void stopWarnUserTakeBreak() + { + if (_taskWarnUserTakeBreak == null) + { + return; + } + + _taskWarnUserTakeBreak.cancel(true); + _taskWarnUserTakeBreak = null; + } + + public void startWarnUserTakeBreak() + { + if (_taskWarnUserTakeBreak == null) + { + _taskWarnUserTakeBreak = ThreadPool.scheduleAtFixedRate(new WarnUserTakeBreakTask(this), 7200000, 7200000); + } + } + + public void stopRentPet() + { + if (_taskRentPet != null) + { + // If the rent of a wyvern expires while over a flying zone, tp to down before unmounting. + if (checkLandingState() && (_mountType == MountType.WYVERN)) + { + teleToLocation(TeleportWhereType.TOWN); + } + + if (dismount()) // This should always be true now, since we teleported already. + { + _taskRentPet.cancel(true); + _taskRentPet = null; + } + } + } + + public void startRentPet(int seconds) + { + if (_taskRentPet == null) + { + _taskRentPet = ThreadPool.scheduleAtFixedRate(new RentPetTask(this), seconds * 1000, seconds * 1000); + } + } + + public boolean isRentedPet() + { + return _taskRentPet != null; + } + + public void stopWaterTask() + { + if (_taskWater == null) + { + return; + } + + _taskWater.cancel(false); + _taskWater = null; + sendPacket(new SetupGauge(getObjectId(), 2, 0)); + } + + public void startWaterTask() + { + if (isDead() || (_taskWater != null)) + { + return; + } + + // Base time in water is 60 seconds (60000 milliseconds). + final int baseTimeInWater = 60000; + + // Calculate the total stat as a percentage of the base time. + final double breathPercentage = calcStat(Stat.BREATH, getBaseTemplate().getBaseBreath()) / 100d; + + // Apply the percentage to the base time. + final int timeInWater = (int) (baseTimeInWater * breathPercentage); + + // Send the setup gauge packet with the calculated time. + sendPacket(new SetupGauge(getObjectId(), 2, timeInWater)); + + // Schedule the water task. + _taskWater = ThreadPool.scheduleAtFixedRate(new WaterTask(this), timeInWater, 1000); + } + + public boolean isInWater() + { + return _taskWater != null; + } + + public void checkWaterState() + { + if (isInsideZone(ZoneId.WATER)) + { + startWaterTask(); + } + else + { + stopWaterTask(); + } + } + + public void onPlayerEnter() + { + startWarnUserTakeBreak(); + + if (SevenSigns.getInstance().isSealValidationPeriod() || SevenSigns.getInstance().isCompResultsPeriod()) + { + if (!isGM() && isIn7sDungeon() && (SevenSigns.getInstance().getPlayerCabal(getObjectId()) != SevenSigns.getInstance().getCabalHighestScore())) + { + teleToLocation(TeleportWhereType.TOWN); + setIn7sDungeon(false); + sendMessage("You have been teleported to the nearest town due to the beginning of the Seal Validation period."); + } + } + else + { + if (!isGM() && isIn7sDungeon() && (SevenSigns.getInstance().getPlayerCabal(getObjectId()) == SevenSigns.CABAL_NULL)) + { + teleToLocation(TeleportWhereType.TOWN); + setIn7sDungeon(false); + sendMessage("You have been teleported to the nearest town because you have not signed for any cabal."); + } + } + + if (isGM() && !GeneralConfig.GM_STARTUP_BUILDER_HIDE) + { + // Bleah, see L2J custom below. + if (isInvul()) + { + sendMessage("Entering world in Invulnerable mode."); + } + + if (isInvisible()) + { + sendMessage("Entering world in Invisible mode."); + } + + if (_silenceMode) + { + sendMessage("Entering world in Silence mode."); + } + } + + // Buff and status icons + if (PlayerConfig.STORE_SKILL_COOLTIME) + { + restoreEffects(); + } + + revalidateZone(true); + + notifyFriends(); + if (!isGM() && PlayerConfig.DECREASE_SKILL_LEVEL) + { + checkPlayerSkills(); + } + + try + { + for (ZoneType zone : ZoneManager.getInstance().getZones(this)) + { + zone.onPlayerLoginInside(this); + } + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "", e); + } + + if (EventDispatcher.getInstance().hasListener(EventType.ON_PLAYER_LOGIN, this)) + { + EventDispatcher.getInstance().notifyEventAsync(new OnPlayerLogin(this), this); + } + + // TODO : Need to fix that hack! + if (!isDead()) + { + // Run on a separate thread to give time to above events to be notified. + ThreadPool.schedule(() -> + { + setCurrentHp(_originalHp); + setCurrentMp(_originalMp); + setCurrentCp(_originalCp); + }, 300); + } + } + + /** + * Fully restores the player's HP, MP, and CP to their maximum values. + */ + @Override + public void fullRestore() + { + setCurrentHp(getMaxHp()); + setCurrentMp(getMaxMp()); + setCurrentCp(getMaxCp()); + } + + public long getLastAccess() + { + return _lastAccess; + } + + @Override + public void doRevive() + { + super.doRevive(); + + // Stop decay task. + DecayTaskManager.getInstance().cancel(this); + + updateEffectIcons(); + sendPacket(new EtcStatusUpdate(this)); + _revivePet = false; + _reviveRequested = 0; + _revivePower = 0; + + // Teleport summon to player. + if (isInsideZone(ZoneId.PEACE) && (_summon != null) && !_summon.isInsideZone(ZoneId.SIEGE)) + { + _summon.teleToLocation(getLocation(), true); + } + + if (isMounted()) + { + startFeed(_mountNpcId); + } + + if (isInParty() && _party.isInDimensionalRift() && !DimensionalRiftManager.getInstance().checkIfInPeaceZone(getX(), getY(), getZ())) + { + _party.getDimensionalRift().memberRessurected(this); + } + + if (isInInstance()) + { + final Instance instance = InstanceManager.getInstance().getInstance(getInstanceId()); + if (instance != null) + { + instance.cancelEjectDeadPlayer(this); + } + } + } + + @Override + public void setName(String value) + { + super.setName(value); + CharInfoTable.getInstance().addName(this); + } + + @Override + public void doRevive(double revivePower) + { + doRevive(); + restoreExp(revivePower); + } + + public void reviveRequest(Player reviver, boolean pet, int power) + { + if (isResurrectionBlocked()) + { + return; + } + + if (_reviveRequested == 1) + { + if (_revivePet == pet) + { + reviver.sendPacket(SystemMessageId.RESURRECTION_HAS_ALREADY_BEEN_PROPOSED); // Resurrection is already been proposed. + } + else if (pet) + { + reviver.sendPacket(SystemMessageId.A_PET_CANNOT_BE_RESURRECTED_WHILE_IT_S_OWNER_IS_IN_THE_PROCESS_OF_RESURRECTING); // A pet cannot be resurrected while it's owner is in the process of resurrecting. + } + else + { + reviver.sendPacket(SystemMessageId.WHILE_A_PET_IS_ATTEMPTING_TO_RESURRECT_IT_CANNOT_HELP_IN_RESURRECTING_ITS_MASTER); // While a pet is attempting to resurrect, it cannot help in resurrecting its master. + } + return; + } + + if ((pet && hasPet() && _summon.isDead()) || (!pet && isDead())) + { + _reviveRequested = 1; + _revivePower = Formulas.calculateSkillResurrectRestorePercent(power, reviver); + _revivePet = pet; + + final ConfirmDlg dlg = new ConfirmDlg(SystemMessageId.S1_IS_MAKING_AN_ATTEMPT_AT_RESURRECTION_DO_YOU_WANT_TO_CONTINUE_WITH_THIS_RESURRECTION.getId()); + dlg.getSystemMessage().addPcName(reviver); + sendPacket(dlg); + } + } + + public void reviveAnswer(int answer) + { + if ((_reviveRequested != 1) || (!isDead() && !_revivePet) || (_revivePet && hasPet() && !_summon.isDead())) + { + return; + } + + if (answer == 1) + { + if (!_revivePet) + { + if (_revivePower != 0) + { + doRevive(_revivePower); + } + else + { + doRevive(); + } + } + else if (hasPet()) + { + if (_revivePower != 0) + { + _summon.doRevive(_revivePower); + } + else + { + _summon.doRevive(); + } + } + } + + _revivePet = false; + _reviveRequested = 0; + _revivePower = 0; + } + + public boolean isReviveRequested() + { + return _reviveRequested == 1; + } + + public boolean isRevivingPet() + { + return _revivePet; + } + + public void removeReviving() + { + _reviveRequested = 0; + _revivePower = 0; + } + + public void onActionRequest() + { + if (isSpawnProtected()) + { + setSpawnProtection(false); + if (!isInsideZone(ZoneId.PEACE)) + { + sendMessage("You are no longer protected from aggressive monsters."); + } + + if (PlayerConfig.RESTORE_SERVITOR_ON_RECONNECT && !hasSummon() && CharSummonTable.getInstance().getServitors().containsKey(getObjectId())) + { + CharSummonTable.getInstance().restoreServitor(this); + } + + if (PlayerConfig.RESTORE_PET_ON_RECONNECT && !hasSummon() && CharSummonTable.getInstance().getPets().containsKey(getObjectId())) + { + CharSummonTable.getInstance().restorePet(this); + } + } + + if (isTeleportProtected()) + { + setTeleportProtection(false); + if (!isInsideZone(ZoneId.PEACE)) + { + sendMessage("Teleport spawn protection ended."); + } + } + } + + /** + * Expertise of the Player (None=0, D=1, C=2, B=3, A=4, S=5) + * @return int Expertise skill level. + */ + public int getExpertiseLevel() + { + return getSkillLevel(239); + } + + @Override + public void teleToLocation(ILocational loc, boolean allowRandomOffset) + { + if ((_vehicle != null) && !_vehicle.isTeleporting()) + { + setVehicle(null); + } + + super.teleToLocation(loc, allowRandomOffset); + } + + @Override + public synchronized void onTeleported() + { + super.onTeleported(); + + setLastServerPosition(getX(), getY(), getZ()); + + // Force a revalidation + revalidateZone(true); + + checkItemRestriction(); + + if ((PlayerConfig.PLAYER_TELEPORT_PROTECTION > 0) && !_inOlympiadMode) + { + setTeleportProtection(true); + } + + // Trained beast is lost after teleport. + if (_tamedBeast != null) + { + for (TamedBeast tamedBeast : _tamedBeast) + { + tamedBeast.deleteMe(); + } + + _tamedBeast.clear(); + } + + // Modify the position of the pet if necessary. + if (_summon != null) + { + _summon.setFollowStatus(false); + _summon.teleToLocation(getLocation(), false); + ((SummonAI) _summon.getAI()).setStartFollowController(true); + _summon.setFollowStatus(true); + _summon.updateAndBroadcastStatus(0); + } + + // Stop auto play. + if (AutoPlayConfig.ENABLE_AUTO_PLAY) + { + AutoPlayTaskManager.getInstance().stopAutoPlay(this); + AutoUseTaskManager.getInstance().stopAutoUseTask(this); + } + + // Send info to nearby players. + broadcastInfo(); + } + + @Override + public void setTeleporting(boolean teleport) + { + setTeleporting(teleport, true); + } + + public void setTeleporting(boolean teleport, boolean useWatchDog) + { + super.setTeleporting(teleport); + if (!useWatchDog) + { + return; + } + + if (teleport) + { + if ((_teleportWatchdog == null) && (PlayerConfig.TELEPORT_WATCHDOG_TIMEOUT > 0)) + { + synchronized (this) + { + _teleportWatchdog = ThreadPool.schedule(new TeleportWatchdogTask(this), PlayerConfig.TELEPORT_WATCHDOG_TIMEOUT * 1000); + } + } + } + else if (_teleportWatchdog != null) + { + _teleportWatchdog.cancel(false); + _teleportWatchdog = null; + } + } + + public void setLastServerPosition(int x, int y, int z) + { + _lastServerPosition.setXYZ(x, y, z); + } + + public Location getLastServerPosition() + { + return _lastServerPosition; + } + + public void setBlinkActive(boolean value) + { + _blinkActive.set(value); + } + + public boolean isBlinkActive() + { + return _blinkActive.get(); + } + + public void addElementSeed(Element element) + { + _elementSeeds.get(element).incrementAndGet(); + ThreadPool.schedule(() -> removeElementSeed(element), 5000); + } + + private void removeElementSeed(Element element) + { + _elementSeeds.get(element).decrementAndGet(); + } + + public int getElementSeedCount(Element element) + { + return _elementSeeds.get(element).get(); + } + + @Override + public synchronized void addExpAndSp(double addToExp, double addToSp) + { + getStat().addExpAndSp(addToExp, addToSp, false); + } + + public synchronized void addExpAndSp(double addToExp, double addToSp, boolean useVitality) + { + getStat().addExpAndSp(addToExp, addToSp, useVitality); + } + + public void removeExpAndSp(long removeExp, long removeSp) + { + getStat().removeExpAndSp(removeExp, removeSp, true); + } + + public void removeExpAndSp(long removeExp, long removeSp, boolean sendMessage) + { + getStat().removeExpAndSp(removeExp, removeSp, sendMessage); + } + + @Override + public void reduceCurrentHp(double value, Creature attacker, boolean awake, boolean isDOT, Skill skill) + { + if (skill != null) + { + getStatus().reduceHp(value, attacker, awake, isDOT, skill.isToggle(), skill.getDmgDirectlyToHP()); + } + else + { + getStatus().reduceHp(value, attacker, awake, isDOT, false, false); + } + + // Notify the tamed beast of attacks. + if (_tamedBeast != null) + { + for (TamedBeast tamedBeast : _tamedBeast) + { + tamedBeast.onOwnerGotAttacked(attacker); + } + } + } + + public void broadcastSnoop(ChatType type, String name, String text, CreatureSay cs) + { + if (_snoopListener.isEmpty()) + { + return; + } + + final Snoop sn = new Snoop(getObjectId(), getName(), type, name, text); + for (Player pci : _snoopListener) + { + if (pci != null) + { + pci.sendPacket(cs); + pci.sendPacket(sn); + } + } + } + + public void addSnooper(Player pci) + { + _snoopListener.add(pci); + } + + public void removeSnooper(Player pci) + { + _snoopListener.remove(pci); + } + + public void addSnooped(Player pci) + { + _snoopedPlayer.add(pci); + } + + public void removeSnooped(Player pci) + { + _snoopedPlayer.remove(pci); + } + + public void addHtmlAction(HtmlActionScope scope, String action) + { + _htmlActionCaches[scope.ordinal()].add(action); + } + + public void clearHtmlActions(HtmlActionScope scope) + { + _htmlActionCaches[scope.ordinal()].clear(); + } + + public void setHtmlActionOriginObjectId(HtmlActionScope scope, int npcObjId) + { + if (npcObjId < 0) + { + throw new IllegalArgumentException(); + } + + _htmlActionOriginObjectIds[scope.ordinal()] = npcObjId; + } + + public int getLastHtmlActionOriginId() + { + return _lastHtmlActionOriginObjId; + } + + private boolean validateHtmlAction(Iterable actionIter, String action) + { + for (String cachedAction : actionIter) + { + if (cachedAction.charAt(cachedAction.length() - 1) == AbstractHtmlPacket.VAR_PARAM_START_CHAR) + { + if (action.startsWith(cachedAction.substring(0, cachedAction.length() - 1).trim())) + { + return true; + } + } + else if (cachedAction.equals(action)) + { + return true; + } + } + + return false; + } + + /** + * Check if the HTML action was sent in a HTML packet.
    + * If the HTML action was not sent for whatever reason, -1 is returned.
    + * Otherwise, the NPC object ID or 0 is returned.
    + * 0 means the HTML action was not bound to an NPC
    + * and no range checks need to be made. + * @param action the HTML action to check + * @return NPC object ID, 0 or -1 + */ + public int validateHtmlAction(String action) + { + for (int i = 0; i < _htmlActionCaches.length; ++i) + { + if (validateHtmlAction(_htmlActionCaches[i], action)) + { + _lastHtmlActionOriginObjId = _htmlActionOriginObjectIds[i]; + return _lastHtmlActionOriginObjId; + } + } + + return -1; + } + + /** + * Performs following tests: + *
      + *
    • Inventory contains item
    • + *
    • Item owner id == owner id
    • + *
    • It isn't pet control item while mounting pet or pet summoned
    • + *
    • It isn't active enchant item
    • + *
    • It isn't cursed weapon/item
    • + *
    • It isn't wear item
    • + *
    + * @param objectId item object id + * @param itemProcessType the item process type + * @return + */ + public boolean validateItemManipulation(int objectId, ItemProcessType itemProcessType) + { + final Item item = _inventory.getItemByObjectId(objectId); + if ((item == null) || (item.getOwnerId() != getObjectId())) + { + LOGGER.finest(getObjectId() + ": player tried to " + itemProcessType + " item he is not owner of."); + return false; + } + + // Pet is summoned and not the item that summoned the pet AND not the buggle from strider you're mounting. + if ((hasSummon() && (_summon.getControlObjectId() == objectId)) || (_mountObjectID == objectId)) + { + return false; + } + + if (_activeEnchantItemId == objectId) + { + return false; + } + + if (CursedWeaponsManager.getInstance().isCursed(item.getId())) + { + // Can not trade a cursed weapon. + return false; + } + + return true; + } + + /** + * @return Returns the inBoat. + */ + public boolean isInBoat() + { + return (_vehicle != null) && _vehicle.isBoat(); + } + + /** + * @return + */ + public Boat getBoat() + { + return (Boat) _vehicle; + } + + public Vehicle getVehicle() + { + return _vehicle; + } + + public void setVehicle(Vehicle v) + { + if ((v == null) && (_vehicle != null)) + { + _vehicle.removePassenger(this); + } + + _vehicle = v; + } + + public boolean isInVehicle() + { + return _vehicle != null; + } + + public void setInCrystallize(boolean inCrystallize) + { + _inCrystallize = inCrystallize; + } + + public boolean isInCrystallize() + { + return _inCrystallize; + } + + /** + * @return + */ + public Location getInVehiclePosition() + { + return _inVehiclePosition; + } + + public void setInVehiclePosition(Location pt) + { + _inVehiclePosition = pt; + } + + /** + * Manage the delete task of a Player (Leave Party, Unsummon pet, Save its inventory in the database, Remove it from the world...).
    + *
    + * Actions: + *
      + *
    • If the Player is in observer mode, set its position to its position before entering in observer mode
    • + *
    • Set the online Flag to True or False and update the characters table of the database with online status and lastAccess
    • + *
    • Stop the HP/MP/CP Regeneration task
    • + *
    • Cancel Crafting, Attack or Cast
    • + *
    • Remove the Player from the world
    • + *
    • Stop Party and Unsummon Pet
    • + *
    • Update database with items in its inventory and remove them from the world
    • + *
    • Remove all WorldObject from _knownObjects and _knownPlayer of the Creature then cancel Attak or Cast and notify AI
    • + *
    • Close the connection with the client
    • + *
    + *
    + * Remember this method is not to be used to half-ass disconnect players! This method is dedicated only to erase the player from the world.
    + * If you intend to disconnect a player please use {@link Disconnection} + */ + @Override + public boolean deleteMe() + { + if (EventDispatcher.getInstance().hasListener(EventType.ON_PLAYER_LOGOUT, this)) + { + EventDispatcher.getInstance().notifyEventAsync(new OnPlayerLogout(this), this); + } + + try + { + for (ZoneType zone : ZoneManager.getInstance().getZones(this)) + { + zone.onPlayerLogoutInside(this); + } + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "deleteMe()", e); + } + + // Set the online Flag to True or False and update the characters table of the database with online status and lastAccess (called when login and logout). + try + { + if (!_isOnline) + { + LOGGER.log(Level.SEVERE, "deleteMe() called on offline character " + this, new RuntimeException()); + } + + setOnlineStatus(false, true); + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "deleteMe()", e); + } + + try + { + _isOnline = false; + _offlinePlay = false; + abortAttack(); + abortCast(); + stopMove(null); + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "deleteMe()", e); + } + + try + { + PartyMatchWaitingList.getInstance().removePlayer(this); + if (_partyroom != 0) + { + final PartyMatchRoom room = PartyMatchRoomList.getInstance().getRoom(_partyroom); + if (room != null) + { + room.deleteMember(this); + } + } + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "deleteMe()", e); + } + + try + { + if (isFlying()) + { + removeSkill(CommonSkill.WYVERN_BREATH.getSkill()); + } + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "deleteMe()", e); + } + + // Make sure player variables are stored. + getVariables().saveNow(); + + // Make sure account variables are stored. + getAccountVariables().saveNow(); + + // Stop the HP/MP/CP Regeneration task (scheduled tasks). + try + { + stopAllTimers(); + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "deleteMe()", e); + } + + try + { + setTeleporting(false); + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "deleteMe()", e); + } + + // Stop crafting, if in progress. + try + { + RecipeManager.getInstance().requestMakeItemAbort(this); + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "deleteMe()", e); + } + + // Cancel Attak or Cast. + try + { + setTarget(null); + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "deleteMe()", e); + } + + if (isChannelized()) + { + getSkillChannelized().abortChannelization(); + } + + // Stop all toggles. + getEffectList().stopAllToggles(); + + // Remove from world regions zones. + final ZoneRegion region = ZoneManager.getInstance().getRegion(this); + if (region != null) + { + region.removeFromZones(this); + } + + // If a Party is in progress, leave it (and festival party). + if (isInParty()) + { + try + { + leaveParty(); + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "deleteMe()", e); + } + } + + stopCubics(); + + // Remove the Player from the world. + try + { + decayMe(); + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "deleteMe()", e); + } + + if (Olympiad.getInstance().isRegistered(this) || (getOlympiadGameId() != -1)) + { + Olympiad.getInstance().removeDisconnectedCompetitor(this); + } + + // If the Player has Pet, unsummon it. + if (hasSummon()) + { + try + { + _summon.setRestoreSummon(true); + + _summon.unSummon(this); + + // Dead pet wasn't unsummoned, broadcast npcinfo changes (pet will be without owner name - means owner offline). + if (hasSummon()) + { + _summon.broadcastNpcInfo(0); + } + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "deleteMe()", e); + } // Returns pet to control item. + } + + if (_clan != null) + { + // Set the status for pledge member list to OFFLINE. + try + { + final ClanMember clanMember = _clan.getClanMember(getObjectId()); + if (clanMember != null) + { + clanMember.setPlayer(null); + } + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "deleteMe()", e); + } + } + + if (getActiveRequester() != null) + { + // Deals with sudden exit in the middle of transaction. + setActiveRequester(null); + cancelActiveTrade(); + } + + // If the Player is a GM, remove it from the GM List. + if (isGM()) + { + try + { + AdminData.getInstance().deleteGm(this); + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "deleteMe()", e); + } + } + + try + { + // Check if the Player is in observer mode to set its position to its position. + // before entering in observer mode. + if (_observerMode) + { + setLocationInvisible(_lastLoc); + } + + if (_vehicle != null) + { + _vehicle.oustPlayer(this); + } + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "deleteMe()", e); + } + + // Remove player from instance and set spawn location if any. + try + { + final int instanceId = getInstanceId(); + if ((instanceId != 0) && !GeneralConfig.RESTORE_PLAYER_INSTANCE) + { + final Instance inst = InstanceManager.getInstance().getInstance(instanceId); + if (inst != null) + { + inst.removePlayer(getObjectId()); + final Location loc = inst.getExitLoc(); + if (loc != null) + { + final int x = loc.getX() + Rnd.get(-30, 30); + final int y = loc.getY() + Rnd.get(-30, 30); + setXYZInvisible(x, y, loc.getZ()); + if (hasSummon()) // dead pet + { + _summon.teleToLocation(loc, true); + _summon.setInstanceId(0); + } + } + } + } + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "deleteMe()", e); + } + + // Update database with items in its inventory and remove them from the world. + try + { + _inventory.deleteMe(); + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "deleteMe()", e); + } + + // Update database with items in its warehouse and remove them from the world. + try + { + getWarehouse().deleteMe(); + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "deleteMe()", e); + } + + try + { + _freight.deleteMe(); + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "deleteMe()", e); + } + + try + { + clearRefund(); + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "deleteMe()", e); + } + + if (isCursedWeaponEquipped()) + { + try + { + CursedWeaponsManager.getInstance().getCursedWeapon(_cursedWeaponEquippedId).setPlayer(null); + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "deleteMe()", e); + } + } + + if (_clanId > 0) + { + _clan.broadcastToOtherOnlineMembers(new PledgeShowMemberListUpdate(this), this); + // ClanTable.getInstance().getClan(getClanId()).broadcastToOnlineMembers(new PledgeShowMemberListAdd(this)); + } + + for (Player player : _snoopedPlayer) + { + player.removeSnooper(this); + } + + for (Player player : _snoopListener) + { + player.removeSnooped(this); + } + + try + { + notifyFriends(); + _blockList.playerLogout(); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Exception on deleteMe() notifyFriends: " + e.getMessage(), e); + } + + PlayerAutoSaveTaskManager.getInstance().remove(this); + + return super.deleteMe(); + } + + // startFishing() was stripped of any pre-fishing related checks, namely the fishing zone check. + // Also worthy of note is the fact the code to find the hook landing position was also striped. + // The stripped code was moved into fishing.java. + // In my opinion it makes more sense for it to be there since all other skill related checks were also there. + // Last but not least, moving the zone check there, fixed a bug where baits would always be consumed no matter if fishing actualy took place. + // startFishing() now takes up 3 arguments, wich are acurately described as being the hook landing coordinates. + public void startFishing(int x, int y, int z) + { + stopMove(null); + setImmobilized(true); + _fishing = true; + _fishX = x; + _fishY = y; + _fishZ = z; + + // broadcastUserInfo(); + // Starts fishing + final int lvl = getRandomFishLvl(); + final int grade = getRandomFishGrade(); + final int group = getRandomFishGroup(grade); + final List fish = FishData.getInstance().getFish(lvl, group, grade); + if ((fish == null) || fish.isEmpty()) + { + sendMessage("Error - Fish are not defined"); + endFishing(false); + return; + } + + // Use a copy constructor else the fish data may be over-written below + _fish = fish.get(Rnd.get(fish.size())).clone(); + fish.clear(); + sendPacket(SystemMessageId.YOU_CAST_YOUR_LINE_AND_START_TO_FISH); + if (!GameTimeTaskManager.getInstance().isNight() && _lure.isNightLure()) + { + _fish.setFishGroup(-1); + } + + // sendMessage("Hook x,y: " + _x + "," + _y + " - Water Z, Player Z:" + _z + ", " + getZ()); //debug line, uncoment to show coordinates used in fishing. + broadcastPacket(new ExFishingStart(this, _fish.getFishGroup(), x, y, z, _lure.isNightLure())); + sendPacket(new PlaySound(1, "SF_P_01", 0, 0, 0, 0, 0)); + startLookingForFishTask(); + } + + public void stopLookingForFishTask() + { + if (_taskForFish == null) + { + return; + } + + _taskForFish.cancel(false); + _taskForFish = null; + } + + public void startLookingForFishTask() + { + if (!isDead() && (_taskForFish == null)) + { + int checkDelay = 0; + boolean isNoob = false; + boolean isUpperGrade = false; + if (_lure != null) + { + final int lureid = _lure.getId(); + isNoob = _fish.getFishGrade() == 0; + isUpperGrade = _fish.getFishGrade() == 2; + if ((lureid == 6519) || (lureid == 6522) || (lureid == 6525) || (lureid == 8505) || (lureid == 8508) || (lureid == 8511)) + { + checkDelay = _fish.getGutsCheckTime() * 133; + } + else if ((lureid == 6520) || (lureid == 6523) || (lureid == 6526) || ((lureid >= 8505) && (lureid <= 8513)) || ((lureid >= 7610) && (lureid <= 7613)) || ((lureid >= 7807) && (lureid <= 7809)) || ((lureid >= 8484) && (lureid <= 8486))) + { + checkDelay = _fish.getGutsCheckTime() * 100; + } + else if ((lureid == 6521) || (lureid == 6524) || (lureid == 6527) || (lureid == 8507) || (lureid == 8510) || (lureid == 8513)) + { + checkDelay = _fish.getGutsCheckTime() * 66; + } + } + + _taskForFish = ThreadPool.scheduleAtFixedRate(new LookingForFishTask(this, _fish.getStartCombatTime(), _fish.getFishGuts(), _fish.getFishGroup(), isNoob, isUpperGrade), 10000, checkDelay); + } + } + + private int getRandomFishGrade() + { + switch (_lure.getId()) + { + case 7807: // green for beginners + case 7808: // purple for beginners + case 7809: // yellow for beginners + case 8486: // prize-winning for beginners + { + return 0; + } + case 8485: // prize-winning luminous + case 8506: // green luminous + case 8509: // purple luminous + case 8512: // yellow luminous + { + return 2; + } + default: + { + return 1; + } + } + } + + private int getRandomFishGroup(int group) + { + final int check = Rnd.get(100); + int type = 1; + switch (group) + { + case 0: // fish for novices + { + switch (_lure.getId()) + { + case 7807: // green lure, preferred by fast-moving (nimble) fish (type 5) + { + if (check <= 54) + { + type = 5; + } + else if (check <= 77) + { + type = 4; + } + else + { + type = 6; + } + break; + } + case 7808: // purple lure, preferred by fat fish (type 4) + { + if (check <= 54) + { + type = 4; + } + else if (check <= 77) + { + type = 6; + } + else + { + type = 5; + } + break; + } + case 7809: // yellow lure, preferred by ugly fish (type 6) + { + if (check <= 54) + { + type = 6; + } + else if (check <= 77) + { + type = 5; + } + else + { + type = 4; + } + break; + } + case 8486: // prize-winning fishing lure for beginners + { + if (check <= 33) + { + type = 4; + } + else if (check <= 66) + { + type = 5; + } + else + { + type = 6; + } + break; + } + } + break; + } + case 1: // normal fish + { + switch (_lure.getId()) + { + case 7610: + case 7611: + case 7612: + case 7613: + { + type = 3; + break; + } + case 6519: // all theese lures (green) are prefered by fast-moving (nimble) fish (type 1) + case 8505: + case 6520: + case 6521: + case 8507: + { + if (check <= 54) + { + type = 1; + } + else if (check <= 74) + { + type = 0; + } + else if (check <= 94) + { + type = 2; + } + else + { + type = 3; + } + break; + } + case 6522: // all theese lures (purple) are prefered by fat fish (type 0) + case 8508: + case 6523: + case 6524: + case 8510: + { + if (check <= 54) + { + type = 0; + } + else if (check <= 74) + { + type = 1; + } + else if (check <= 94) + { + type = 2; + } + else + { + type = 3; + } + break; + } + case 6525: // all theese lures (yellow) are prefered by ugly fish (type 2) + case 8511: + case 6526: + case 6527: + case 8513: + { + if (check <= 55) + { + type = 2; + } + else if (check <= 74) + { + type = 1; + } + else if (check <= 94) + { + type = 0; + } + else + { + type = 3; + } + break; + } + case 8484: // prize-winning fishing lure + { + if (check <= 33) + { + type = 0; + } + else if (check <= 66) + { + type = 1; + } + else + { + type = 2; + } + break; + } + } + break; + } + case 2: // upper grade fish, luminous lure + { + switch (_lure.getId()) + { + case 8506: // green lure, preferred by fast-moving (nimble) fish (type 8) + { + if (check <= 54) + { + type = 8; + } + else if (check <= 77) + { + type = 7; + } + else + { + type = 9; + } + break; + } + case 8509: // purple lure, preferred by fat fish (type 7) + { + if (check <= 54) + { + type = 7; + } + else if (check <= 77) + { + type = 9; + } + else + { + type = 8; + } + break; + } + case 8512: // yellow lure, preferred by ugly fish (type 9) + { + if (check <= 54) + { + type = 9; + } + else if (check <= 77) + { + type = 8; + } + else + { + type = 7; + } + break; + } + case 8485: // prize-winning fishing lure + { + if (check <= 33) + { + type = 7; + } + else if (check <= 66) + { + type = 8; + } + else + { + type = 9; + } + break; + } + } + } + } + + return type; + } + + private int getRandomFishLvl() + { + int skillLevel = getSkillLevel(1315 /* Fishing Expertise */); + final BuffInfo info = getEffectList().getBuffInfoBySkillId(2274 /* Fisherman's Potion */); + if (info != null) + { + skillLevel = (int) info.getSkill().getPower(); + } + + if (skillLevel <= 0) + { + return 1; + } + + int randomlvl; + final int check = Rnd.get(100); + if (check <= 50) + { + randomlvl = skillLevel; + } + else if (check <= 85) + { + randomlvl = skillLevel - 1; + if (randomlvl <= 0) + { + randomlvl = 1; + } + } + else + { + randomlvl = skillLevel + 1; + if (randomlvl > 27) + { + randomlvl = 27; + } + } + + return randomlvl; + } + + public void startFishCombat(boolean isNoob, boolean isUpperGrade) + { + _fishCombat = new Fishing(this, _fish, isNoob, isUpperGrade, _lure.getId()); + } + + public void endFishing(boolean win) + { + _fishing = false; + _fishX = 0; + _fishY = 0; + _fishZ = 0; + + // broadcastUserInfo(); + if (_fishCombat == null) + { + sendPacket(SystemMessageId.BAITS_HAVE_BEEN_LOST_BECAUSE_THE_FISH_GOT_AWAY); + } + + _fishCombat = null; + _lure = null; + + // Ends fishing + broadcastPacket(new ExFishingEnd(win, this)); + sendPacket(SystemMessageId.YOU_REEL_YOUR_LINE_IN_AND_STOP_FISHING); + setImmobilized(false); + stopLookingForFishTask(); + } + + public Fishing getFishCombat() + { + return _fishCombat; + } + + public int getFishX() + { + return _fishX; + } + + public int getFishY() + { + return _fishY; + } + + public int getFishZ() + { + return _fishZ; + } + + public void setLure(Item lure) + { + _lure = lure; + } + + public Item getLure() + { + return _lure; + } + + public int getInventoryLimit() + { + int ivlim; + if (isGM()) + { + ivlim = PlayerConfig.INVENTORY_MAXIMUM_GM; + } + else + { + ivlim = getRace() == Race.DWARF ? PlayerConfig.INVENTORY_MAXIMUM_DWARF : PlayerConfig.INVENTORY_MAXIMUM_NO_DWARF; + } + + return ivlim += (int) getStat().calcStat(Stat.INV_LIM, 0, null, null); + } + + public int getWareHouseLimit() + { + return (getRace() == Race.DWARF ? PlayerConfig.WAREHOUSE_SLOTS_DWARF : PlayerConfig.WAREHOUSE_SLOTS_NO_DWARF) + (int) getStat().calcStat(Stat.WH_LIM, 0, null, null); + } + + public int getPrivateSellStoreLimit() + { + return (getRace() == Race.DWARF ? PlayerConfig.MAX_PVTSTORESELL_SLOTS_DWARF : PlayerConfig.MAX_PVTSTORESELL_SLOTS_OTHER) + (int) getStat().calcStat(Stat.P_SELL_LIM, 0, null, null); + } + + public int getPrivateBuyStoreLimit() + { + return (getRace() == Race.DWARF ? PlayerConfig.MAX_PVTSTOREBUY_SLOTS_DWARF : PlayerConfig.MAX_PVTSTOREBUY_SLOTS_OTHER) + (int) getStat().calcStat(Stat.P_BUY_LIM, 0, null, null); + } + + public int getDwarfRecipeLimit() + { + return PlayerConfig.DWARF_RECIPE_LIMIT + (int) getStat().calcStat(Stat.REC_D_LIM, 0, null, null); + } + + public int getCommonRecipeLimit() + { + return PlayerConfig.COMMON_RECIPE_LIMIT + (int) getStat().calcStat(Stat.REC_C_LIM, 0, null, null); + } + + /** + * @return Returns the mountNpcId. + */ + public int getMountNpcId() + { + return _mountNpcId; + } + + /** + * @return Returns the mountLevel. + */ + public int getMountLevel() + { + return _mountLevel; + } + + public void setMountObjectID(int newID) + { + _mountObjectID = newID; + } + + public int getMountObjectID() + { + return _mountObjectID; + } + + /** + * @return the current skill in use or return null. + */ + public SkillUseHolder getCurrentSkill() + { + return _currentSkill; + } + + /** + * Create a new SkillUseHolder object and set the player _currentSkill. + * @param currentSkill + * @param ctrlPressed + * @param shiftPressed + */ + public void setCurrentSkill(Skill currentSkill, boolean ctrlPressed, boolean shiftPressed) + { + if (currentSkill == null) + { + _currentSkill = null; + return; + } + + _currentSkill = new SkillUseHolder(currentSkill, ctrlPressed, shiftPressed); + } + + /** + * @return the current pet skill in use or return null. + */ + public SkillUseHolder getCurrentPetSkill() + { + return _currentPetSkill; + } + + /** + * Create a new SkillUseHolder object and set the player _currentPetSkill. + * @param currentSkill + * @param ctrlPressed + * @param shiftPressed + */ + public void setCurrentPetSkill(Skill currentSkill, boolean ctrlPressed, boolean shiftPressed) + { + if (currentSkill == null) + { + _currentPetSkill = null; + return; + } + + _currentPetSkill = new SkillUseHolder(currentSkill, ctrlPressed, shiftPressed); + } + + public SkillUseHolder getQueuedSkill() + { + return _queuedSkill; + } + + /** + * Create a new SkillUseHolder object and queue it in the player _queuedSkill. + * @param queuedSkill + * @param ctrlPressed + * @param shiftPressed + */ + public void setQueuedSkill(Skill queuedSkill, boolean ctrlPressed, boolean shiftPressed) + { + if (queuedSkill == null) + { + _queuedSkill = null; + return; + } + + _queuedSkill = new SkillUseHolder(queuedSkill, ctrlPressed, shiftPressed); + } + + /** + * @return {@code true} if player is jailed, {@code false} otherwise. + */ + public boolean isJailed() + { + return PunishmentManager.getInstance().hasPunishment(getObjectId(), PunishmentAffect.CHARACTER, PunishmentType.JAIL) // + || PunishmentManager.getInstance().hasPunishment(getAccountName(), PunishmentAffect.ACCOUNT, PunishmentType.JAIL) // + || PunishmentManager.getInstance().hasPunishment(getIPAddress(), PunishmentAffect.IP, PunishmentType.JAIL) // + || ((_client != null) && (_client.getHardwareInfo() != null) && PunishmentManager.getInstance().hasPunishment(_client.getHardwareInfo().getMacAddress(), PunishmentAffect.HWID, PunishmentType.JAIL)); + } + + /** + * @return {@code true} if player is chat banned, {@code false} otherwise. + */ + public boolean isChatBanned() + { + return PunishmentManager.getInstance().hasPunishment(getObjectId(), PunishmentAffect.CHARACTER, PunishmentType.CHAT_BAN) // + || PunishmentManager.getInstance().hasPunishment(getAccountName(), PunishmentAffect.ACCOUNT, PunishmentType.CHAT_BAN) // + || PunishmentManager.getInstance().hasPunishment(getIPAddress(), PunishmentAffect.IP, PunishmentType.CHAT_BAN) // + || ((_client != null) && (_client.getHardwareInfo() != null) && PunishmentManager.getInstance().hasPunishment(_client.getHardwareInfo().getMacAddress(), PunishmentAffect.HWID, PunishmentType.CHAT_BAN)); + } + + public void startFameTask(long delay, int fameFixRate) + { + if (!PlayerConfig.FAME_SYSTEM_ENABLED) + { + return; + } + + if ((getLevel() < 40) || (getPlayerClass().level() < 2)) + { + return; + } + + if (_fameTask == null) + { + _fameTask = ThreadPool.scheduleAtFixedRate(new FameTask(this, fameFixRate), delay, delay); + } + } + + public void stopFameTask() + { + if (_fameTask == null) + { + return; + } + + _fameTask.cancel(false); + _fameTask = null; + } + + public void startVitalityTask() + { + if (PlayerConfig.ENABLE_VITALITY && (_vitalityTask == null)) + { + _vitalityTask = ThreadPool.scheduleAtFixedRate(new VitalityTask(this), 1000, 60000); + } + } + + public void stopVitalityTask() + { + if (_vitalityTask != null) + { + _vitalityTask.cancel(false); + _vitalityTask = null; + } + } + + public int getPowerGrade() + { + return _powerGrade; + } + + public void setPowerGrade(int power) + { + _powerGrade = power; + } + + public boolean isCursedWeaponEquipped() + { + return _cursedWeaponEquippedId != 0; + } + + public void setCursedWeaponEquippedId(int value) + { + _cursedWeaponEquippedId = value; + } + + public int getCursedWeaponEquippedId() + { + return _cursedWeaponEquippedId; + } + + public int getDeathPenaltyBuffLevel() + { + return _deathPenaltyBuffLevel; + } + + public void setDeathPenaltyBuffLevel(int level) + { + _deathPenaltyBuffLevel = level; + } + + public void calculateDeathPenaltyBuffLevel(Creature killer) + { + if (killer == null) + { + LOGGER.warning(this + " called calculateDeathPenaltyBuffLevel with killer null!"); + return; + } + + if (isResurrectSpecialAffected() || isLucky() || isOnEvent() || isInsideZone(ZoneId.PVP) || isInsideZone(ZoneId.SIEGE) || isGM()) + { + return; + } + + double percent = 1.0; + if (killer.isRaid()) + { + percent *= calcStat(Stat.REDUCE_DEATH_PENALTY_BY_RAID, 1); + } + else if (killer.isMonster()) + { + percent *= calcStat(Stat.REDUCE_DEATH_PENALTY_BY_MOB, 1); + } + else if (killer.isPlayable()) + { + percent *= calcStat(Stat.REDUCE_DEATH_PENALTY_BY_PVP, 1); + } + + if ((Rnd.get(1, 100) <= ((PlayerConfig.DEATH_PENALTY_CHANCE) * percent)) && (!killer.isPlayable() || (getKarma() > 0))) + { + increaseDeathPenaltyBuffLevel(); + } + } + + public void increaseDeathPenaltyBuffLevel() + { + if (_deathPenaltyBuffLevel >= 15) + { + return; + } + + if (_deathPenaltyBuffLevel != 0) + { + final Skill skill = SkillData.getInstance().getSkill(5076, getDeathPenaltyBuffLevel()); + if (skill != null) + { + removeSkill(skill, true); + } + } + + _deathPenaltyBuffLevel++; + addSkill(SkillData.getInstance().getSkill(5076, getDeathPenaltyBuffLevel()), false); + sendPacket(new EtcStatusUpdate(this)); + final SystemMessage sm = new SystemMessage(SystemMessageId.YOUR_DEATH_PENALTY_IS_NOW_LEVEL_S1); + sm.addInt(_deathPenaltyBuffLevel); + sendPacket(sm); + } + + public void reduceDeathPenaltyBuffLevel() + { + if (_deathPenaltyBuffLevel <= 0) + { + return; + } + + final Skill skill = SkillData.getInstance().getSkill(5076, getDeathPenaltyBuffLevel()); + if (skill != null) + { + removeSkill(skill, true); + } + + _deathPenaltyBuffLevel--; + + if (_deathPenaltyBuffLevel > 0) + { + addSkill(SkillData.getInstance().getSkill(5076, getDeathPenaltyBuffLevel()), false); + sendPacket(new EtcStatusUpdate(this)); + final SystemMessage sm = new SystemMessage(SystemMessageId.YOUR_DEATH_PENALTY_IS_NOW_LEVEL_S1); + sm.addInt(_deathPenaltyBuffLevel); + sendPacket(sm); + } + else + { + sendPacket(new EtcStatusUpdate(this)); + sendPacket(SystemMessageId.YOUR_DEATH_PENALTY_HAS_BEEN_LIFTED); + } + } + + public void restoreDeathPenaltyBuffLevel() + { + if (_deathPenaltyBuffLevel > 0) + { + addSkill(SkillData.getInstance().getSkill(5076, getDeathPenaltyBuffLevel()), false); + } + } + + @Override + public void sendDamageMessage(Creature target, int damage, boolean mcrit, boolean pcrit, boolean miss) + { + // Check if hit is missed. + if (miss) + { + sendPacket(SystemMessageId.YOU_HAVE_MISSED); + return; + } + + // Check if hit is critical. + if (pcrit) + { + sendPacket(SystemMessageId.CRITICAL_HIT); + } + + if (mcrit) + { + sendPacket(SystemMessageId.MAGIC_CRITICAL_HIT); + } + + if (isInOlympiadMode() && target.isPlayer() && target.asPlayer().isInOlympiadMode() && (target.asPlayer().getOlympiadGameId() == getOlympiadGameId())) + { + Olympiad.getInstance().notifyCompetitorDamage(this, damage, getOlympiadGameId()); + } + + if (this != target) + { + final SystemMessage sm = new SystemMessage(SystemMessageId.YOU_HIT_FOR_S1_DAMAGE); + sm.addInt(damage); + sendPacket(sm); + } + } + + /** + * @param npcId + */ + public void setAgathionId(int npcId) + { + _agathionId = npcId; + } + + /** + * @return + */ + public int getAgathionId() + { + return _agathionId; + } + + public int getVitalityPoints() + { + return getStat().getVitalityPoints(); + } + + /** + * @return Vitality Level + */ + public int getVitalityLevel() + { + return getStat().getVitalityLevel(); + } + + public void setVitalityPoints(int points, boolean quiet) + { + getStat().setVitalityPoints(points, quiet); + } + + public void updateVitalityPoints(float points, boolean useRates, boolean quiet) + { + getStat().updateVitalityPoints(points, useRates, quiet); + } + + public void checkItemRestriction() + { + for (int i = 0; i < Inventory.PAPERDOLL_TOTALSLOTS; i++) + { + final Item equippedItem = _inventory.getPaperdollItem(i); + if ((equippedItem != null) && !equippedItem.getTemplate().checkCondition(this, this, false)) + { + _inventory.unEquipItemInSlot(i); + + final InventoryUpdate iu = new InventoryUpdate(); + iu.addModifiedItem(equippedItem); + sendInventoryUpdate(iu); + + SystemMessage sm = null; + if (equippedItem.getTemplate().getBodyPart() == BodyPart.BACK) + { + sendMessage("Your cloak has been unequipped because your armor set is no longer complete."); + return; + } + + if (equippedItem.isEnchanted()) + { + sm = new SystemMessage(SystemMessageId.THE_EQUIPMENT_S1_S2_HAS_BEEN_REMOVED); + sm.addInt(equippedItem.getEnchantLevel()); + } + else + { + sm = new SystemMessage(SystemMessageId.S1_HAS_BEEN_DISARMED); + } + + sm.addItemName(equippedItem); + sendPacket(sm); + } + } + } + + protected void startFeed(int npcId) + { + _canFeed = npcId > 0; + if (!isMounted()) + { + return; + } + + if (hasSummon()) + { + setCurrentFeed(_summon.asPet().getCurrentFed()); + _controlItemId = _summon.getControlObjectId(); + sendPacket(new SetupGauge(getObjectId(), 3, (_curFeed * 10000) / getFeedConsume(), (getMaxFeed() * 10000) / getFeedConsume())); + if (!isDead()) + { + _mountFeedTask = ThreadPool.scheduleAtFixedRate(new PetFeedTask(this), 10000, 10000); + } + } + else if (_canFeed) + { + setCurrentFeed(getMaxFeed()); + sendPacket(new SetupGauge(getObjectId(), 3, (_curFeed * 10000) / getFeedConsume(), (getMaxFeed() * 10000) / getFeedConsume())); + if (!isDead()) + { + _mountFeedTask = ThreadPool.scheduleAtFixedRate(new PetFeedTask(this), 10000, 10000); + } + } + } + + public void stopFeed() + { + if (_mountFeedTask == null) + { + return; + } + + _mountFeedTask.cancel(false); + _mountFeedTask = null; + } + + private PetLevelData getPetLevelData(int npcId) + { + if (_leveldata == null) + { + _leveldata = PetDataTable.getInstance().getPetData(npcId).getPetLevelData(getMountLevel()); + } + + return _leveldata; + } + + public int getCurrentFeed() + { + return _curFeed; + } + + public int getFeedConsume() + { + // If pet is attacking. + if (isAttackingNow()) + { + return getPetLevelData(_mountNpcId).getPetFeedBattle(); + } + + return getPetLevelData(_mountNpcId).getPetFeedNormal(); + } + + public void setCurrentFeed(int num) + { + final boolean lastHungryState = isHungry(); + _curFeed = num > getMaxFeed() ? getMaxFeed() : num; + sendPacket(new SetupGauge(getObjectId(), 3, (_curFeed * 10000) / getFeedConsume(), (getMaxFeed() * 10000) / getFeedConsume())); + + // Broadcast move speed change when strider becomes hungry / full. + if (lastHungryState != isHungry()) + { + broadcastUserInfo(); + } + } + + private int getMaxFeed() + { + return getPetLevelData(_mountNpcId).getPetMaxFeed(); + } + + public boolean isHungry() + { + return _canFeed && (getCurrentFeed() < ((PetDataTable.getInstance().getPetData(getMountNpcId()).getHungryLimit() / 100f) * getPetLevelData(getMountNpcId()).getPetMaxFeed())); + } + + public void enteredNoLanding(int delay) + { + _dismountTask = ThreadPool.schedule(new DismountTask(this), delay * 1000); + } + + public void exitedNoLanding() + { + if (_dismountTask == null) + { + return; + } + + _dismountTask.cancel(true); + _dismountTask = null; + } + + public void storePetFood(int petId) + { + if ((_controlItemId == 0) || (petId == 0)) + { + return; + } + + final String req = "UPDATE pets SET fed=? WHERE item_obj_id = ?"; + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement(req)) + { + ps.setInt(1, _curFeed); + ps.setInt(2, _controlItemId); + ps.executeUpdate(); + _controlItemId = 0; + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "Failed to store Pet [NpcId: " + petId + "] data", e); + } + } + + public void setInSiege(boolean value) + { + _isInSiege = value; + } + + public boolean isInSiege() + { + return _isInSiege; + } + + /** + * @param isInHideoutSiege sets the value of {@link #_isInHideoutSiege}. + */ + public void setInHideoutSiege(boolean isInHideoutSiege) + { + _isInHideoutSiege = isInHideoutSiege; + } + + /** + * @return the value of {@link #_isInHideoutSiege}, {@code true} if the player is participing on a Hideout Siege, otherwise {@code false}. + */ + public boolean isInHideoutSiege() + { + return _isInHideoutSiege; + } + + /** + * Returns the Number of Charges this Player got. + * @return + */ + public int getCharges() + { + return _charges.get(); + } + + public void increaseCharges(int count, int max) + { + if (_charges.get() >= max) + { + sendPacket(SystemMessageId.YOUR_FORCE_HAS_REACHED_MAXIMUM_CAPACITY); + return; + } + + // Charge clear task should be reset every time a charge is increased. + restartChargeTask(); + + if (_charges.addAndGet(count) >= max) + { + _charges.set(max); + sendPacket(SystemMessageId.YOUR_FORCE_HAS_REACHED_MAXIMUM_CAPACITY); + } + else + { + final SystemMessage sm = new SystemMessage(SystemMessageId.YOUR_FORCE_HAS_INCREASED_TO_S1_LEVEL); + sm.addInt(_charges.get()); + sendPacket(sm); + } + + sendPacket(new EtcStatusUpdate(this)); + } + + public boolean decreaseCharges(int count) + { + if (_charges.get() < count) + { + return false; + } + + // Charge clear task should be reset every time a charge is decreased and stopped when charges become 0. + if (_charges.addAndGet(-count) == 0) + { + stopChargeTask(); + } + else + { + restartChargeTask(); + } + + sendPacket(new EtcStatusUpdate(this)); + return true; + } + + public void clearCharges() + { + _charges.set(0); + sendPacket(new EtcStatusUpdate(this)); + } + + /** + * Starts/Restarts the ChargeTask to Clear Charges after 10 Mins. + */ + private void restartChargeTask() + { + if (_chargeTask != null) + { + synchronized (this) + { + _chargeTask.cancel(false); + } + } + + _chargeTask = ThreadPool.schedule(new ResetChargesTask(this), 600000); + } + + /** + * Stops the Charges Clearing Task. + */ + public void stopChargeTask() + { + if (_chargeTask == null) + { + return; + } + + _chargeTask.cancel(false); + _chargeTask = null; + } + + @Override + public void sendInfo(Player player) + { + if (isInBoat()) + { + setXYZ(getBoat().getLocation()); + player.sendPacket(new CharInfo(this, isInvisible() && player.isGM())); + // player.sendPacket(new ExBrExtraUserInfo(this)); + player.sendPacket(new GetOnVehicle(getObjectId(), getBoat().getObjectId(), _inVehiclePosition)); + } + else + { + player.sendPacket(new CharInfo(this, isInvisible() && player.isGM())); + // player.sendPacket(new ExBrExtraUserInfo(this)); + } + + final int relation = getRelation(player); + final boolean isAutoAttackable = isAutoAttackable(player); + final RelationCache cache = getKnownRelations().get(player.getObjectId()); + if ((cache == null) || (cache.getRelation() != relation) || (cache.isAutoAttackable() != isAutoAttackable)) + { + player.sendPacket(new RelationChanged(this, relation, isAutoAttackable)); + if (hasSummon()) + { + player.sendPacket(new RelationChanged(_summon, relation, isAutoAttackable)); + } + + getKnownRelations().put(player.getObjectId(), new RelationCache(relation, isAutoAttackable)); + } + + switch (_privateStoreType) + { + case SELL: + { + player.sendPacket(new PrivateStoreMsgSell(this)); + break; + } + case PACKAGE_SELL: + { + // player.sendPacket(new ExPrivateStoreSetWholeMsg(this)); + player.sendPacket(new PrivateStoreMsgSell(this)); + break; + } + case BUY: + { + player.sendPacket(new PrivateStoreMsgBuy(this)); + break; + } + case MANUFACTURE: + { + player.sendPacket(new RecipeShopMsg(this)); + break; + } + } + } + + public boolean isAllowedToEnchantSkills() + { + if (_subclassLock) + { + return false; + } + + if (AttackStanceTaskManager.getInstance().hasAttackStanceTask(this)) + { + return false; + } + + if (isCastingNow() || isCastingSimultaneouslyNow()) + { + return false; + } + + if (isInBoat()) + { + return false; + } + + return true; + } + + /** + * Set the _createDate of the Player. + * @param createDate + */ + public void setCreateDate(Calendar createDate) + { + _createDate = createDate; + } + + /** + * @return the _createDate of the Player. + */ + public Calendar getCreateDate() + { + return _createDate; + } + + public Collection getFriendList() + { + return _friendList; + } + + public void selectFriend(Integer friendId) + { + if (!_selectedFriendList.contains(friendId)) + { + _selectedFriendList.add(friendId); + } + } + + public void deselectFriend(Integer friendId) + { + _selectedFriendList.remove(friendId); + } + + public List getSelectedFriendList() + { + return _selectedFriendList; + } + + public void restoreFriendList() + { + _friendList.clear(); + + final String sqlQuery = "SELECT friendId FROM character_friends WHERE charId=? AND relation=0"; + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement(sqlQuery)) + { + ps.setInt(1, getObjectId()); + try (ResultSet rs = ps.executeQuery()) + { + while (rs.next()) + { + final int friendId = rs.getInt("friendId"); + if (friendId == getObjectId()) + { + continue; + } + + _friendList.add(friendId); + } + } + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Error found in " + getName() + "'s FriendList: " + e.getMessage(), e); + } + } + + private void notifyFriends() + { + final FriendStatusPacket pkt = new FriendStatusPacket(getObjectId()); + for (int id : _friendList) + { + final Player friend = World.getPlayer(id); + if (friend != null) + { + friend.sendPacket(pkt); + } + } + } + + public void selectBlock(Integer friendId) + { + if (!_selectedBlocksList.contains(friendId)) + { + _selectedBlocksList.add(friendId); + } + } + + public void deselectBlock(Integer friendId) + { + _selectedBlocksList.remove(friendId); + } + + public List getSelectedBlocksList() + { + return _selectedBlocksList; + } + + /** + * Verify if this player is in silence mode. + * @return the {@code true} if this player is in silence mode, {@code false} otherwise + */ + public boolean isSilenceMode() + { + return _silenceMode; + } + + /** + * While at silenceMode, checks if this player blocks PMs for this user + * @param playerObjId the player object Id + * @return {@code true} if the given Id is not excluded and this player is in silence mode, {@code false} otherwise + */ + public boolean isSilenceMode(int playerObjId) + { + if (PlayerConfig.SILENCE_MODE_EXCLUDE && _silenceMode && (_silenceModeExcluded != null)) + { + return !_silenceModeExcluded.contains(playerObjId); + } + + return _silenceMode; + } + + /** + * Set the silence mode. + * @param mode the value + */ + public void setSilenceMode(boolean mode) + { + _silenceMode = mode; + if (_silenceModeExcluded != null) + { + _silenceModeExcluded.clear(); // Clear the excluded list on each setSilenceMode. + } + + sendPacket(new EtcStatusUpdate(this)); + } + + /** + * Add a player to the "excluded silence mode" list. + * @param playerObjId the player's object Id + */ + public void addSilenceModeExcluded(int playerObjId) + { + if (_silenceModeExcluded == null) + { + _silenceModeExcluded = new ArrayList<>(1); + } + + _silenceModeExcluded.add(playerObjId); + } + + private void storeRecipeShopList() + { + if (hasManufactureShop()) + { + try (Connection con = DatabaseFactory.getConnection()) + { + try (PreparedStatement st = con.prepareStatement(DELETE_CHAR_RECIPE_SHOP)) + { + st.setInt(1, getObjectId()); + st.execute(); + } + + try (PreparedStatement st = con.prepareStatement(INSERT_CHAR_RECIPE_SHOP)) + { + final AtomicInteger slot = new AtomicInteger(1); + for (ManufactureItem item : _manufactureItems.values()) + { + st.setInt(1, getObjectId()); + st.setInt(2, item.getRecipeId()); + st.setLong(3, item.getCost()); + st.setInt(4, slot.getAndIncrement()); + st.addBatch(); + } + + st.executeBatch(); + // No need to call con.commit() as HikariCP autocommit is true. + } + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "Could not store recipe shop for playerId " + getObjectId() + ": ", e); + } + } + } + + private void restoreRecipeShopList() + { + if (_manufactureItems != null) + { + _manufactureItems.clear(); + } + + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement(RESTORE_CHAR_RECIPE_SHOP)) + { + ps.setInt(1, getObjectId()); + try (ResultSet rs = ps.executeQuery()) + { + while (rs.next()) + { + getManufactureItems().put(rs.getInt("recipeId"), new ManufactureItem(rs.getInt("recipeId"), rs.getInt("price"))); + } + } + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "Could not restore recipe shop list data for playerId: " + getObjectId(), e); + } + } + + public float getCollisionRadius() + { + if (isMounted() && (_mountNpcId > 0)) + { + return NpcData.getInstance().getTemplate(getMountNpcId()).getFCollisionRadius(); + } + + return _appearance.isFemale() ? getBaseTemplate().getFCollisionRadiusFemale() : getBaseTemplate().getFCollisionRadius(); + } + + public float getCollisionHeight() + { + if (isMounted() && (_mountNpcId > 0)) + { + return NpcData.getInstance().getTemplate(getMountNpcId()).getFCollisionHeight(); + } + + return _appearance.isFemale() ? getBaseTemplate().getFCollisionHeightFemale() : getBaseTemplate().getFCollisionHeight(); + } + + public int getClientX() + { + return _clientX; + } + + public int getClientY() + { + return _clientY; + } + + public int getClientZ() + { + return _clientZ; + } + + public int getClientHeading() + { + return _clientHeading; + } + + public void setClientX(int value) + { + _clientX = value; + } + + public void setClientY(int value) + { + _clientY = value; + } + + public void setClientZ(int value) + { + _clientZ = value; + } + + public void setClientHeading(int value) + { + _clientHeading = value; + } + + /** + * @param z + * @return true if character falling now on the start of fall return false for correct coord sync! + */ + public boolean isFalling(int z) + { + if (isDead() || isFlying() || isInsideZone(ZoneId.WATER)) + { + return false; + } + + if ((_fallingTimestamp != 0) && (System.currentTimeMillis() < _fallingTimestamp)) + { + return true; + } + + final int deltaZ = getZ() - z; + if (deltaZ <= getBaseTemplate().getSafeFallHeight()) + { + _fallingTimestamp = 0; + return false; + } + + // If there is no geodata loaded for the place we are, client Z correction might cause falling damage. + if (!GeoEngine.getInstance().hasGeo(getX(), getY())) + { + _fallingTimestamp = 0; + return false; + } + + if (_fallingDamage == 0) + { + _fallingDamage = (int) Formulas.calcFallDam(this, deltaZ); + } + + if (_fallingDamageTask != null) + { + _fallingDamageTask.cancel(true); + } + + _fallingDamageTask = ThreadPool.schedule(() -> + { + if ((_fallingDamage > 0) && !isInvul()) + { + reduceCurrentHp(Math.min(_fallingDamage, getCurrentHp() - 1), null, false, true, null); + final SystemMessage sm = new SystemMessage(SystemMessageId.YOU_RECEIVED_S1_DAMAGE_FROM_TAKING_A_HIGH_FALL); + sm.addInt(_fallingDamage); + sendPacket(sm); + } + + _fallingDamage = 0; + _fallingDamageTask = null; + }, 1500); + + // Prevent falling under ground. + sendPacket(new ValidateLocation(this)); + setFalling(); + + return false; + } + + /** + * Set falling timestamp + */ + public void setFalling() + { + _fallingTimestamp = System.currentTimeMillis() + FALLING_VALIDATION_DELAY; + } + + /** + * Update last item auction request timestamp to current + */ + public void updateLastItemAuctionRequest() + { + _lastItemAuctionInfoRequest = System.currentTimeMillis(); + } + + /** + * @return true if receiving item auction requests
    + * (last request was in 2 seconds before) + */ + public boolean isItemAuctionPolling() + { + return (System.currentTimeMillis() - _lastItemAuctionInfoRequest) < 2000; + } + + public String getHtmlPrefix() + { + return MultilingualSupportConfig.MULTILANG_ENABLE ? _htmlPrefix : ""; + } + + public String getLang() + { + return _lang; + } + + public boolean setLang(String lang) + { + boolean result = false; + if (MultilingualSupportConfig.MULTILANG_ENABLE) + { + if (MultilingualSupportConfig.MULTILANG_ALLOWED.contains(lang)) + { + _lang = lang; + result = true; + } + else + { + _lang = MultilingualSupportConfig.MULTILANG_DEFAULT; + } + + _htmlPrefix = _lang.equals("en") ? "" : "data/lang/" + _lang + "/"; + } + else + { + _lang = null; + _htmlPrefix = ""; + } + + return result; + } + + public long getOfflineStartTime() + { + return _offlineShopStart; + } + + public void setOfflineStartTime(long time) + { + _offlineShopStart = time; + } + + public int getPcCafePoints() + { + return _pcCafePoints; + } + + public void setPcCafePoints(int count) + { + _pcCafePoints = count < PremiumSystemConfig.PC_CAFE_MAX_POINTS ? count : PremiumSystemConfig.PC_CAFE_MAX_POINTS; + } + + /** + * Remove player from BossZones (used on char logout/exit) + */ + public void removeFromBossZone() + { + try + { + for (BossZone zone : GrandBossManager.getInstance().getZones().values()) + { + zone.removePlayer(this); + } + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Exception on removeFromBossZone(): " + e.getMessage(), e); + } + } + + /** + * Check all player skills for skill level. If player level is lower than skill learn level - 9, skill level is decreased to next possible level. + */ + public void checkPlayerSkills() + { + for (Entry e : getSkills().entrySet()) + { + final SkillLearn learn = SkillTreeData.getInstance().getClassSkill(e.getKey(), e.getValue().getLevel() % 100, getPlayerClass()); + if (learn != null) + { + final int levelDiff = e.getKey() == CommonSkill.EXPERTISE.getId() ? 0 : 9; + if (getLevel() < (learn.getGetLevel() - levelDiff)) + { + deacreaseSkillLevel(e.getValue(), levelDiff); + } + } + } + } + + private void deacreaseSkillLevel(Skill skill, int levelDiff) + { + int nextLevel = -1; + final Map skillTree = SkillTreeData.getInstance().getCompleteClassSkillTree(getPlayerClass()); + for (SkillLearn sl : skillTree.values()) + { + if ((sl.getSkillId() == skill.getId()) && (nextLevel < sl.getSkillLevel()) && (getLevel() >= (sl.getGetLevel() - levelDiff))) + { + nextLevel = sl.getSkillLevel(); // next possible skill level + } + } + + if (nextLevel == -1) + { + LOGGER.info("Removing skill " + skill + " from " + this); + removeSkill(skill, true); // there is no lower skill + } + else + { + LOGGER.info("Decreasing skill " + skill + " to " + nextLevel + " for " + this); + addSkill(SkillData.getInstance().getSkill(skill.getId(), nextLevel), true); // replace with lower one + } + } + + public boolean canMakeSocialAction() + { + return (_privateStoreType == PrivateStoreType.NONE) && (getActiveRequester() == null) && !isAlikeDead() && !isAllSkillsDisabled() && !isCastingNow() && !isCastingSimultaneouslyNow() && (getAI().getIntention() == Intention.IDLE); + } + + public void setMultiSocialAction(int id, int targetId) + { + _multiSociaAction = id; + _multiSocialTarget = targetId; + } + + public int getMultiSociaAction() + { + return _multiSociaAction; + } + + public int getMultiSocialTarget() + { + return _multiSocialTarget; + } + + public boolean canAttackCreature(Creature creature) + { + if (creature == null) + { + return false; + } + + if (creature.isAttackable()) + { + return true; + } + + if (creature.isPlayable()) + { + if (creature.isInsideZone(ZoneId.PVP) && !creature.isInsideZone(ZoneId.SIEGE)) + { + return true; + } + + Player target; + if (creature.isSummon()) + { + target = creature.asSummon().getOwner(); + } + else + { + target = creature.asPlayer(); + } + + if (isInDuel() && target.isInDuel() && (target.getDuelId() == getDuelId())) + { + return true; + } + else if (isInParty() && target.isInParty()) + { + if (getParty() == target.getParty()) + { + return false; + } + + if (((getParty().getCommandChannel() != null) || (target.getParty().getCommandChannel() != null)) && (getParty().getCommandChannel() == target.getParty().getCommandChannel())) + { + return false; + } + } + else if ((getClan() != null) && (target.getClan() != null)) + { + if (getClanId() == target.getClanId()) + { + return false; + } + + if (((getAllyId() > 0) || (target.getAllyId() > 0)) && (getAllyId() == target.getAllyId())) + { + return false; + } + + if (getClan().isAtWarWith(target.getClan().getId()) && target.getClan().isAtWarWith(getClan().getId())) + { + return true; + } + } + else if ((getClan() == null) || (target.getClan() == null)) + { + if ((target.getPvpFlag() == 0) && (target.getKarma() == 0)) + { + return false; + } + } + } + + return true; + } + + /** + * Test if player inventory is under 90% capacity + * @param includeQuestInv check also quest inventory + * @return + */ + public boolean isInventoryUnder90(boolean includeQuestInv) + { + return (includeQuestInv ? _inventory.getSize() : _inventory.getNonQuestSize()) <= (getInventoryLimit() * 0.9); + } + + /** + * Test if player inventory is under 80% capacity + * @param includeQuestInv check also quest inventory + * @return + */ + public boolean isInventoryUnder80(boolean includeQuestInv) + { + return (includeQuestInv ? _inventory.getSize() : _inventory.getNonQuestSize()) <= (getInventoryLimit() * 0.8); + } + + public boolean havePetInvItems() + { + return _petItems; + } + + public void setPetInvItems(boolean haveit) + { + _petItems = haveit; + } + + /** + * Restore Pet's inventory items from database. + */ + private void restorePetInventoryItems() + { + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement("SELECT object_id FROM `items` WHERE `owner_id`=? AND (`loc`='PET' OR `loc`='PET_EQUIP') LIMIT 1;")) + { + ps.setInt(1, getObjectId()); + try (ResultSet rs = ps.executeQuery()) + { + setPetInvItems(rs.next() && (rs.getInt("object_id") > 0)); + } + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "Could not check Items in Pet Inventory for playerId: " + getObjectId(), e); + } + } + + public String getAdminConfirmCmd() + { + return _adminConfirmCmd; + } + + public void setAdminConfirmCmd(String adminConfirmCmd) + { + _adminConfirmCmd = adminConfirmCmd; + } + + public void setPremiumStatus(boolean premiumStatus) + { + _premiumStatus = premiumStatus; + } + + public boolean hasPremiumStatus() + { + return PremiumSystemConfig.PREMIUM_SYSTEM_ENABLED && _premiumStatus; + } + + public void setLastPetitionGmName(String gmName) + { + _lastPetitionGmName = gmName; + } + + public String getLastPetitionGmName() + { + return _lastPetitionGmName; + } + + public ContactList getContactList() + { + return _contactList; + } + + public long getNotMoveUntil() + { + return _notMoveUntil; + } + + public void updateNotMoveUntil() + { + _notMoveUntil = System.currentTimeMillis() + PlayerConfig.PLAYER_MOVEMENT_BLOCK_TIME; + } + + @Override + public boolean isPlayer() + { + return true; + } + + @Override + public Player asPlayer() + { + return this; + } + + @Override + public boolean isChargedShot(ShotType type) + { + final Item weapon = getActiveWeaponInstance(); + return (weapon != null) && weapon.isChargedShot(type); + } + + @Override + public void setChargedShot(ShotType type, boolean charged) + { + final Item weapon = getActiveWeaponInstance(); + if (weapon != null) + { + weapon.setChargedShot(type, charged); + } + } + + /** + * @param skillId the display skill Id + * @return the custom skill + */ + public Skill getCustomSkill(int skillId) + { + return (_customSkills != null) ? _customSkills.get(skillId) : null; + } + + /** + * Add a skill level to the custom skills map. + * @param skill the skill to add + */ + private void addCustomSkill(Skill skill) + { + if ((skill == null) || (skill.getDisplayId() == skill.getId())) + { + return; + } + + if (_customSkills == null) + { + _customSkills = new ConcurrentHashMap<>(); + } + + _customSkills.put(skill.getDisplayId(), skill); + } + + /** + * Remove a skill level from the custom skill map. + * @param skill the skill to remove + */ + private void removeCustomSkill(Skill skill) + { + if ((skill != null) && (_customSkills != null) && (skill.getDisplayId() != skill.getId())) + { + _customSkills.remove(skill.getDisplayId()); + } + } + + /** + * @return {@code true} if current player can revive and shows 'To Village' button upon death, {@code false} otherwise. + */ + @Override + public boolean canRevive() + { + return _canRevive; + } + + /** + * This method can prevent from displaying 'To Village' button upon death. + * @param value + */ + @Override + public void setCanRevive(boolean value) + { + _canRevive = value; + } + + public boolean isRegisteredOnEvent() + { + return _isRegisteredOnEvent || _isOnEvent; + } + + public void setRegisteredOnEvent(boolean value) + { + _isRegisteredOnEvent = value; + } + + @Override + public boolean isOnEvent() + { + return _isOnEvent; + } + + public void setOnEvent(boolean value) + { + _isOnEvent = value; + } + + public boolean isOnSoloEvent() + { + return _isOnSoloEvent; + } + + public void setOnSoloEvent(boolean value) + { + _isOnSoloEvent = value; + } + + public void setOriginalCpHpMp(double cp, double hp, double mp) + { + _originalCp = cp; + _originalHp = hp; + _originalMp = mp; + } + + /** + * @return {@code true} if {@link PlayerVariables} instance is attached to current player's scripts, {@code false} otherwise. + */ + public boolean hasVariables() + { + return getScript(PlayerVariables.class) != null; + } + + /** + * @return {@link PlayerVariables} instance containing parameters regarding player. + */ + public PlayerVariables getVariables() + { + final PlayerVariables vars = getScript(PlayerVariables.class); + return vars != null ? vars : addScript(new PlayerVariables(getObjectId())); + } + + /** + * @return {@code true} if {@link AccountVariables} instance is attached to current player's scripts, {@code false} otherwise. + */ + public boolean hasAccountVariables() + { + return getScript(AccountVariables.class) != null; + } + + /** + * @return {@link AccountVariables} instance containing parameters regarding player. + */ + public AccountVariables getAccountVariables() + { + final AccountVariables vars = getScript(AccountVariables.class); + return vars != null ? vars : addScript(new AccountVariables(getAccountName())); + } + + @Override + public int getId() + { + return getPlayerClass().getId(); + } + + public boolean isPartyBanned() + { + return PunishmentManager.getInstance().hasPunishment(getObjectId(), PunishmentAffect.CHARACTER, PunishmentType.PARTY_BAN); + } + + /** + * @param act + * @return {@code true} if action was added successfully, {@code false} otherwise. + */ + public boolean addAction(PlayerAction act) + { + if (!hasAction(act)) + { + _actionMask |= act.getMask(); + return true; + } + + return false; + } + + /** + * @param act + * @return {@code true} if action was removed successfully, {@code false} otherwise. + */ + public boolean removeAction(PlayerAction act) + { + if (hasAction(act)) + { + _actionMask &= ~act.getMask(); + return true; + } + + return false; + } + + /** + * @param act + * @return {@code true} if action is present, {@code false} otherwise. + */ + public boolean hasAction(PlayerAction act) + { + return (_actionMask & act.getMask()) == act.getMask(); + } + + /** + * Set true/false if character got Charm of Courage + * @param value true/false + */ + public void setCharmOfCourage(boolean value) + { + _hasCharmOfCourage = value; + } + + /** + * @return {@code true} if effect is present, {@code false} otherwise. + */ + public boolean hasCharmOfCourage() + { + return _hasCharmOfCourage; + } + + public boolean isGood() + { + return _isGood; + } + + public boolean isEvil() + { + return _isEvil; + } + + public void setGood() + { + _isGood = true; + _isEvil = false; + } + + public void setEvil() + { + _isGood = false; + _isEvil = true; + } + + /** + * @param target the target + * @return {@code true} if this player got war with the target, {@code false} otherwise. + */ + public boolean isAtWarWith(Creature target) + { + if (target == null) + { + return false; + } + + if ((_clan != null) && !isAcademyMember() && (target.getClan() != null) && !target.isAcademyMember()) + { + return _clan.isAtWarWith(target.getClan()); + } + + return false; + } + + /** + * @param target the target + * @return {@code true} if this player in same party with the target, {@code false} otherwise. + */ + public boolean isInPartyWith(Creature target) + { + return isInParty() && target.isInParty() && (getParty().getLeaderObjectId() == target.getParty().getLeaderObjectId()); + } + + /** + * @param target the target + * @return {@code true} if this player in same command channel with the target, {@code false} otherwise. + */ + public boolean isInCommandChannelWith(Creature target) + { + return isInParty() && target.isInParty() && getParty().isInCommandChannel() && target.getParty().isInCommandChannel() && (getParty().getCommandChannel().getLeaderObjectId() == target.getParty().getCommandChannel().getLeaderObjectId()); + } + + /** + * @param target the target + * @return {@code true} if this player in same clan with the target, {@code false} otherwise. + */ + public boolean isInClanWith(Creature target) + { + return (getClanId() != 0) && (target.getClanId() != 0) && (getClanId() == target.getClanId()); + } + + /** + * @param target the target + * @return {@code true} if this player in same ally with the target, {@code false} otherwise. + */ + public boolean isInAllyWith(Creature target) + { + return (getAllyId() != 0) && (target.getAllyId() != 0) && (getAllyId() == target.getAllyId()); + } + + /** + * @param target the target + * @return {@code true} if this player at duel with the target, {@code false} otherwise. + */ + public boolean isInDuelWith(Creature target) + { + return isInDuel() && target.isInDuel() && (getDuelId() == target.getDuelId()); + } + + /** + * @param target the target + * @return {@code true} if this player is on same siege side with the target, {@code false} otherwise. + */ + public boolean isOnSameSiegeSideWith(Creature target) + { + return (getSiegeState() > 0) && isInsideZone(ZoneId.SIEGE) && (getSiegeState() == target.getSiegeState()) && (getSiegeSide() == target.getSiegeSide()); + } + + /** + * @return the game shop points of the player. + */ + public long getGamePoints() + { + return getAccountVariables().getInt(GAME_POINTS_VAR, 0); + } + + /** + * Sets game shop points for current player. + * @param points + */ + public void setGamePoints(long points) + { + // Immediate store upon change. + final AccountVariables vars = getAccountVariables(); + vars.set(GAME_POINTS_VAR, Math.max(points, 0)); + vars.storeMe(); + } + + private TerminateReturn onExperienceReceived() + { + if (isDead()) + { + return new TerminateReturn(false, false, false); + } + + return new TerminateReturn(true, true, true); + } + + public void disableExpGain() + { + addListener(new FunctionEventListener(this, EventType.ON_PLAYABLE_EXP_CHANGED, (OnPlayableExpChanged _) -> onExperienceReceived(), this)); + } + + public void enableExpGain() + { + removeListenerIf(EventType.ON_PLAYABLE_EXP_CHANGED, listener -> listener.getOwner() == this); + } + + public void sendInventoryUpdate(InventoryUpdate iu) + { + if (_inventoryUpdateTask != null) + { + _inventoryUpdateTask.cancel(false); + } + + _inventoryUpdate.putAll(iu.getItemEntries()); + + _inventoryUpdateTask = ThreadPool.schedule(() -> + { + sendPacket(_inventoryUpdate); + }, 100); + } + + public void sendItemList(boolean open) + { + if (_itemListTask != null) + { + _itemListTask.cancel(false); + } + + _itemListTask = ThreadPool.schedule(() -> + { + sendPacket(new ItemList(this, open)); + }, 250); + } + + /** + * Precautionary method to end all tasks upon disconnection. + * @TODO: Rework stopAllTimers() method. + */ + public void stopAllTasks() + { + if ((_mountFeedTask != null) && !_mountFeedTask.isDone() && !_mountFeedTask.isCancelled()) + { + _mountFeedTask.cancel(false); + _mountFeedTask = null; + } + + if ((_dismountTask != null) && !_dismountTask.isDone() && !_dismountTask.isCancelled()) + { + _dismountTask.cancel(false); + _dismountTask = null; + } + + if ((_fameTask != null) && !_fameTask.isDone() && !_fameTask.isCancelled()) + { + _fameTask.cancel(false); + _fameTask = null; + } + + if ((_vitalityTask != null) && !_vitalityTask.isDone() && !_vitalityTask.isCancelled()) + { + _vitalityTask.cancel(false); + _vitalityTask = null; + } + + if ((_teleportWatchdog != null) && !_teleportWatchdog.isDone() && !_teleportWatchdog.isCancelled()) + { + _teleportWatchdog.cancel(false); + _teleportWatchdog = null; + } + + if ((_taskForFish != null) && !_taskForFish.isDone() && !_taskForFish.isCancelled()) + { + _taskForFish.cancel(false); + _taskForFish = null; + } + + if ((_chargeTask != null) && !_chargeTask.isDone() && !_chargeTask.isCancelled()) + { + _chargeTask.cancel(false); + _chargeTask = null; + } + + if ((_taskRentPet != null) && !_taskRentPet.isDone() && !_taskRentPet.isCancelled()) + { + _taskRentPet.cancel(false); + _taskRentPet = null; + } + + if ((_taskWater != null) && !_taskWater.isDone() && !_taskWater.isCancelled()) + { + _taskWater.cancel(false); + _taskWater = null; + } + + if ((_fallingDamageTask != null) && !_fallingDamageTask.isDone() && !_fallingDamageTask.isCancelled()) + { + _fallingDamageTask.cancel(false); + _fallingDamageTask = null; + } + + if ((_taskWarnUserTakeBreak != null) && !_taskWarnUserTakeBreak.isDone() && !_taskWarnUserTakeBreak.isCancelled()) + { + _taskWarnUserTakeBreak.cancel(false); + _taskWarnUserTakeBreak = null; + } + + synchronized (_questTimers) + { + for (QuestTimer timer : _questTimers) + { + timer.cancelTask(); + } + + _questTimers.clear(); + } + + synchronized (_timerHolders) + { + for (TimerHolder timer : _timerHolders) + { + timer.cancelTask(); + } + + _timerHolders.clear(); + } + } + + public void addQuestTimer(QuestTimer questTimer) + { + synchronized (_questTimers) + { + _questTimers.add(questTimer); + } + } + + public void removeQuestTimer(QuestTimer questTimer) + { + synchronized (_questTimers) + { + _questTimers.remove(questTimer); + } + } + + public void addTimerHolder(TimerHolder timer) + { + synchronized (_timerHolders) + { + _timerHolders.add(timer); + } + } + + public void removeTimerHolder(TimerHolder timer) + { + synchronized (_timerHolders) + { + _timerHolders.remove(timer); + } + } + + public void setServitorShare(Map map) + { + _servitorShare = map; + } + + public double getServitorShareBonus(Stat stat) + { + final Map stats = _servitorShare; + if (stats == null) + { + return 1.0d; + } + + final Double val = stats.get(stat); + if (val == null) + { + return 1.0d; + } + + return val; + } + + public AutoPlaySettingsHolder getAutoPlaySettings() + { + return _autoPlaySettings; + } + + public AutoUseSettingsHolder getAutoUseSettings() + { + return _autoUseSettings; + } + + public void setAutoPlaying(boolean value) + { + _autoPlaying.set(value); + + if (!value && _offlinePlay && OfflinePlayConfig.RESTORE_AUTO_PLAY_OFFLINERS) + { + OfflinePlayTable.getInstance().removeOfflinePlay(this); + } + } + + public boolean isAutoPlaying() + { + return _autoPlaying.get(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/Summon.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/Summon.java new file mode 100644 index 0000000..7d93f49 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/Summon.java @@ -0,0 +1,1213 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor; + +import java.util.concurrent.ScheduledFuture; + +import org.l2jmobius.commons.threads.ThreadPool; +import org.l2jmobius.commons.util.Rnd; +import org.l2jmobius.gameserver.ai.CreatureAI; +import org.l2jmobius.gameserver.ai.SummonAI; +import org.l2jmobius.gameserver.config.GeoEngineConfig; +import org.l2jmobius.gameserver.config.PlayerConfig; +import org.l2jmobius.gameserver.config.custom.FakePlayersConfig; +import org.l2jmobius.gameserver.data.xml.ExperienceData; +import org.l2jmobius.gameserver.data.xml.ItemData; +import org.l2jmobius.gameserver.entity.Location; +import org.l2jmobius.gameserver.entity.World; +import org.l2jmobius.gameserver.entity.WorldObject; +import org.l2jmobius.gameserver.entity.actor.enums.creature.InstanceType; +import org.l2jmobius.gameserver.entity.actor.enums.creature.Race; +import org.l2jmobius.gameserver.entity.actor.enums.creature.Team; +import org.l2jmobius.gameserver.entity.actor.holders.npc.AggroInfo; +import org.l2jmobius.gameserver.entity.actor.stat.SummonStat; +import org.l2jmobius.gameserver.entity.actor.status.SummonStatus; +import org.l2jmobius.gameserver.entity.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.entity.groups.Party; +import org.l2jmobius.gameserver.entity.item.EtcItem; +import org.l2jmobius.gameserver.entity.item.Weapon; +import org.l2jmobius.gameserver.entity.item.enums.ItemProcessType; +import org.l2jmobius.gameserver.entity.item.enums.ShotType; +import org.l2jmobius.gameserver.entity.item.instance.Item; +import org.l2jmobius.gameserver.entity.item.type.ActionType; +import org.l2jmobius.gameserver.entity.itemcontainer.PetInventory; +import org.l2jmobius.gameserver.entity.zone.ZoneRegion; +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding; +import org.l2jmobius.gameserver.handler.IItemHandler; +import org.l2jmobius.gameserver.handler.ItemHandler; +import org.l2jmobius.gameserver.managers.ZoneManager; +import org.l2jmobius.gameserver.mechanics.effects.EffectType; +import org.l2jmobius.gameserver.mechanics.events.EventDispatcher; +import org.l2jmobius.gameserver.mechanics.events.EventType; +import org.l2jmobius.gameserver.mechanics.events.holders.actor.player.OnPlayerSummonSpawn; +import org.l2jmobius.gameserver.mechanics.olympiad.Olympiad; +import org.l2jmobius.gameserver.mechanics.skill.Skill; +import org.l2jmobius.gameserver.mechanics.skill.targets.TargetType; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.serverpackets.AbstractNpcInfo.SummonInfo; +import org.l2jmobius.gameserver.network.serverpackets.ActionFailed; +import org.l2jmobius.gameserver.network.serverpackets.PetDelete; +import org.l2jmobius.gameserver.network.serverpackets.PetInfo; +import org.l2jmobius.gameserver.network.serverpackets.PetItemList; +import org.l2jmobius.gameserver.network.serverpackets.PetStatusUpdate; +import org.l2jmobius.gameserver.network.serverpackets.RelationChanged; +import org.l2jmobius.gameserver.network.serverpackets.ServerPacket; +import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; +import org.l2jmobius.gameserver.network.serverpackets.TeleportToLocation; +import org.l2jmobius.gameserver.taskmanagers.DecayTaskManager; +import org.l2jmobius.gameserver.util.ArrayUtil; + +public abstract class Summon extends Playable +{ + private Player _owner; + private int _attackRange = 36; // Melee range + private boolean _follow = true; + private boolean _previousFollowStatus = true; + protected boolean _restoreSummon = true; + private int _shotsMask = 0; + private ScheduledFuture _abnormalEffectTask; + + // @formatter:off + private static final int[] PASSIVE_SUMMONS = + { + 12564, 12621, 14702, 14703, 14704, 14705, 14706, 14707, 14708, 14709, 14710, 14711, + 14712, 14713, 14714, 14715, 14716, 14717, 14718, 14719, 14720, 14721, 14722, 14723, + 14724, 14725, 14726, 14727, 14728, 14729, 14730, 14731, 14732, 14733, 14734, 14735, 14736 + }; + // @formatter:on + + /** + * Creates an abstract summon. + * @param template the summon NPC template + * @param owner the owner + */ + public Summon(NpcTemplate template, Player owner) + { + super(template); + setInstanceType(InstanceType.Summon); + setInstanceId(owner.getInstanceId()); // Set instance to same as owner. + setShowSummonAnimation(true); + _owner = owner; + getAI(); + + // Make sure summon does not spawn in a wall. + final int x = owner.getX(); + final int y = owner.getY(); + final int z = owner.getZ(); + final Location location = GeoEngine.getInstance().getValidLocation(x, y, z, x + Rnd.get(-100, 100), y + Rnd.get(-100, 100), z, owner.getInstanceId()); + setXYZInvisible(location.getX(), location.getY(), location.getZ()); + } + + @Override + public void onSpawn() + { + super.onSpawn(); + + if (PlayerConfig.SUMMON_STORE_SKILL_COOLTIME && !isTeleporting()) + { + restoreEffects(); + } + + setFollowStatus(true); + updateAndBroadcastStatus(0); + sendPacket(new RelationChanged(this, _owner.getRelation(_owner), false)); + World.forEachVisibleObjectInRange(getOwner(), Player.class, 800, player -> player.sendPacket(new RelationChanged(this, _owner.getRelation(player), isAutoAttackable(player)))); + // final Party party = _owner.getParty(); + // if (party != null) + // { + // party.broadcastToPartyMembers(_owner, new ExPartyPetWindowAdd(this)); + // } + + setShowSummonAnimation(false); // AddVisibleObject created the info packets with summon animation. + + // If someone comes into range now, the animation shouldn't show any more. + _restoreSummon = false; + rechargeShots(true, true); + + // Notify to scripts. + if (EventDispatcher.getInstance().hasListener(EventType.ON_PLAYER_SUMMON_SPAWN, this)) + { + EventDispatcher.getInstance().notifyEventAsync(new OnPlayerSummonSpawn(this), this); + } + } + + @Override + public SummonStat getStat() + { + return (SummonStat) super.getStat(); + } + + @Override + public void initCharStat() + { + setStat(new SummonStat(this)); + } + + @Override + public SummonStatus getStatus() + { + return (SummonStatus) super.getStatus(); + } + + @Override + public void initCharStatus() + { + setStatus(new SummonStatus(this)); + } + + @Override + protected CreatureAI initAI() + { + return new SummonAI(this); + } + + @Override + public NpcTemplate getTemplate() + { + return (NpcTemplate) super.getTemplate(); + } + + // This defines the action buttons, 1 for Summon, 2 for Pets. + public abstract int getSummonType(); + + @Override + public void stopAllEffects() + { + super.stopAllEffects(); + updateAndBroadcastStatus(1); + } + + @Override + public void stopAllEffectsExceptThoseThatLastThroughDeath() + { + super.stopAllEffectsExceptThoseThatLastThroughDeath(); + updateAndBroadcastStatus(1); + } + + @Override + public void updateAbnormalEffect() + { + if (_abnormalEffectTask == null) + { + _abnormalEffectTask = ThreadPool.schedule(() -> + { + if (isSpawned()) + { + World.forEachVisibleObject(this, Player.class, player -> player.sendPacket(new SummonInfo(this, player, 1))); + } + + _abnormalEffectTask = null; + }, 50); + } + } + + /** + * @return Returns the mountable. + */ + public boolean isMountable() + { + return false; + } + + public long getExpForThisLevel() + { + if (getLevel() >= ExperienceData.getInstance().getMaxPetLevel()) + { + return 0; + } + + return ExperienceData.getInstance().getExpForLevel(getLevel()); + } + + public long getExpForNextLevel() + { + if (getLevel() >= (ExperienceData.getInstance().getMaxPetLevel() - 1)) + { + return 0; + } + + return ExperienceData.getInstance().getExpForLevel(getLevel() + 1); + } + + @Override + public int getKarma() + { + return _owner != null ? _owner.getKarma() : 0; + } + + @Override + public byte getPvpFlag() + { + return _owner != null ? _owner.getPvpFlag() : 0; + } + + @Override + public Team getTeam() + { + return _owner != null ? _owner.getTeam() : Team.NONE; + } + + public Player getOwner() + { + return _owner; + } + + /** + * Gets the summon ID. + * @return the summon ID + */ + @Override + public int getId() + { + return getTemplate().getId(); + } + + public short getSoulShotsPerHit() + { + if (getTemplate().getSoulShot() > 0) + { + return (short) getTemplate().getSoulShot(); + } + + return 1; + } + + public short getSpiritShotsPerHit() + { + if (getTemplate().getSpiritShot() > 0) + { + return (short) getTemplate().getSpiritShot(); + } + + return 1; + } + + public void followOwner() + { + setFollowStatus(true); + } + + @Override + public boolean doDie(Creature killer) + { + if (isNoblesseBlessedAffected()) + { + stopEffects(EffectType.NOBLESSE_BLESSING); + storeEffect(true); + } + else + { + storeEffect(false); + } + + if (!super.doDie(killer)) + { + return false; + } + + if (_owner != null) + { + World.forEachVisibleObject(this, Attackable.class, targetMob -> + { + if (targetMob.isDead()) + { + return; + } + + final AggroInfo info = targetMob.getAggroList().get(this); + if (info != null) + { + targetMob.addDamageHate(_owner, info.getDamage(), info.getHate()); + } + }); + } + + addKillerHate(killer); + + DecayTaskManager.getInstance().add(this); + return true; + } + + public boolean doDie(Creature killer, boolean decayed) + { + if (!super.doDie(killer)) + { + return false; + } + + addKillerHate(killer); + + if (!decayed) + { + DecayTaskManager.getInstance().add(this); + } + + return true; + } + + /** + * Retail monster AIs add 500 hate against the owner to the monster that finishes a summon or pet. + * @param killer the creature that killed this summon + */ + private void addKillerHate(Creature killer) + { + if ((_owner != null) && (killer != null) && killer.isAttackable() && !killer.isDead()) + { + killer.asAttackable().addDamageHate(_owner, 0, 500); + } + } + + public void stopDecay() + { + DecayTaskManager.getInstance().cancel(this); + } + + @Override + public void onDecay() + { + deleteMe(_owner); + } + + @Override + public void broadcastStatusUpdate() + { + super.broadcastStatusUpdate(); + updateAndBroadcastStatus(1); + } + + public void deleteMe(Player owner) + { + if (owner != null) + { + owner.sendPacket(new PetDelete(getSummonType(), getObjectId())); + // final Party party = owner.getParty(); + // if (party != null) + // { + // party.broadcastToPartyMembers(owner, new ExPartyPetWindowDelete(this)); + // } + } + + // pet will be deleted along with all his items + if (getInventory() != null) + { + getInventory().destroyAllItems(ItemProcessType.DESTROY, _owner, this); + } + + decayMe(); + if (owner != null) + { + owner.setPet(null); + } + + super.deleteMe(); + } + + public void unSummon(Player owner) + { + if (isSpawned() && !isDead()) + { + stopHpMpRegeneration(); + + getAI().stopFollow(); + if (owner != null) + { + owner.sendPacket(new PetDelete(getSummonType(), getObjectId())); + // final Party party = owner.getParty(); + // if (party != null) + // { + // party.broadcastToPartyMembers(owner, new ExPartyPetWindowDelete(this)); + // } + + if ((getInventory() != null) && (getInventory().getSize() > 0)) + { + _owner.setPetInvItems(true); + _owner.sendMessage("There are items in your Pet Inventory rendering you unable to sell/trade/drop pet summoning items. Please empty your Pet Inventory."); + } + else + { + _owner.setPetInvItems(false); + } + } + + abortAttack(); + abortCast(); + storeMe(); + storeEffect(true); + if (owner != null) + { + owner.setPet(null); + } + + // Stop AI tasks. + if (hasAI()) + { + getAI().stopAITask(); + } + + stopAllEffects(); + final ZoneRegion oldRegion = ZoneManager.getInstance().getRegion(this); + decayMe(); + oldRegion.removeFromZones(this); + + setTarget(null); + if (owner != null) + { + for (int itemId : owner.getAutoSoulShot()) + { + final String handler = ((EtcItem) ItemData.getInstance().getTemplate(itemId)).getHandlerName(); + if ((handler != null) && handler.contains("Beast")) + { + owner.disableAutoShot(itemId); + } + } + } + } + } + + public int getAttackRange() + { + return _attackRange; + } + + public void setAttackRange(int range) + { + _attackRange = (range < 36) ? 36 : range; + } + + public void setFollowStatus(boolean value) + { + _follow = value; + if (_follow) + { + getAI().setIntentionFollow(_owner); + } + else + { + getAI().setIntentionIdle(); + } + } + + public boolean getFollowStatus() + { + return _follow; + } + + @Override + public boolean isAutoAttackable(Creature attacker) + { + return (_owner != null) && _owner.isAutoAttackable(attacker); + } + + public int getControlObjectId() + { + return 0; + } + + public Weapon getActiveWeapon() + { + return null; + } + + @Override + public PetInventory getInventory() + { + return null; + } + + public void setRestoreSummon(boolean value) + { + } + + @Override + public Item getActiveWeaponInstance() + { + return null; + } + + @Override + public Weapon getActiveWeaponItem() + { + return null; + } + + @Override + public Item getSecondaryWeaponInstance() + { + return null; + } + + @Override + public Weapon getSecondaryWeaponItem() + { + return null; + } + + /** + * Return True if the Summon is invulnerable or if the summoner is in spawn protection. + */ + @Override + public boolean isInvul() + { + return super.isInvul() || _owner.isSpawnProtected(); + } + + /** + * Return the Party object of its Player owner or null. + */ + @Override + public Party getParty() + { + if (_owner == null) + { + return null; + } + + return _owner.getParty(); + } + + /** + * Return True if the Creature has a Party in progress. + */ + @Override + public boolean isInParty() + { + return (_owner != null) && _owner.isInParty(); + } + + /** + * Check if the active Skill can be casted.
    + *
    + * Actions: + *
      + *
    • Check if the target is correct
    • + *
    • Check if the target is in the skill cast range
    • + *
    • Check if the summon owns enough HP and MP to cast the skill
    • + *
    • Check if all skills are enabled and this skill is enabled
    • + *
    • Check if the skill is active
    • + *
    • Notify the AI with CAST and target
    • + *
    + * @param skill The Skill to use + * @param forceUse used to force ATTACK on players + * @param dontMove used to prevent movement, if not in range + */ + @Override + public boolean useMagic(Skill skill, boolean forceUse, boolean dontMove) + { + // Null skill, dead summon or null owner are reasons to prevent casting. + if ((skill == null) || isDead() || (_owner == null)) + { + return false; + } + + // Check if the skill is active. + if (skill.isPassive()) + { + // Just ignore the passive skill request. Why does the client send it anyway?? + return false; + } + + // If a skill is currently being used. + if (isCastingNow()) + { + return false; + } + + // Set current pet skill. + _owner.setCurrentPetSkill(skill, forceUse, dontMove); + + // Get the target for the skill. + WorldObject target = null; + switch (skill.getTargetType()) + { + // OWNER_PET should be cast even if no target has been found + case OWNER_PET: + { + target = _owner; + break; + } + // PARTY, AURA, SELF should be cast even if no target has been found + case PARTY: + case AURA: + case FRONT_AURA: + case BEHIND_AURA: + case SELF: + case AURA_CORPSE_MOB: + case COMMAND_CHANNEL: + { + target = this; + break; + } + default: + { + // Get the first target of the list. + target = skill.getFirstOfTargetList(this); + break; + } + } + + // Check the validity of the target. + if (target == null) + { + sendPacket(SystemMessageId.YOUR_TARGET_CANNOT_BE_FOUND); + return false; + } + + // Check if this skill is enabled (e.g. reuse time). + if (isSkillDisabled(skill)) + { + final SystemMessage sm = new SystemMessage(SystemMessageId.S1_IS_NOT_AVAILABLE_AT_THIS_TIME_BEING_PREPARED_FOR_REUSE); + sm.addString(skill.getName()); + sendPacket(sm); + return false; + } + + // Check if the summon has enough MP. + if (getCurrentMp() < (getStat().getMpConsume(skill) + getStat().getMpInitialConsume(skill))) + { + // Send a System Message to the caster. + sendPacket(SystemMessageId.NOT_ENOUGH_MP); + return false; + } + + // Check if the summon has enough HP. + if (getCurrentHp() <= skill.getHpConsume()) + { + // Send a System Message to the caster. + sendPacket(SystemMessageId.NOT_ENOUGH_HP); + return false; + } + + if ((this != target) && skill.isPhysical() && (GeoEngineConfig.PATHFINDING > 0) && (PathFinding.getInstance().findPath(getX(), getY(), getZ(), target.getX(), target.getY(), target.getZ(), getInstanceId(), false) == null)) + { + sendPacket(SystemMessageId.CANNOT_SEE_TARGET); + return false; + } + + // Check if this is bad magic skill. + if (skill.hasNegativeEffect()) + { + if (_owner == target) + { + return false; + } + + // Summons can cast skills on NPCs inside peace zones. + if (isInsidePeaceZone(this, target) && !_owner.getAccessLevel().allowPeaceAttack()) + { + // If summon or target is in a peace zone, send a system message: + sendPacket(SystemMessageId.YOU_MAY_NOT_ATTACK_THIS_TARGET_IN_A_PEACEFUL_ZONE); + return false; + } + + // If Player is in Olympiad and the match isn't already start, send a Server->Client packet ActionFailed + if (_owner.isInOlympiadMode() && !_owner.isOlympiadStart()) + { + sendPacket(ActionFailed.STATIC_PACKET); + return false; + } + + if (_owner.isSiegeFriend(target)) + { + _owner.sendMessage("Force attack is impossible against a temporary allied member during a siege."); + sendPacket(ActionFailed.STATIC_PACKET); + return false; + } + + // Check if the target is attackable. + if (target.isDoor()) + { + if (!target.isAutoAttackable(_owner)) + { + return false; + } + } + else + { + // Summons can cast skills on NPCs inside peace zones. + if (!target.canBeAttacked() && !_owner.getAccessLevel().allowPeaceAttack()) + { + return false; + } + + // Check if a Forced attack is in progress on non-attackable target. + if (!target.isAutoAttackable(this) && !forceUse && !target.isNpc() && (skill.getTargetType() != TargetType.AURA) && (skill.getTargetType() != TargetType.FRONT_AURA) && (skill.getTargetType() != TargetType.BEHIND_AURA) && (skill.getTargetType() != TargetType.CLAN) && (skill.getTargetType() != TargetType.PARTY) && (skill.getTargetType() != TargetType.SELF)) + { + return false; + } + } + } + + // Notify the AI with CAST and target. + getAI().setIntentionCast(skill, target); + return true; + } + + @Override + public void setImmobilized(boolean value) + { + super.setImmobilized(value); + + if (value) + { + _previousFollowStatus = _follow; + + // If immobilized temporarily disable follow mode. + if (_previousFollowStatus) + { + setFollowStatus(false); + } + } + else + { + // If not more immobilized restore previous follow mode. + setFollowStatus(_previousFollowStatus); + } + } + + public void setOwner(Player newOwner) + { + _owner = newOwner; + } + + @Override + public void sendDamageMessage(Creature target, int damage, boolean mcrit, boolean pcrit, boolean miss) + { + if (miss || (_owner == null)) + { + return; + } + + // Prevents the double spam of system messages, if the target is the owning player. + if (target.getObjectId() != _owner.getObjectId()) + { + if (pcrit || mcrit) + { + if (isServitor()) + { + sendPacket(SystemMessageId.SUMMONED_MONSTER_S_CRITICAL_HIT); + } + else + { + sendPacket(SystemMessageId.PET_S_CRITICAL_HIT); + } + } + + if (_owner.isInOlympiadMode() && target.isPlayer() && target.asPlayer().isInOlympiadMode() && (target.asPlayer().getOlympiadGameId() == _owner.getOlympiadGameId())) + { + Olympiad.getInstance().notifyCompetitorDamage(getOwner(), damage, getOwner().getOlympiadGameId()); + } + + final SystemMessage sm; + if (target.isInvul() && !target.isNpc()) + { + sm = new SystemMessage(SystemMessageId.THE_ATTACK_HAS_BEEN_BLOCKED); + } + else + { + sm = new SystemMessage(SystemMessageId.YOUR_PET_HIT_FOR_S1_DAMAGE); + sm.addInt(damage); + } + + sendPacket(sm); + } + } + + @Override + public void reduceCurrentHp(double damage, Creature attacker, Skill skill) + { + super.reduceCurrentHp(damage, attacker, skill); + if ((_owner != null) && (attacker != null)) + { + final SystemMessage sm = new SystemMessage(SystemMessageId.YOUR_PET_RECEIVED_S2_DAMAGE_CAUSED_BY_S1); + sm.addInt((int) damage); + sm.addString(attacker.getName()); + sendPacket(sm); + } + } + + @Override + public void doCast(Skill skill) + { + final Player actingPlayer = getOwner(); + if (!actingPlayer.checkPvpSkill(getTarget(), skill) && !actingPlayer.getAccessLevel().allowPeaceAttack()) + { + // Send a System Message to the Player. + actingPlayer.sendPacket(SystemMessageId.THAT_IS_THE_INCORRECT_TARGET); + + // Send a Server->Client packet ActionFailed to the Player. + actingPlayer.sendPacket(ActionFailed.STATIC_PACKET); + return; + } + + super.doCast(skill); + } + + @Override + public boolean isInCombat() + { + return (_owner != null) && _owner.isInCombat(); + } + + @Override + public Player asPlayer() + { + return _owner; + } + + public void updateAndBroadcastStatus(int value) + { + if (_owner == null) + { + return; + } + + if (isSpawned()) + { + sendPacket(new PetInfo(this, value)); + sendPacket(new PetStatusUpdate(this)); + broadcastNpcInfo(value); + + // final Party party = _owner.getParty(); + // if (party != null) + // { + // party.broadcastToPartyMembers(_owner, new ExPartyPetWindowUpdate(this)); + // } + + updateEffectIcons(true); + } + } + + public void broadcastNpcInfo(int value) + { + World.forEachVisibleObject(this, Player.class, player -> + { + if (player == _owner) + { + return; + } + + player.sendPacket(new SummonInfo(this, player, value)); + }); + } + + public boolean isHungry() + { + return false; + } + + public int getWeapon() + { + return 0; + } + + public int getArmor() + { + return 0; + } + + @Override + public void sendInfo(Player player) + { + // Check if the Player is the owner of the Pet. + if (player == _owner) + { + player.sendPacket(new PetInfo(this, 0)); + + // The PetInfo packet wipes the PartySpelled (list of active spells' icons). Re-add them. + updateEffectIcons(true); + if (isPet()) + { + player.sendPacket(new PetItemList(getInventory().getItems())); + } + } + else + { + player.sendPacket(new SummonInfo(this, player, 0)); + } + } + + @Override + public synchronized void onTeleported() + { + super.onTeleported(); + sendPacket(new TeleportToLocation(this, getX(), getY(), getZ(), getHeading())); + } + + @Override + public boolean isUndead() + { + return getTemplate().getRace() == Race.UNDEAD; + } + + /** + * Change the summon's state. + */ + public void switchMode() + { + // Do nothing. + } + + /** + * Cancel the summon's action. + */ + public void cancelAction() + { + if (!isMovementDisabled()) + { + getAI().setIntentionActive(); + } + } + + /** + * Performs an attack to the owner's target. + * @param target the target to attack. + */ + public void doSummonAttack(WorldObject target) + { + if ((_owner != null) && (target != null)) + { + setTarget(target); + getAI().setIntentionAttack(target); + if (target.isFakePlayer() && !FakePlayersConfig.FAKE_PLAYER_AUTO_ATTACKABLE) + { + _owner.updatePvPStatus(); + } + } + } + + /** + * Verify if the summon can perform an attack. + * @param ctrlPressed {@code true} if Ctrl key is pressed + * @return {@code true} if the summon can attack, {@code false} otherwise + */ + public boolean canAttack(boolean ctrlPressed) + { + if (_owner == null) + { + return false; + } + + final WorldObject target = _owner.getTarget(); + if ((target == null) || (this == target) || (_owner == target)) + { + return false; + } + + // Sin eater, Big Boom, Wyvern can't attack with attack button. + final int npcId = getId(); + if (ArrayUtil.contains(PASSIVE_SUMMONS, npcId)) + { + _owner.sendPacket(ActionFailed.STATIC_PACKET); + return false; + } + + if (isBetrayed()) + { + sendPacket(SystemMessageId.YOUR_PET_SERVITOR_IS_UNRESPONSIVE_AND_WILL_NOT_OBEY_ANY_ORDERS); + sendPacket(ActionFailed.STATIC_PACKET); + return false; + } + + if (isPet() && ((getLevel() - _owner.getLevel()) > 20)) + { + sendPacket(SystemMessageId.YOUR_PET_IS_TOO_HIGH_LEVEL_TO_CONTROL); + sendPacket(ActionFailed.STATIC_PACKET); + return false; + } + + if (_owner.isInOlympiadMode() && !_owner.isOlympiadStart()) + { + // If owner is in Olympiad and the match isn't already start, send a Server->Client packet ActionFailed. + _owner.sendPacket(ActionFailed.STATIC_PACKET); + return false; + } + + if (_owner.isSiegeFriend(target)) + { + _owner.sendMessage("Force attack is impossible against a temporary allied member during a siege."); + sendPacket(ActionFailed.STATIC_PACKET); + return false; + } + + if (!_owner.getAccessLevel().allowPeaceAttack() && _owner.isInsidePeaceZone(this, target)) + { + sendPacket(SystemMessageId.YOU_MAY_NOT_ATTACK_THIS_TARGET_IN_A_PEACEFUL_ZONE); + return false; + } + + if (isLockedTarget()) + { + sendPacket(SystemMessageId.FAILED_TO_CHANGE_ATTACK_TARGET); + return false; + } + + // Summons can attack NPCs even when the owner cannot. + if (!target.isAutoAttackable(_owner) && !ctrlPressed && !target.isNpc()) + { + setFollowStatus(false); + getAI().setIntentionFollow(target); + sendPacket(SystemMessageId.INVALID_TARGET); + return false; + } + + // Siege golems AI doesn't support attacking other than doors/walls at the moment. + if (target.isDoor() && (getTemplate().getRace() != Race.SIEGE_WEAPON)) + { + return false; + } + + if (_owner.calculateDistance3D(target) > 3000) + { + getAI().setIntentionFollow(_owner); + sendPacket(SystemMessageId.YOUR_TARGET_IS_OUT_OF_RANGE); + return false; + } + + if (!GeoEngine.getInstance().canSeeTarget(this, target)) + { + getAI().setIntentionFollow(_owner); + sendPacket(SystemMessageId.CANNOT_SEE_TARGET); + return false; + } + + return true; + } + + @Override + public void sendPacket(ServerPacket packet) + { + if (_owner != null) + { + _owner.sendPacket(packet); + } + } + + @Override + public void sendPacket(SystemMessageId id) + { + if (_owner != null) + { + _owner.sendPacket(id); + } + } + + @Override + public boolean isSummon() + { + return true; + } + + @Override + public Summon asSummon() + { + return this; + } + + @Override + public boolean isChargedShot(ShotType type) + { + return (_shotsMask & type.getMask()) == type.getMask(); + } + + @Override + public void setChargedShot(ShotType type, boolean charged) + { + if (charged) + { + _shotsMask |= type.getMask(); + } + else + { + _shotsMask &= ~type.getMask(); + } + } + + @Override + public void rechargeShots(boolean physical, boolean magic) + { + Item item; + IItemHandler handler; + if ((_owner.getAutoSoulShot() == null) || _owner.getAutoSoulShot().isEmpty()) + { + return; + } + + for (int itemId : _owner.getAutoSoulShot()) + { + item = _owner.getInventory().getItemByItemId(itemId); + if (item != null) + { + if (magic && (item.getTemplate().getDefaultAction() == ActionType.SUMMON_SPIRITSHOT)) + { + handler = ItemHandler.getInstance().getHandler(item.getEtcItem()); + if (handler != null) + { + handler.onItemUse(_owner, item, false); + } + } + + if (physical && (item.getTemplate().getDefaultAction() == ActionType.SUMMON_SOULSHOT)) + { + handler = ItemHandler.getInstance().getHandler(item.getEtcItem()); + if (handler != null) + { + handler.onItemUse(_owner, item, false); + } + } + } + else + { + _owner.removeAutoSoulShot(itemId); + } + } + } + + @Override + public int getClanId() + { + return (_owner != null) ? _owner.getClanId() : 0; + } + + @Override + public int getAllyId() + { + return (_owner != null) ? _owner.getAllyId() : 0; + } + + @Override + public boolean isOnEvent() + { + return (_owner != null) && _owner.isOnEvent(); + } + + @Override + public String toString() + { + final StringBuilder sb = new StringBuilder(); + sb.append(super.toString()); + sb.append('('); + sb.append(getId()); + sb.append(") Owner: "); + sb.append(_owner); + return sb.toString(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/Tower.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/Tower.java new file mode 100644 index 0000000..642e9d8 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/Tower.java @@ -0,0 +1,77 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.actor; + +import org.l2jmobius.gameserver.entity.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.network.serverpackets.ActionFailed; + +/** + * This class is a super-class for ControlTower and FlameTower. + * @author Zoey76 + */ +public abstract class Tower extends Npc +{ + public Tower(NpcTemplate template) + { + super(template); + setInvul(false); + } + + @Override + public boolean canBeAttacked() + { + // Attackable during siege by attacker only. + return (getCastle() != null) && (getCastle().getResidenceId() > 0) && getCastle().getSiege().isInProgress(); + } + + @Override + public boolean isAutoAttackable(Creature attacker) + { + // Attackable during siege by attacker only. + return (attacker != null) && attacker.isPlayer() && (getCastle() != null) && (getCastle().getResidenceId() > 0) && getCastle().getSiege().isInProgress() && getCastle().getSiege().checkIsAttacker(attacker.asPlayer().getClan()); + } + + @Override + public void onAction(Player player, boolean interact) + { + if (!canTarget(player)) + { + return; + } + + if (this != player.getTarget()) + { + // Set the target of the Player player. + player.setTarget(this); + } + else if (interact && isAutoAttackable(player) && (Math.abs(player.getZ() - getZ()) < 100) && GeoEngine.getInstance().canSeeTarget(player, this)) + { + // Notify the Player AI with INTERACT. + player.getAI().setIntentionAttack(this); + } + + // Send a Server->Client ActionFailed to the Player in order to avoid that the client wait another packet. + player.sendPacket(ActionFailed.STATIC_PACKET); + } + + @Override + public void onForcedAttack(Player player) + { + onAction(player); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/Vehicle.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/Vehicle.java new file mode 100644 index 0000000..4ad3b97 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/Vehicle.java @@ -0,0 +1,509 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.actor; + +import java.util.Collection; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ScheduledFuture; +import java.util.logging.Level; + +import org.l2jmobius.commons.threads.ThreadPool; +import org.l2jmobius.gameserver.data.xml.MapRegionData; +import org.l2jmobius.gameserver.entity.Location; +import org.l2jmobius.gameserver.entity.World; +import org.l2jmobius.gameserver.entity.actor.enums.creature.InstanceType; +import org.l2jmobius.gameserver.entity.actor.enums.player.TeleportWhereType; +import org.l2jmobius.gameserver.entity.actor.holders.creature.VehiclePathPoint; +import org.l2jmobius.gameserver.entity.actor.stat.VehicleStat; +import org.l2jmobius.gameserver.entity.actor.templates.CreatureTemplate; +import org.l2jmobius.gameserver.entity.item.Weapon; +import org.l2jmobius.gameserver.entity.item.enums.ItemProcessType; +import org.l2jmobius.gameserver.entity.item.instance.Item; +import org.l2jmobius.gameserver.entity.zone.ZoneRegion; +import org.l2jmobius.gameserver.interfaces.ILocational; +import org.l2jmobius.gameserver.managers.ZoneManager; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.serverpackets.InventoryUpdate; +import org.l2jmobius.gameserver.network.serverpackets.ServerPacket; +import org.l2jmobius.gameserver.taskmanagers.GameTimeTaskManager; +import org.l2jmobius.gameserver.taskmanagers.MovementTaskManager; +import org.l2jmobius.gameserver.util.LocationUtil; + +/** + * @author DS + */ +public abstract class Vehicle extends Creature +{ + protected int _dockId = 0; + protected final Collection _passengers = ConcurrentHashMap.newKeySet(); + protected Location _oustLoc = null; + private Runnable _engine = null; + + protected VehiclePathPoint[] _currentPath = null; + protected int _runState = 0; + private ScheduledFuture _monitorTask = null; + private final Location _monitorLocation = new Location(this); + + /** + * Creates an abstract vehicle. + * @param template the vehicle template + */ + public Vehicle(CreatureTemplate template) + { + super(template); + setInstanceType(InstanceType.Vehicle); + setFlying(true); + } + + public boolean isBoat() + { + return false; + } + + public boolean isAirShip() + { + return false; + } + + public boolean canBeControlled() + { + return _engine == null; + } + + public void registerEngine(Runnable r) + { + _engine = r; + } + + public void runEngine(int delay) + { + if (_engine != null) + { + ThreadPool.schedule(_engine, delay); + } + } + + public void executePath(VehiclePathPoint[] path) + { + _runState = 0; + _currentPath = path; + if ((_currentPath != null) && (_currentPath.length > 0)) + { + final VehiclePathPoint point = _currentPath[0]; + if (point.getMoveSpeed() > 0) + { + getStat().setMoveSpeed(point.getMoveSpeed()); + } + + if (point.getRotationSpeed() > 0) + { + getStat().setRotationSpeed(point.getRotationSpeed()); + } + + getAI().setIntentionMoveTo(new Location(point.getX(), point.getY(), point.getZ())); + return; + } + + getAI().setIntentionActive(); + } + + @Override + public boolean moveToNextRoutePoint() + { + _move = null; + if (_currentPath != null) + { + _runState++; + if (_runState < _currentPath.length) + { + final VehiclePathPoint point = _currentPath[_runState]; + if (!isMovementDisabled()) + { + if (point.getMoveSpeed() == 0) + { + point.setHeading(point.getRotationSpeed()); + teleToLocation(point, false); + if (_monitorTask != null) + { + _monitorTask.cancel(true); + _monitorTask = null; + } + + _currentPath = null; + } + else + { + if (point.getMoveSpeed() > 0) + { + getStat().setMoveSpeed(point.getMoveSpeed()); + } + + if (point.getRotationSpeed() > 0) + { + getStat().setRotationSpeed(point.getRotationSpeed()); + } + + final MoveData m = new MoveData(); + m.disregardingGeodata = false; + m.onGeodataPathIndex = -1; + m.xDestination = point.getX(); + m.yDestination = point.getY(); + m.zDestination = point.getZ(); + m.heading = 0; + + final double dx = point.getX() - getX(); + final double dy = point.getY() - getY(); + final double distance = Math.sqrt((dx * dx) + (dy * dy)); + if (distance > 1) + { + setHeading(LocationUtil.calculateHeadingFrom(getX(), getY(), point.getX(), point.getY())); + } + + m.moveStartTime = GameTimeTaskManager.getInstance().getGameTicks(); + _move = m; + MovementTaskManager.getInstance().registerMovingObject(this); + + // Make sure vehicle is not stuck. + if (_monitorTask == null) + { + _monitorTask = ThreadPool.scheduleAtFixedRate(() -> + { + if (!isInDock() && (calculateDistance3D(_monitorLocation) == 0)) + { + if (_currentPath != null) + { + if (_runState < _currentPath.length) + { + _runState = Math.max(0, _runState - 1); + moveToNextRoutePoint(); + } + else + { + broadcastInfo(); + } + } + } + else + { + _monitorLocation.setXYZ(this); + } + }, 1000, 1000); + } + + return true; + } + } + } + else + { + if (_monitorTask != null) + { + _monitorTask.cancel(true); + _monitorTask = null; + } + + _currentPath = null; + } + } + + runEngine(10); + return false; + } + + @Override + public VehicleStat getStat() + { + return (VehicleStat) super.getStat(); + } + + @Override + public void initCharStat() + { + setStat(new VehicleStat(this)); + } + + public boolean isInDock() + { + return _dockId > 0; + } + + public int getDockId() + { + return _dockId; + } + + public void setInDock(int d) + { + _dockId = d; + } + + public void setOustLoc(Location loc) + { + _oustLoc = loc; + } + + public Location getOustLoc() + { + return _oustLoc != null ? _oustLoc : MapRegionData.getInstance().getTeleToLocation(this, TeleportWhereType.TOWN); + } + + public void oustPlayers() + { + _passengers.forEach(this::oustPlayer); + _passengers.clear(); + } + + public void oustPlayer(Player player) + { + player.setVehicle(null); + player.setInVehiclePosition(null); + removePassenger(player); + } + + public boolean addPassenger(Player player) + { + if ((player == null) || _passengers.contains(player) || ((player.getVehicle() != null) && (player.getVehicle() != this))) + { + return false; + } + + _passengers.add(player); + return true; + } + + public void removePassenger(Player player) + { + try + { + _passengers.remove(player); + } + catch (Exception e) + { + // Ignore. + } + } + + public boolean isEmpty() + { + return _passengers.isEmpty(); + } + + public Collection getPassengers() + { + return _passengers; + } + + public void broadcastToPassengers(ServerPacket packet) + { + for (Player player : _passengers) + { + if (player != null) + { + player.sendPacket(packet); + } + } + } + + /** + * Consume ticket(s) and teleport player from boat if no correct ticket. + * @param itemId Ticket itemId + * @param count Ticket count + * @param oustX + * @param oustY + * @param oustZ + */ + public void payForRide(int itemId, int count, int oustX, int oustY, int oustZ) + { + World.forEachVisibleObjectInRange(this, Player.class, 1000, player -> + { + if (player.isInBoat() && (player.getBoat() == this)) + { + if (itemId > 0) + { + final Item ticket = player.getInventory().getItemByItemId(itemId); + if ((ticket == null) || (player.getInventory().destroyItem(ItemProcessType.FEE, ticket, count, player, this) == null)) + { + player.sendPacket(SystemMessageId.YOU_DO_NOT_POSSESS_THE_CORRECT_TICKET_TO_BOARD_THE_BOAT); + player.teleToLocation(new Location(oustX, oustY, oustZ), true); + return; + } + + final InventoryUpdate iu = new InventoryUpdate(); + iu.addModifiedItem(ticket); + player.sendInventoryUpdate(iu); + } + + addPassenger(player); + } + }); + } + + @Override + public boolean updatePosition() + { + final boolean result = super.updatePosition(); + for (Player player : _passengers) + { + if ((player != null) && (player.getVehicle() == this)) + { + player.setXYZ(getX(), getY(), getZ()); + player.revalidateZone(false); + } + } + + return result; + } + + @Override + public void teleToLocation(ILocational loc, boolean allowRandomOffset) + { + if (isMoving()) + { + stopMove(null); + } + + setTeleporting(true); + + getAI().setIntentionActive(); + + for (Player player : _passengers) + { + if (player != null) + { + player.teleToLocation(loc, false); + } + } + + decayMe(); + setXYZ(loc); + + // Temporary fix for heading on teleports. + if (loc.getHeading() != 0) + { + setHeading(loc.getHeading()); + } + + onTeleported(); + revalidateZone(true); + } + + @Override + public void stopMove(Location loc) + { + _move = null; + if (loc != null) + { + setXYZ(loc); + setHeading(loc.getHeading()); + revalidateZone(true); + } + } + + @Override + public boolean deleteMe() + { + _engine = null; + + try + { + if (isMoving()) + { + stopMove(null); + } + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "Failed stopMove().", e); + } + + try + { + oustPlayers(); + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "Failed oustPlayers().", e); + } + + final ZoneRegion oldZoneRegion = ZoneManager.getInstance().getRegion(this); + + try + { + decayMe(); + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "Failed decayMe().", e); + } + + oldZoneRegion.removeFromZones(this); + + return super.deleteMe(); + } + + @Override + public void updateAbnormalEffect() + { + } + + @Override + public Item getActiveWeaponInstance() + { + return null; + } + + @Override + public Weapon getActiveWeaponItem() + { + return null; + } + + @Override + public Item getSecondaryWeaponInstance() + { + return null; + } + + @Override + public Weapon getSecondaryWeaponItem() + { + return null; + } + + @Override + public int getLevel() + { + return 0; + } + + @Override + public boolean isAutoAttackable(Creature attacker) + { + return false; + } + + @Override + public void detachAI() + { + } + + @Override + public boolean isVehicle() + { + return true; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/appearance/PlayerAppearance.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/appearance/PlayerAppearance.java new file mode 100644 index 0000000..f0dea05 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/appearance/PlayerAppearance.java @@ -0,0 +1,203 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.appearance; + +import org.l2jmobius.gameserver.entity.actor.Player; + +public class PlayerAppearance +{ + public static final int DEFAULT_TITLE_COLOR = 0xECF9A2; + + private Player _owner; + + private byte _face; + + private byte _hairColor; + + private byte _hairStyle; + + private boolean _isFemale; + + /** The current visible name of this player, not necessarily the real one */ + private String _visibleName; + + /** The current visible title of this player, not necessarily the real one */ + private String _visibleTitle; + + /** The default name color is 0xFFFFFF. */ + private int _nameColor = 0xFFFFFF; + + /** The default title color is 0xECF9A2. */ + private int _titleColor = DEFAULT_TITLE_COLOR; + + public PlayerAppearance(byte face, byte hColor, byte hStyle, boolean isFemale) + { + _face = face; + _hairColor = hColor; + _hairStyle = hStyle; + _isFemale = isFemale; + } + + /** + * @param visibleName The visibleName to set. + */ + public void setVisibleName(String visibleName) + { + _visibleName = visibleName; + } + + /** + * @return Returns the visibleName. + */ + public String getVisibleName() + { + return _visibleName == null ? _owner.getName() : _visibleName; + } + + /** + * @param visibleTitle The visibleTitle to set. + */ + public void setVisibleTitle(String visibleTitle) + { + _visibleTitle = visibleTitle; + } + + /** + * @return Returns the visibleTitle. + */ + public String getVisibleTitle() + { + return _visibleTitle == null ? _owner.getTitle() : _visibleTitle; + } + + public byte getFace() + { + return _face; + } + + /** + * @param value + */ + public void setFace(int value) + { + _face = (byte) value; + } + + public byte getHairColor() + { + return _hairColor; + } + + /** + * @param value + */ + public void setHairColor(int value) + { + _hairColor = (byte) value; + } + + public byte getHairStyle() + { + return _hairStyle; + } + + /** + * @param value + */ + public void setHairStyle(int value) + { + _hairStyle = (byte) value; + } + + /** + * @return true if char is female + */ + public boolean isFemale() + { + return _isFemale; + } + + public void setFemale() + { + _isFemale = true; + } + + public void setMale() + { + _isFemale = false; + } + + public int getNameColor() + { + return _nameColor; + } + + public void setNameColor(int nameColor) + { + if (nameColor < 0) + { + return; + } + + _nameColor = nameColor; + } + + public void setNameColor(int red, int green, int blue) + { + _nameColor = (red & 0xFF) + ((green & 0xFF) << 8) + ((blue & 0xFF) << 16); + } + + public int getTitleColor() + { + return _titleColor; + } + + public void setTitleColor(int titleColor) + { + if (titleColor < 0) + { + return; + } + + _titleColor = titleColor; + } + + public void setTitleColor(int red, int green, int blue) + { + _titleColor = (red & 0xFF) + ((green & 0xFF) << 8) + ((blue & 0xFF) << 16); + } + + /** + * @param owner The owner to set. + */ + public void setOwner(Player owner) + { + _owner = owner; + } + + /** + * @return Returns the owner. + */ + public Player getOwner() + { + return _owner; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/enums/creature/FenceState.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/enums/creature/FenceState.java new file mode 100644 index 0000000..97edf7a --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/enums/creature/FenceState.java @@ -0,0 +1,45 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.actor.enums.creature; + +/** + * @author Nik64 + */ +public enum FenceState +{ + HIDDEN(0), + OPENED(1), + CLOSED(2), + CLOSED_HIDDEN(0); + + private final int _clientId; + + private FenceState(int clientId) + { + _clientId = clientId; + } + + public int getClientId() + { + return _clientId; + } + + public boolean isGeodataEnabled() + { + return (this == CLOSED_HIDDEN) || (this == CLOSED); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/enums/creature/InstanceType.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/enums/creature/InstanceType.java new file mode 100644 index 0000000..980ba34 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/enums/creature/InstanceType.java @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.enums.creature; + +public enum InstanceType +{ + WorldObject(null), + Item(WorldObject), + Creature(WorldObject), + Npc(Creature), + Playable(Creature), + Summon(Playable), + Player(Playable), + Folk(Npc), + Merchant(Folk), + Warehouse(Folk), + StaticObject(Creature), + Door(Creature), + TerrainObject(Npc), + EffectPoint(Npc), + + // Summons, Pets, Decoys and Traps + Servitor(Summon), + Pet(Summon), + BabyPet(Pet), + Decoy(Creature), + Trap(Npc), + + // Attackable + Attackable(Npc), + Guard(Attackable), + QuestGuard(Guard), + Monster(Attackable), + Chest(Monster), + FeedableBeast(Monster), + TamedBeast(FeedableBeast), + FriendlyMob(Attackable), + RiftInvader(Monster), + RaidBoss(Monster), + GrandBoss(RaidBoss), + + // FlyMobs + FlyNpc(Folk), + FlyMonster(Monster), + FlyRaidBoss(RaidBoss), + FlyTerrainObject(Npc), + + // Festival + FestivalGuide(Npc), + FestivalMonster(Monster), + + // Vehicles + Vehicle(Creature), + Boat(Vehicle), + + // Siege + Defender(Attackable), + Artefact(Folk), + ControlTower(Npc), + FlameTower(Npc), + SiegeFlag(Npc), + + // Seven Signs + SignsPriest(Npc), + DawnPriest(SignsPriest), + DuskPriest(SignsPriest), + DungeonGatekeeper(Npc), + + // City NPCs + Adventurer(Folk), + Auctioneer(Npc), + BroadcastingTower(Npc), + ClanHallManager(Merchant), + Fisherman(Merchant), + OlympiadManager(Npc), + PetManager(Merchant), + RaceManager(Npc), + Teleporter(Npc), + Trainer(Folk), + VillageMaster(Folk), + + // Doormen + Doorman(Folk), + CastleDoorman(Doorman), + FortDoorman(Doorman), + ClanHallDoorman(Doorman), + + // Custom + ClassMaster(Folk), + SchemeBuffer(Npc), + EventMob(Npc); + + private final InstanceType _parent; + private final long _typeL; + private final long _typeH; + private final long _maskL; + private final long _maskH; + + private InstanceType(InstanceType parent) + { + _parent = parent; + + final int high = ordinal() - (Long.SIZE - 1); + if (high < 0) + { + _typeL = 1L << ordinal(); + _typeH = 0; + } + else + { + _typeL = 0; + _typeH = 1L << high; + } + + if ((_typeL < 0) || (_typeH < 0)) + { + throw new Error("Too many instance types, failed to load " + name()); + } + + if (parent != null) + { + _maskL = _typeL | parent._maskL; + _maskH = _typeH | parent._maskH; + } + else + { + _maskL = _typeL; + _maskH = _typeH; + } + } + + public InstanceType getParent() + { + return _parent; + } + + public boolean isType(InstanceType it) + { + return ((_maskL & it._typeL) > 0) || ((_maskH & it._typeH) > 0); + } + + public boolean isTypes(InstanceType... it) + { + for (InstanceType i : it) + { + if (isType(i)) + { + return true; + } + } + + return false; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/enums/creature/Position.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/enums/creature/Position.java new file mode 100644 index 0000000..66de61a --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/enums/creature/Position.java @@ -0,0 +1,58 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.actor.enums.creature; + +import org.l2jmobius.gameserver.interfaces.ILocational; + +/** + * @author Sdw + */ +public enum Position +{ + FRONT, + SIDE, + BACK; + + /** + * Position calculation based on the retail-like formulas:
    + *
      + *
    • heading: (unsigned short) abs(heading - (unsigned short)(int)floor(atan2(toY - fromY, toX - fromX) * 65535.0 / 6.283185307179586))
    • + *
    • side: if (heading >= 0x2000 && heading <= 0x6000 || (unsigned int)(heading - 0xA000) <= 0x4000)
    • + *
    • front: else if ((unsigned int)(heading - 0x2000) <= 0xC000)
    • + *
    • back: otherwise.
    • + *
    + * @param from + * @param to + * @return + */ + public static Position getPosition(ILocational from, ILocational to) + { + final int heading = Math.abs(to.getHeading() - from.calculateHeadingTo(to)); + if (((heading >= 0x2000) && (heading <= 0x6000)) || (Integer.toUnsignedLong(heading - 0xA000) <= 0x4000)) + { + return SIDE; + } + else if (Integer.toUnsignedLong(heading - 0x2000) <= 0xC000) + { + return FRONT; + } + else + { + return BACK; + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/enums/creature/Race.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/enums/creature/Race.java new file mode 100644 index 0000000..fe86d7a --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/enums/creature/Race.java @@ -0,0 +1,48 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.actor.enums.creature; + +/** + * Creature races enumerated. + * @author Zealar + */ +public enum Race +{ + HUMAN, + ELF, + DARK_ELF, + ORC, + DWARF, + ANIMAL, + BEAST, + BUG, + CASTLE_GUARD, + CONSTRUCT, + DEMONIC, + DIVINE, + DRAGON, + ELEMENTAL, + ETC, + FAIRY, + GIANT, + HUMANOID, + MERCENARY, + NONE, + PLANT, + SIEGE_WEAPON, + UNDEAD; +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/enums/creature/Team.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/enums/creature/Team.java new file mode 100644 index 0000000..48c01ad --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/enums/creature/Team.java @@ -0,0 +1,39 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.actor.enums.creature; + +/** + * @author NosBit + */ +public enum Team +{ + NONE(0), + BLUE(1), + RED(2); + + private int _id; + + private Team(int id) + { + _id = id; + } + + public int getId() + { + return _id; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/enums/creature/TrapAction.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/enums/creature/TrapAction.java new file mode 100644 index 0000000..bdcba61 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/enums/creature/TrapAction.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.enums.creature; + +public enum TrapAction +{ + TRAP_TRIGGERED, + TRAP_DETECTED, + TRAP_DISARMED +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/enums/npc/AISkillScope.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/enums/npc/AISkillScope.java new file mode 100644 index 0000000..bf97c9e --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/enums/npc/AISkillScope.java @@ -0,0 +1,37 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.actor.enums.npc; + +/** + * @author Zoey76 + */ +public enum AISkillScope +{ + BUFF, + DEBUFF, + NEGATIVE, + ATTACK, + IMMOBILIZE, + HEAL, + RES, + COT, + UNIVERSAL, + LONG_RANGE, + SHORT_RANGE, + GENERAL, + SUICIDE +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/enums/npc/AIType.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/enums/npc/AIType.java new file mode 100644 index 0000000..fe06988 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/enums/npc/AIType.java @@ -0,0 +1,30 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.actor.enums.npc; + +/** + * @author Zoey76 + */ +public enum AIType +{ + FIGHTER, + ARCHER, + BALANCED, + MAGE, + HEALER, + CORPSE +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/enums/npc/DropType.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/enums/npc/DropType.java new file mode 100644 index 0000000..d337bd2 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/enums/npc/DropType.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.enums.npc; + +/** + * @author Mobius + */ +public enum DropType +{ + DROP, + SPOIL; +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/enums/npc/RaidBossStatus.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/enums/npc/RaidBossStatus.java new file mode 100644 index 0000000..032ecce --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/enums/npc/RaidBossStatus.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.enums.npc; + +/** + * @author Mobius + */ +public enum RaidBossStatus +{ + ALIVE, + DEAD, + UNDEFINED +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/enums/player/ChatBroadcastType.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/enums/player/ChatBroadcastType.java new file mode 100644 index 0000000..fe909f1 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/enums/player/ChatBroadcastType.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.enums.player; + +/** + * @author Mobius + */ +public enum ChatBroadcastType +{ + ON, + OFF, + GM, + GLOBAL; +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/enums/player/DuelResult.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/enums/player/DuelResult.java new file mode 100644 index 0000000..ce54c2c --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/enums/player/DuelResult.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.enums.player; + +public enum DuelResult +{ + CONTINUE, + TEAM_1_WIN, + TEAM_2_WIN, + TEAM_1_SURRENDER, + TEAM_2_SURRENDER, + CANCELED, + TIMEOUT +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/enums/player/IllegalActionPunishmentType.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/enums/player/IllegalActionPunishmentType.java new file mode 100644 index 0000000..5751520 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/enums/player/IllegalActionPunishmentType.java @@ -0,0 +1,43 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.actor.enums.player; + +/** + * Illegal Action Punishment Type. + * @author xban1x + */ +public enum IllegalActionPunishmentType +{ + NONE, + BROADCAST, + KICK, + KICKBAN, + JAIL; + + public static IllegalActionPunishmentType findByName(String name) + { + for (IllegalActionPunishmentType type : values()) + { + if (type.name().equalsIgnoreCase(name)) + { + return type; + } + } + + return NONE; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/enums/player/MacroType.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/enums/player/MacroType.java new file mode 100644 index 0000000..8ab819e --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/enums/player/MacroType.java @@ -0,0 +1,32 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.actor.enums.player; + +/** + * Macro type enumerated. + * @author Zoey76 + */ +public enum MacroType +{ + NONE, + SKILL, + ACTION, + TEXT, + SHORTCUT, + ITEM, + DELAY +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/enums/player/MountType.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/enums/player/MountType.java new file mode 100644 index 0000000..2d93331 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/enums/player/MountType.java @@ -0,0 +1,44 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.actor.enums.player; + +import org.l2jmobius.gameserver.data.enums.CategoryType; +import org.l2jmobius.gameserver.data.xml.CategoryData; + +/** + * @author UnAfraid + */ +public enum MountType +{ + NONE, + STRIDER, + WYVERN; + + public static MountType findByNpcId(int npcId) + { + if (CategoryData.getInstance().isInCategory(CategoryType.STRIDER, npcId)) + { + return STRIDER; + } + else if (CategoryData.getInstance().isInCategory(CategoryType.WYVERN_GROUP, npcId)) + { + return WYVERN; + } + + return NONE; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/enums/player/PlayerAction.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/enums/player/PlayerAction.java new file mode 100644 index 0000000..8ac350d --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/enums/player/PlayerAction.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.enums.player; + +/** + * @author UnAfraid, Mobius + */ +public enum PlayerAction +{ + ADMIN_COMMAND, + ADMIN_SAVE_ZONE, + ADMIN_SHOW_TERRITORY, + USER_ENGAGE, + OFFLINE_TRADE, + OFFLINE_PLAY; + + private final int _mask; + + private PlayerAction() + { + _mask = 1 << ordinal(); + } + + public int getMask() + { + return _mask; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/enums/player/PlayerClass.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/enums/player/PlayerClass.java new file mode 100644 index 0000000..defd309 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/enums/player/PlayerClass.java @@ -0,0 +1,407 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.enums.player; + +import java.util.EnumSet; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.l2jmobius.gameserver.entity.actor.enums.creature.Race; +import org.l2jmobius.gameserver.entity.actor.holders.player.ClassType; + +/** + *

    + * This enum defines all the classes that a player can choose in the game. Each class has a unique ID, a flag indicating if it is a mage class, a flag indicating if it is a summoner class, a race associated with it and a parent class from which it is derived. + *

    + *

    + * The class hierarchy is structured such that each class can have a parent class, forming a tree-like structure.
    + * This allows for class progression and specialization as players advance in the game. + *

    + * It also utility methods to retrieve information, check relationships and determine possible class transfers. + */ +public enum PlayerClass +{ + FIGHTER(0, false, Race.HUMAN, null), + + WARRIOR(1, false, Race.HUMAN, FIGHTER), + GLADIATOR(2, false, Race.HUMAN, WARRIOR), + WARLORD(3, false, Race.HUMAN, WARRIOR), + KNIGHT(4, false, Race.HUMAN, FIGHTER), + PALADIN(5, false, Race.HUMAN, KNIGHT), + DARK_AVENGER(6, false, Race.HUMAN, KNIGHT), + ROGUE(7, false, Race.HUMAN, FIGHTER), + TREASURE_HUNTER(8, false, Race.HUMAN, ROGUE), + HAWKEYE(9, false, Race.HUMAN, ROGUE), + + MAGE(10, true, Race.HUMAN, null), + WIZARD(11, true, Race.HUMAN, MAGE), + SORCERER(12, true, Race.HUMAN, WIZARD), + NECROMANCER(13, true, Race.HUMAN, WIZARD), + WARLOCK(14, true, true, Race.HUMAN, WIZARD), + CLERIC(15, true, Race.HUMAN, MAGE), + BISHOP(16, true, Race.HUMAN, CLERIC), + PROPHET(17, true, Race.HUMAN, CLERIC), + + ELVEN_FIGHTER(18, false, Race.ELF, null), + ELVEN_KNIGHT(19, false, Race.ELF, ELVEN_FIGHTER), + TEMPLE_KNIGHT(20, false, Race.ELF, ELVEN_KNIGHT), + SWORDSINGER(21, false, Race.ELF, ELVEN_KNIGHT), + ELVEN_SCOUT(22, false, Race.ELF, ELVEN_FIGHTER), + PLAINS_WALKER(23, false, Race.ELF, ELVEN_SCOUT), + SILVER_RANGER(24, false, Race.ELF, ELVEN_SCOUT), + + ELVEN_MAGE(25, true, Race.ELF, null), + ELVEN_WIZARD(26, true, Race.ELF, ELVEN_MAGE), + SPELLSINGER(27, true, Race.ELF, ELVEN_WIZARD), + ELEMENTAL_SUMMONER(28, true, true, Race.ELF, ELVEN_WIZARD), + ORACLE(29, true, Race.ELF, ELVEN_MAGE), + ELDER(30, true, Race.ELF, ORACLE), + + DARK_FIGHTER(31, false, Race.DARK_ELF, null), + PALUS_KNIGHT(32, false, Race.DARK_ELF, DARK_FIGHTER), + SHILLIEN_KNIGHT(33, false, Race.DARK_ELF, PALUS_KNIGHT), + BLADEDANCER(34, false, Race.DARK_ELF, PALUS_KNIGHT), + ASSASSIN(35, false, Race.DARK_ELF, DARK_FIGHTER), + ABYSS_WALKER(36, false, Race.DARK_ELF, ASSASSIN), + PHANTOM_RANGER(37, false, Race.DARK_ELF, ASSASSIN), + + DARK_MAGE(38, true, Race.DARK_ELF, null), + DARK_WIZARD(39, true, Race.DARK_ELF, DARK_MAGE), + SPELLHOWLER(40, true, Race.DARK_ELF, DARK_WIZARD), + PHANTOM_SUMMONER(41, true, true, Race.DARK_ELF, DARK_WIZARD), + SHILLIEN_ORACLE(42, true, Race.DARK_ELF, DARK_MAGE), + SHILLIEN_ELDER(43, true, Race.DARK_ELF, SHILLIEN_ORACLE), + + ORC_FIGHTER(44, false, Race.ORC, null), + ORC_RAIDER(45, false, Race.ORC, ORC_FIGHTER), + DESTROYER(46, false, Race.ORC, ORC_RAIDER), + ORC_MONK(47, false, Race.ORC, ORC_FIGHTER), + TYRANT(48, false, Race.ORC, ORC_MONK), + + ORC_MAGE(49, true, Race.ORC, null), + ORC_SHAMAN(50, true, Race.ORC, ORC_MAGE), + OVERLORD(51, true, Race.ORC, ORC_SHAMAN), + WARCRYER(52, true, Race.ORC, ORC_SHAMAN), + + DWARVEN_FIGHTER(53, false, Race.DWARF, null), + SCAVENGER(54, false, Race.DWARF, DWARVEN_FIGHTER), + BOUNTY_HUNTER(55, false, Race.DWARF, SCAVENGER), + ARTISAN(56, false, Race.DWARF, DWARVEN_FIGHTER), + WARSMITH(57, false, Race.DWARF, ARTISAN), + + DUELIST(88, false, Race.HUMAN, GLADIATOR), + DREADNOUGHT(89, false, Race.HUMAN, WARLORD), + PHOENIX_KNIGHT(90, false, Race.HUMAN, PALADIN), + HELL_KNIGHT(91, false, Race.HUMAN, DARK_AVENGER), + SAGITTARIUS(92, false, Race.HUMAN, HAWKEYE), + ADVENTURER(93, false, Race.HUMAN, TREASURE_HUNTER), + ARCHMAGE(94, true, Race.HUMAN, SORCERER), + SOULTAKER(95, true, Race.HUMAN, NECROMANCER), + ARCANA_LORD(96, true, true, Race.HUMAN, WARLOCK), + CARDINAL(97, true, Race.HUMAN, BISHOP), + HIEROPHANT(98, true, Race.HUMAN, PROPHET), + + EVA_TEMPLAR(99, false, Race.ELF, TEMPLE_KNIGHT), + SWORD_MUSE(100, false, Race.ELF, SWORDSINGER), + WIND_RIDER(101, false, Race.ELF, PLAINS_WALKER), + MOONLIGHT_SENTINEL(102, false, Race.ELF, SILVER_RANGER), + MYSTIC_MUSE(103, true, Race.ELF, SPELLSINGER), + ELEMENTAL_MASTER(104, true, true, Race.ELF, ELEMENTAL_SUMMONER), + EVA_SAINT(105, true, Race.ELF, ELDER), + + SHILLIEN_TEMPLAR(106, false, Race.DARK_ELF, SHILLIEN_KNIGHT), + SPECTRAL_DANCER(107, false, Race.DARK_ELF, BLADEDANCER), + GHOST_HUNTER(108, false, Race.DARK_ELF, ABYSS_WALKER), + GHOST_SENTINEL(109, false, Race.DARK_ELF, PHANTOM_RANGER), + STORM_SCREAMER(110, true, Race.DARK_ELF, SPELLHOWLER), + SPECTRAL_MASTER(111, true, true, Race.DARK_ELF, PHANTOM_SUMMONER), + SHILLIEN_SAINT(112, true, Race.DARK_ELF, SHILLIEN_ELDER), + + TITAN(113, false, Race.ORC, DESTROYER), + GRAND_KHAVATARI(114, false, Race.ORC, TYRANT), + DOMINATOR(115, true, Race.ORC, OVERLORD), + DOOMCRYER(116, true, Race.ORC, WARCRYER), + + FORTUNE_SEEKER(117, false, Race.DWARF, BOUNTY_HUNTER), + MAESTRO(118, false, Race.DWARF, WARSMITH); + + /** The Identifier of the Class */ + private final int _id; + + /** True if the class is a mage class */ + private final boolean _isMage; + + /** True if the class is a summoner class */ + private final boolean _isSummoner; + + /** The Race object of the class */ + private final Race _race; + + /** The parent PlayerClass or null if this class is a root */ + private final PlayerClass _parentClass; + + /** List of available Class for next transfer **/ + private final Set _nextClasses = new HashSet<>(1); + + /** A set of player classes that are considered priests **/ + private static final Set _priestSet = EnumSet.of(CLERIC, BISHOP, PROPHET, ORACLE, ELDER, SHILLIEN_ORACLE, SHILLIEN_ELDER, CARDINAL, HIEROPHANT, EVA_SAINT, SHILLIEN_SAINT); + + /** Map to store all PlayerClass instances by their ID */ + private static Map _classMap = new HashMap<>(PlayerClass.values().length); + static + { + for (PlayerClass playerClass : PlayerClass.values()) + { + _classMap.put(playerClass.getId(), playerClass); + } + } + + /** + * Constructs a new PlayerClass instance. + * @param id the class ID. + * @param isMage {@code true} if the class is a mage class. + * @param race the race associated with the class. + * @param parent the parent PlayerClass, or {@code null} if this is a root class. + */ + private PlayerClass(int id, boolean isMage, Race race, PlayerClass parent) + { + _id = id; + _isMage = isMage; + _isSummoner = false; + _race = race; + _parentClass = parent; + + if (_parentClass != null) + { + _parentClass.addNextClass(this); + } + } + + /** + * Constructs a new PlayerClass instance with summoner flag. + * @param id the class ID. + * @param isMage {@code true} if the class is a mage class. + * @param isSummoner {@code true} if the class is a summoner class. + * @param race the race associated with the class. + * @param parent the parent PlayerClass, or {@code null} if this is a root class. + */ + private PlayerClass(int id, boolean isMage, boolean isSummoner, Race race, PlayerClass parent) + { + _id = id; + _isMage = isMage; + _isSummoner = isSummoner; + _race = race; + _parentClass = parent; + + if (_parentClass != null) + { + _parentClass.addNextClass(this); + } + } + + /** + * Retrieves the PlayerClass instance by its ID. + * @param id the ID of the class to retrieve. + * @return the PlayerClass instance corresponding to the given ID, or {@code null} if not found. + */ + public static PlayerClass getPlayerClass(int id) + { + return _classMap.get(id); + } + + /** + * Gets the ID of the class. + * @return the ID of the class. + */ + public int getId() + { + return _id; + } + + /** + * Checks if the class is a mage class. + * @return {@code true} if the class is a mage class. + */ + public boolean isMage() + { + return _isMage; + } + + /** + * Checks if the class is a summoner class. + * @return {@code true} if the class is a summoner class. + */ + public boolean isSummoner() + { + return _isSummoner; + } + + /** + * Gets the race associated with the class. + * @return the Race object of the class. + */ + public Race getRace() + { + return _race; + } + + /** + * Checks if this class is a child of the specified class. + * @param id the parent PlayerClass to check. + * @return {@code true} if this class is a child of the specified class. + */ + public boolean childOf(PlayerClass id) + { + if (_parentClass == null) + { + return false; + } + + if (_parentClass == id) + { + return true; + } + + return _parentClass.childOf(id); + } + + /** + * Checks if this player class belongs to the specified race. + * @param race the race to check against. + * @return {@code true} if this player class is of the specified race, {@code false} otherwise. + */ + public boolean isOfRace(Race race) + { + return _race == race; + } + + /** + * Checks if this player class matches the specified class type.
    + *

    + * The class type can be one of the following:
    + * - {@code FIGHTER}: Represents non-mage classes.
    + * - {@code PRIEST}: Represents priest classes (a subset of mage classes).
    + * - {@code MAGE}: Represents mage classes that are not priests. + *

    + * @param pType the class type to check against. + * @return {@code true} if this player class matches the specified type, {@code false} otherwise. + */ + public boolean isOfType(ClassType pType) + { + switch (pType) + { + case FIGTHER: + { + return !_isMage; + } + case PRIEST: + { + if (!_isMage) + { + return false; + } + + if (_priestSet.contains(this)) + { + return true; + } + + return false; + } + default: + { + if (_isMage && !_priestSet.contains(this)) + { + return true; + } + + return false; + } + } + } + + /** + * Checks if this class is equal to or a child of the specified class. + * @param playerClass the PlayerClass to check. + * @return {@code true} if this class is equal to or a child of the specified class. + */ + public boolean equalsOrChildOf(PlayerClass playerClass) + { + return (this == playerClass) || childOf(playerClass); + } + + /** + * Gets the level of this class in the class hierarchy. + * @return the child level of this PlayerClass (0=root, 1=first class, etc.). + */ + public int level() + { + if (_parentClass == null) + { + return 0; + } + + return 1 + _parentClass.level(); + } + + /** + * Gets the parent class of this class. + * @return the parent PlayerClass, or {@code null} if this is a root class. + */ + public PlayerClass getParent() + { + return _parentClass; + } + + /** + * Gets the root class of this class. + * @return the root PlayerClass of this class. + */ + public PlayerClass getRootClass() + { + if (_parentClass != null) + { + return _parentClass.getRootClass(); + } + + return this; + } + + /** + * Gets the set of possible class transfers for this class. + * @return a set of PlayerClass instances representing possible class transfers. + */ + public Set getNextClasses() + { + return _nextClasses; + } + + /** + * Adds a class to the list of possible class transfers for this class. + * @param playerClass the PlayerClass to add. + */ + private void addNextClass(PlayerClass playerClass) + { + _nextClasses.add(playerClass); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/enums/player/PlayerState.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/enums/player/PlayerState.java new file mode 100644 index 0000000..132ace1 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/enums/player/PlayerState.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.enums.player; + +public enum PlayerState +{ + RESTING, + MOVING, + RUNNING, + STANDING, + FLYING, + BEHIND, + FRONT, + CHAOTIC, + OLYMPIAD +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/enums/player/PrivateStoreType.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/enums/player/PrivateStoreType.java new file mode 100644 index 0000000..d79aa63 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/enums/player/PrivateStoreType.java @@ -0,0 +1,57 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.actor.enums.player; + +/** + * @author NosBit + */ +public enum PrivateStoreType +{ + NONE(0), + SELL(1), + SELL_MANAGE(2), + BUY(3), + BUY_MANAGE(4), + MANUFACTURE(5), + PACKAGE_SELL(8), + SELL_BUFFS(9); + + private int _id; + + private PrivateStoreType(int id) + { + _id = id; + } + + public int getId() + { + return _id; + } + + public static PrivateStoreType findById(int id) + { + for (PrivateStoreType privateStoreType : values()) + { + if (privateStoreType.getId() == id) + { + return privateStoreType; + } + } + + return null; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/enums/player/Sex.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/enums/player/Sex.java new file mode 100644 index 0000000..7bf20e1 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/enums/player/Sex.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.enums.player; + +public enum Sex +{ + MALE, + FEMALE, + ETC; +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/enums/player/ShortcutType.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/enums/player/ShortcutType.java new file mode 100644 index 0000000..a685fe5 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/enums/player/ShortcutType.java @@ -0,0 +1,31 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.actor.enums.player; + +/** + * Shortcut type enumerated. + * @author Zoey76 + */ +public enum ShortcutType +{ + NONE, + ITEM, + SKILL, + ACTION, + MACRO, + RECIPE, +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/enums/player/SocialClass.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/enums/player/SocialClass.java new file mode 100644 index 0000000..93d7562 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/enums/player/SocialClass.java @@ -0,0 +1,39 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.actor.enums.player; + +/** + * Enumerated for Social Classes. + * @author Zoey76 + */ +public enum SocialClass +{ + VAGABOND, + VASSAL, + APPRENTICE, + HEIR, + KNIGHT, + ELDER, + BARON, + VISCOUNT, + COUNT, + MARQUIS, + DUKE, + GRAND_DUKE, + DISTINGUISHED_KING, + EMPEROR +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/enums/player/TeleportType.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/enums/player/TeleportType.java new file mode 100644 index 0000000..d6c29e2 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/enums/player/TeleportType.java @@ -0,0 +1,28 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.actor.enums.player; + +/** + * @author UnAfraid + */ +public enum TeleportType +{ + NORMAL, + NOBLES_TOKEN, + NOBLES_ADENA, + OTHER; +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/enums/player/TeleportWhereType.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/enums/player/TeleportWhereType.java new file mode 100644 index 0000000..57b42ce --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/enums/player/TeleportWhereType.java @@ -0,0 +1,29 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.actor.enums.player; + +/** + * All teleport types. + * @author xban1x + */ +public enum TeleportWhereType +{ + CASTLE, + CLANHALL, + SIEGEFLAG, + TOWN, +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/creature/DoorRequestHolder.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/creature/DoorRequestHolder.java new file mode 100644 index 0000000..47431ce --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/creature/DoorRequestHolder.java @@ -0,0 +1,37 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.actor.holders.creature; + +import org.l2jmobius.gameserver.entity.actor.instance.Door; + +/** + * @author UnAfraid + */ +public class DoorRequestHolder +{ + private final Door _target; + + public DoorRequestHolder(Door door) + { + _target = door; + } + + public Door getDoor() + { + return _target; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/creature/EffectList.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/creature/EffectList.java new file mode 100644 index 0000000..549900a --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/creature/EffectList.java @@ -0,0 +1,1751 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.holders.creature; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.EnumSet; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.logging.Logger; + +import org.l2jmobius.commons.threads.ThreadPool; +import org.l2jmobius.gameserver.config.PlayerConfig; +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.mechanics.effects.AbstractEffect; +import org.l2jmobius.gameserver.mechanics.effects.EffectFlag; +import org.l2jmobius.gameserver.mechanics.effects.EffectType; +import org.l2jmobius.gameserver.mechanics.olympiad.Olympiad; +import org.l2jmobius.gameserver.mechanics.skill.AbnormalType; +import org.l2jmobius.gameserver.mechanics.skill.BuffInfo; +import org.l2jmobius.gameserver.mechanics.skill.EffectScope; +import org.l2jmobius.gameserver.mechanics.skill.Skill; +import org.l2jmobius.gameserver.mechanics.skill.enums.SkillFinishType; +import org.l2jmobius.gameserver.network.serverpackets.AbnormalStatusUpdate; +import org.l2jmobius.gameserver.network.serverpackets.ExOlympiadSpelledInfo; +import org.l2jmobius.gameserver.network.serverpackets.PartySpelled; +import org.l2jmobius.gameserver.network.serverpackets.ShortBuffStatusUpdate; + +/** + * Effect lists.
    + * Holds all the buff infos that are affecting a creature.
    + * Manages the logic that controls whether a buff is added, remove, replaced or set inactive.
    + * Uses maps with skill ID as key and buff info DTO as value to avoid iterations.
    + * Methods may resemble List interface, although it doesn't implement such interface. + * @author Zoey76, Mobius + */ +public class EffectList +{ + private static final Logger LOGGER = Logger.getLogger(EffectList.class.getName()); + /** Queue containing all effects from buffs for this effect list. */ + private final Queue _buffs = new ConcurrentLinkedQueue<>(); + /** Queue containing all dances/songs for this effect list. */ + private final Queue _dances = new ConcurrentLinkedQueue<>(); + /** Queue containing all toggle for this effect list. */ + private final Queue _toggles = new ConcurrentLinkedQueue<>(); + /** Queue containing all debuffs for this effect list. */ + private final Queue _debuffs = new ConcurrentLinkedQueue<>(); + /** Queue containing all passives for this effect list. They bypass most of the actions and they are not included in most operations. */ + private final Queue _passives = new ConcurrentLinkedQueue<>(); + /** Map containing the all stacked effect in progress for each abnormal type. */ + private final Map _stackedEffects = new ConcurrentHashMap<>(); + /** Set containing all {@code AbnormalType}s that shouldn't be added to this creature effect list. */ + private final Set _blockedAbnormalTypes = EnumSet.noneOf(AbnormalType.class); + /** Short buff skill ID. */ + private BuffInfo _shortBuff = null; + /** If {@code true} this effect list has buffs removed on any action. */ + private volatile boolean _hasBuffsRemovedOnAnyAction = false; + /** If {@code true} this effect list has buffs removed on damage. */ + private volatile boolean _hasBuffsRemovedOnDamage = false; + /** If {@code true} this effect list has debuffs removed on damage. */ + private volatile boolean _hasDebuffsRemovedOnDamage = false; + /** Effect flags. */ + private int _effectFlags; + /** If {@code true} only party icons need to be updated. */ + private boolean _partyOnly = false; + /** The owner of this effect list. */ + private final Creature _owner; + /** Hidden buffs count, prevents iterations. */ + private final AtomicInteger _hiddenBuffs = new AtomicInteger(); + /** Delay task **/ + private ScheduledFuture _updateEffectIconTask; + + /** + * Constructor for effect list. + * @param owner the creature that owns this effect list + */ + public EffectList(Creature owner) + { + _owner = owner; + } + + /** + * Gets buff skills. + * @return the buff skills + */ + public Queue getBuffs() + { + return _buffs; + } + + /** + * Gets dance/song skills. + * @return the dance/song skills + */ + public Queue getDances() + { + return _dances; + } + + /** + * Gets toggle skills. + * @return the toggle skills + */ + public Queue getToggles() + { + return _toggles; + } + + /** + * Gets debuff skills. + * @return the debuff skills + */ + public Queue getDebuffs() + { + return _debuffs; + } + + /** + * Gets passive skills. + * @return the passive skills + */ + public Queue getPassives() + { + return _passives; + } + + /** + * Gets all the effects on this effect list. + * @return all the effects on this effect list + */ + public List getEffects() + { + if (isEmpty()) + { + return Collections. emptyList(); + } + + final List buffs = new ArrayList<>(); + if (hasBuffs()) + { + buffs.addAll(_buffs); + } + + if (hasDances()) + { + buffs.addAll(_dances); + } + + if (hasToggles()) + { + buffs.addAll(_toggles); + } + + if (hasDebuffs()) + { + buffs.addAll(_debuffs); + } + + return buffs; + } + + /** + * Gets the effect list where the skill effects should be. + * @param skill the skill + * @return the effect list + */ + private Queue getEffectList(Skill skill) + { + if (skill == null) + { + return null; + } + + final Queue effects; + if (skill.isPassive()) + { + effects = _passives; + } + else if (skill.isDebuff()) + { + effects = _debuffs; + } + else if (skill.isDance()) + { + effects = _dances; + } + else if (skill.isToggle()) + { + effects = _toggles; + } + else + { + effects = _buffs; + } + + return effects; + } + + /** + * Gets the first effect for the given effect type.
    + * Prevents initialization.
    + * TODO: Remove this method after all the effect types gets replaced by abnormal skill types. + * @param type the effect type + * @return the first effect matching the given effect type + */ + public BuffInfo getFirstEffect(EffectType type) + { + if (hasBuffs()) + { + for (BuffInfo info : _buffs) + { + if (info != null) + { + for (AbstractEffect effect : info.getEffects()) + { + if ((effect != null) && (effect.getEffectType() == type)) + { + return info; + } + } + } + } + } + + if (hasDances()) + { + for (BuffInfo info : _dances) + { + if (info != null) + { + for (AbstractEffect effect : info.getEffects()) + { + if ((effect != null) && (effect.getEffectType() == type)) + { + return info; + } + } + } + } + } + + if (hasToggles()) + { + for (BuffInfo info : _toggles) + { + if (info != null) + { + for (AbstractEffect effect : info.getEffects()) + { + if ((effect != null) && (effect.getEffectType() == type)) + { + return info; + } + } + } + } + } + + if (hasDebuffs()) + { + for (BuffInfo info : _debuffs) + { + if (info != null) + { + for (AbstractEffect effect : info.getEffects()) + { + if ((effect != null) && (effect.getEffectType() == type)) + { + return info; + } + } + } + } + } + + return null; + } + + /** + * Verifies if this effect list contains the given skill ID.
    + * Prevents initialization. + * @param skillId the skill ID to verify + * @return {@code true} if the skill ID is present in the effect list, {@code false} otherwise + */ + public boolean isAffectedBySkill(int skillId) + { + return getBuffInfoBySkillId(skillId) != null; + } + + /** + * Gets the buff info by skill ID.
    + * Prevents initialization. + * @param skillId the skill ID + * @return the buff info + */ + public BuffInfo getBuffInfoBySkillId(int skillId) + { + BuffInfo info = null; + + if (hasBuffs()) + { + for (BuffInfo b : _buffs) + { + if (b.getSkill().getId() == skillId) + { + info = b; + break; + } + } + } + + if (hasDances() && (info == null)) + { + for (BuffInfo b : _dances) + { + if (b.getSkill().getId() == skillId) + { + info = b; + break; + } + } + } + + if (hasToggles() && (info == null)) + { + for (BuffInfo b : _toggles) + { + if (b.getSkill().getId() == skillId) + { + info = b; + break; + } + } + } + + if (hasDebuffs() && (info == null)) + { + for (BuffInfo b : _debuffs) + { + if (b.getSkill().getId() == skillId) + { + info = b; + break; + } + } + } + + if (hasPassives() && (info == null)) + { + for (BuffInfo b : _passives) + { + if (b.getSkill().getId() == skillId) + { + info = b; + break; + } + } + } + + return info; + } + + /** + * Gets a buff info by abnormal type.
    + * It's O(1) for every buff in this effect list. + * @param type the abnormal skill type + * @return the buff info if it's present, {@code null} otherwise + */ + public BuffInfo getBuffInfoByAbnormalType(AbnormalType type) + { + return _stackedEffects.get(type); + } + + /** + * Adds {@code AbnormalType}s to the blocked buff slot set. + * @param blockedAbnormalTypes the blocked buff slot set to add + */ + public void addBlockedAbnormalTypes(Set blockedAbnormalTypes) + { + _blockedAbnormalTypes.addAll(blockedAbnormalTypes); + } + + /** + * Removes {@code AbnormalType}s from the blocked buff slot set. + * @param blockedBuffSlots the blocked buff slot set to remove + * @return {@code true} if the blocked buff slots set has been modified, {@code false} otherwise + */ + public boolean removeBlockedAbnormalTypes(Set blockedBuffSlots) + { + return _blockedAbnormalTypes.removeAll(blockedBuffSlots); + } + + /** + * Gets all the blocked {@code AbnormalType}s for this creature effect list. + * @return the current blocked {@code AbnormalType}s set in unmodifiable view. + */ + public Set getBlockedAbnormalTypes() + { + return Collections.unmodifiableSet(_blockedAbnormalTypes); + } + + /** + * Gets the Short Buff info. + * @return the short buff info + */ + public BuffInfo getShortBuff() + { + return _shortBuff; + } + + /** + * Sets the Short Buff data and sends an update if the effected is a player. + * @param info the buff info + */ + public void shortBuffStatusUpdate(BuffInfo info) + { + if (_owner.isPlayer()) + { + _shortBuff = info; + if (info != null) + { + _owner.sendPacket(new ShortBuffStatusUpdate(info.getSkill().getId(), info.getSkill().getLevel(), info.getTime())); + } + else + { + _owner.sendPacket(ShortBuffStatusUpdate.RESET_SHORT_BUFF); + } + } + } + + /** + * Checks if the given skill stacks with an existing one. + * @param skill the skill to verify + * @return {@code true} if this effect stacks with the given skill, {@code false} otherwise + */ + private boolean doesStack(Skill skill) + { + final AbnormalType type = skill.getAbnormalType(); + if (type.isNone() || isEmpty()) + { + return false; + } + + final Queue effects = getEffectList(skill); + for (BuffInfo info : effects) + { + if ((info != null) && (info.getSkill().getAbnormalType() == type)) + { + return true; + } + } + + return false; + } + + /** + * Gets the buffs count without including the hidden buffs (after getting an Herb buff).
    + * Prevents initialization. + * @return the number of buffs in this creature effect list + */ + public int getBuffCount() + { + return hasBuffs() ? _buffs.size() - _hiddenBuffs.get() - (_shortBuff != null ? 1 : 0) : 0; + } + + /** + * Gets the Songs/Dances count.
    + * Prevents initialization. + * @return the number of Songs/Dances in this creature effect list + */ + public int getDanceCount() + { + return hasDances() ? _dances.size() : 0; + } + + /** + * Gets the hidden buff count. + * @return the number of hidden buffs + */ + public int getHiddenBuffsCount() + { + return _hiddenBuffs.get(); + } + + /** + * Auxiliary method to stop all effects from a buff info and remove it from an effect list and stacked effects. + * @param info the buff info + */ + protected void stopAndRemove(BuffInfo info) + { + stopAndRemove(true, SkillFinishType.REMOVED, info, getEffectList(info.getSkill())); + } + + /** + * Auxiliary method to stop all effects from a buff info and remove it from an effect list and stacked effects. + * @param broadcast if {@code true} broadcast abnormal visual effects + * @param info the buff info + * @param effects the effect list + */ + protected void stopAndRemove(boolean broadcast, BuffInfo info, Queue effects) + { + stopAndRemove(broadcast, SkillFinishType.REMOVED, info, effects); + } + + /** + * Auxiliary method to stop all effects from a buff info and remove it from an effect list and stacked effects. + * @param broadcast if {@code true} broadcast abnormal visual effects + * @param type determines the system message that will be sent. + * @param info the buff info + * @param buffs the buff list + */ + private void stopAndRemove(boolean broadcast, SkillFinishType type, BuffInfo info, Queue buffs) + { + if (info == null) + { + return; + } + + // Removes the buff from the given effect list. + buffs.remove(info); + + // Stop the buff effects. + info.stopAllEffects(type, broadcast); + + // If it's a hidden buff that ends, then decrease hidden buff count. + if (!info.isInUse()) + { + _hiddenBuffs.decrementAndGet(); + } + else // Removes the buff from the stack. + { + _stackedEffects.remove(info.getSkill().getAbnormalType()); + } + + // If it's an herb that ends, check if there are hidden buffs. + if (info.getSkill().isAbnormalInstant() && hasBuffs()) + { + for (BuffInfo buff : _buffs) + { + if ((buff != null) && (buff.getSkill().getAbnormalType() == info.getSkill().getAbnormalType()) && !buff.isInUse()) + { + // Sets the buff in use again. + buff.setInUse(true); + + // Adds the stats. + buff.addStats(); + + // Adds the buff to the stack. + _stackedEffects.put(buff.getSkill().getAbnormalType(), buff); + + // If it's a hidden buff that gets activated, then decrease hidden buff count. + _hiddenBuffs.decrementAndGet(); + break; + } + } + } + + if (type != SkillFinishType.REMOVED) + { + info.getSkill().applyEffectScope(EffectScope.END, info, true, false); + } + } + + /** + * Exits all effects in this effect list.
    + * Stops all the effects, clear the effect lists and updates the effect flags and icons. + */ + public void stopAllEffects() + { + // Stop buffs. + stopAllBuffs(false); + + // Stop dances and songs. + stopAllDances(false); + + // Stop toggles. + stopAllToggles(false); + + // Stop debuffs. + stopAllDebuffs(false); + + _stackedEffects.clear(); + + // Update effect flags, icons and ave. + updateEffectList(true); + _owner.updateAbnormalEffect(); + } + + /** + * Stops all effects in this effect list except those that last through death. + */ + public void stopAllEffectsExceptThoseThatLastThroughDeath() + { + boolean update = false; + if (hasBuffs()) + { + for (BuffInfo info : _buffs) + { + if (!info.getSkill().isStayAfterDeath()) + { + stopAndRemove(true, info, _buffs); + } + } + + update = true; + } + + if (hasDebuffs()) + { + for (BuffInfo info : _debuffs) + { + if (!info.getSkill().isStayAfterDeath()) + { + stopAndRemove(true, info, _debuffs); + } + } + + update = true; + } + + if (hasDances()) + { + for (BuffInfo info : _dances) + { + if (!info.getSkill().isStayAfterDeath()) + { + stopAndRemove(true, info, _dances); + } + } + + update = true; + } + + if (hasToggles()) + { + for (BuffInfo info : _toggles) + { + if (!info.getSkill().isStayAfterDeath()) + { + stopAndRemove(true, info, _toggles); + } + } + + update = true; + } + + // Update effect flags and icons. + updateEffectList(update); + } + + /** + * Exits all effects in this effect list without excluding anything.
    + * Stops all the effects, clear the effect lists and updates the effect flags and icons. + * @param update set to true to update the effect flags and icons. + * @param broadcast {@code true} to broadcast update packets, {@code false} otherwise. + */ + public void stopAllEffectsWithoutExclusions(boolean update, boolean broadcast) + { + for (BuffInfo info : _buffs) + { + stopAndRemove(broadcast, info, _buffs); + } + + for (BuffInfo info : _dances) + { + stopAndRemove(broadcast, info, _dances); + } + + for (BuffInfo info : _toggles) + { + stopAndRemove(broadcast, info, _toggles); + } + + for (BuffInfo info : _debuffs) + { + stopAndRemove(broadcast, info, _debuffs); + } + + for (BuffInfo info : _passives) + { + stopAndRemove(broadcast, info, _passives); + } + + // Update stats, effect flags and icons. + if (update) + { + updateEffectList(broadcast); + } + } + + /** + * Stop all effects that doesn't stay on sub-class change. + */ + public void stopAllEffectsNotStayOnSubclassChange() + { + boolean update = false; + if (hasBuffs()) + { + for (BuffInfo info : _buffs) + { + if (!info.getSkill().isStayOnSubclassChange()) + { + stopAndRemove(true, info, _buffs); + } + } + + update = true; + } + + if (hasDebuffs()) + { + for (BuffInfo info : _debuffs) + { + if (!info.getSkill().isStayOnSubclassChange()) + { + stopAndRemove(true, info, _debuffs); + } + } + + update = true; + } + + if (hasDances()) + { + for (BuffInfo info : _dances) + { + if (!info.getSkill().isStayOnSubclassChange()) + { + stopAndRemove(true, info, _dances); + } + } + + update = true; + } + + if (hasToggles()) + { + for (BuffInfo info : _toggles) + { + if (!info.getSkill().isStayOnSubclassChange()) + { + stopAndRemove(true, info, _toggles); + } + } + + update = true; + } + + // Update effect flags and icons. + updateEffectList(update); + } + + /** + * Stops all the active buffs. + * @param update set to true to update the effect flags and icons + */ + public void stopAllBuffs(boolean update) + { + if (hasBuffs()) + { + _buffs.forEach(b -> stopAndRemove(update, b, _buffs)); + } + + // Update effect flags and icons. + updateEffectList(update); + } + + /** + * Stops all active toggle skills.
    + * Performs an update. + */ + public void stopAllToggles() + { + stopAllToggles(true); + } + + /** + * Stops all active toggle skills. + * @param update set to true to update the effect flags and icons + */ + public void stopAllToggles(boolean update) + { + if (!hasToggles()) + { + return; + } + + _toggles.forEach(b -> stopAndRemove(update, b, _toggles)); + + // Update effect flags and icons. + updateEffectList(update); + } + + /** + * Stops all active dances/songs skills. + * @param update set to true to update the effect flags and icons + */ + public void stopAllDances(boolean update) + { + if (!hasDances()) + { + return; + } + + _dances.forEach(b -> stopAndRemove(update, b, _dances)); + + // Update effect flags and icons. + updateEffectList(update); + } + + /** + * Stops all active dances/songs skills. + * @param update set to true to update the effect flags and icons + */ + public void stopAllDebuffs(boolean update) + { + if (!hasDebuffs()) + { + return; + } + + _debuffs.forEach(b -> stopAndRemove(update, b, _debuffs)); + + // Update effect flags and icons. + updateEffectList(update); + } + + /** + * Exit all effects having a specified type.
    + * TODO: Remove after all effect types are replaced by abnormal skill types. + * @param type the type of the effect to stop + */ + public void stopEffects(EffectType type) + { + boolean update = false; + final Consumer action = info -> + { + for (AbstractEffect effect : info.getEffects()) + { + if ((effect != null) && (effect.getEffectType() == type)) + { + stopAndRemove(info); + } + } + }; + + if (hasBuffs()) + { + for (BuffInfo info : _buffs) + { + if (info != null) + { + action.accept(info); + } + } + + update = true; + } + + if (hasDances()) + { + for (BuffInfo info : _dances) + { + if (info != null) + { + action.accept(info); + } + } + + update = true; + } + + if (hasToggles()) + { + for (BuffInfo info : _toggles) + { + if (info != null) + { + action.accept(info); + } + } + + update = true; + } + + if (hasDebuffs()) + { + for (BuffInfo info : _debuffs) + { + if (info != null) + { + action.accept(info); + } + } + + update = true; + } + + // Update effect flags and icons. + updateEffectList(update); + } + + /** + * Exits all effects created by a specific skill ID.
    + * Removes the effects from the effect list.
    + * Removes the stats from the creature.
    + * Updates the effect flags and icons.
    + * @param type determines the system message that will be sent. + * @param skillId the skill ID + */ + public void stopSkillEffects(SkillFinishType type, int skillId) + { + final BuffInfo info = getBuffInfoBySkillId(skillId); + if (info != null) + { + remove(type, info); + } + } + + /** + * Exits all effects created by a specific skill.
    + * Removes the effects from the effect list.
    + * Removes the stats from the creature.
    + * Updates the effect flags and icons.
    + * @param type determines the system message that will be sent. + * @param skill the skill + */ + public void stopSkillEffects(SkillFinishType type, Skill skill) + { + if (skill != null) + { + stopSkillEffects(type, skill.getId()); + } + } + + /** + * Exits all effects created by a specific skill abnormal type.
    + * It's O(1) for every effect in this effect list except passive effects.
    + * @param removeType determines the system message that will be sent. + * @param abnormalType the skill abnormal type + * @return {@code true} if there was a buff info with the given abnormal type + */ + public boolean stopSkillEffects(SkillFinishType removeType, AbnormalType abnormalType) + { + final BuffInfo old = _stackedEffects.remove(abnormalType); + if (old != null) + { + stopSkillEffects(removeType, old.getSkill()); + return true; + } + + return false; + } + + /** + * Exits all buffs effects of the skills with "removedOnAnyAction" set.
    + * Called on any action except movement (attack, cast). + */ + public void stopEffectsOnAction() + { + if (!_hasBuffsRemovedOnAnyAction) + { + return; + } + + boolean update = false; + + if (hasBuffs()) + { + for (BuffInfo info : _buffs) + { + if (info.getSkill().isRemovedOnAnyActionExceptMove()) + { + stopAndRemove(true, info, _buffs); + } + } + + update = true; + } + + if (hasDebuffs()) + { + for (BuffInfo info : _debuffs) + { + if (info.getSkill().isRemovedOnAnyActionExceptMove()) + { + stopAndRemove(true, info, _debuffs); + } + } + + update = true; + } + + if (hasDances()) + { + for (BuffInfo info : _dances) + { + if (info.getSkill().isRemovedOnAnyActionExceptMove()) + { + stopAndRemove(true, info, _dances); + } + } + + update = true; + } + + if (hasToggles()) + { + for (BuffInfo info : _toggles) + { + if (info.getSkill().isRemovedOnAnyActionExceptMove()) + { + stopAndRemove(true, info, _toggles); + } + } + + update = true; + } + + // Update effect flags and icons. + updateEffectList(update); + } + + public void stopEffectsOnDamage(boolean awake) + { + if (!awake) + { + return; + } + + boolean update = false; + if (_hasBuffsRemovedOnDamage) + { + if (hasBuffs()) + { + for (BuffInfo info : _buffs) + { + if ((info != null) && info.getSkill().isRemovedOnDamage()) + { + stopAndRemove(true, info, _buffs); + } + } + + update = true; + } + + if (hasDances()) + { + for (BuffInfo info : _dances) + { + if ((info != null) && info.getSkill().isRemovedOnDamage()) + { + stopAndRemove(true, info, _dances); + } + } + + update = true; + } + + if (hasToggles()) + { + for (BuffInfo info : _toggles) + { + if ((info != null) && info.getSkill().isRemovedOnDamage()) + { + stopAndRemove(true, info, _toggles); + } + } + + update = true; + } + } + + if (_hasDebuffsRemovedOnDamage && hasDebuffs()) + { + for (BuffInfo info : _debuffs) + { + if ((info != null) && info.getSkill().isRemovedOnDamage()) + { + stopAndRemove(true, info, _debuffs); + } + } + + update = true; + } + + // Update effect flags and icons. + updateEffectList(update); + } + + /** + * @param partyOnly + */ + public void updateEffectIcons(boolean partyOnly) + { + if (partyOnly) + { + _partyOnly = true; + } + + // Update effect flags and icons. + updateEffectList(true); + } + + /** + * Verify if this effect list is empty.
    + * Prevents initialization. + * @return {@code true} if this effect list contains any skills + */ + public boolean isEmpty() + { + return !hasBuffs() && !hasDances() && !hasDebuffs() && !hasToggles(); + } + + /** + * Verify if this effect list has buffs skills.
    + * Prevents initialization. + * @return {@code true} if {@link #_buffs} is not {@code null} and is not empty + */ + public boolean hasBuffs() + { + return !_buffs.isEmpty(); + } + + /** + * Verify if this effect list has dance/song skills.
    + * Prevents initialization. + * @return {@code true} if {@link #_dances} is not {@code null} and is not empty + */ + public boolean hasDances() + { + return !_dances.isEmpty(); + } + + /** + * Verify if this effect list has toggle skills.
    + * Prevents initialization. + * @return {@code true} if {@link #_toggles} is not {@code null} and is not empty + */ + public boolean hasToggles() + { + return !_toggles.isEmpty(); + } + + /** + * Verify if this effect list has debuffs skills.
    + * Prevents initialization. + * @return {@code true} if {@link #_debuffs} is not {@code null} and is not empty + */ + public boolean hasDebuffs() + { + return !_debuffs.isEmpty(); + } + + /** + * Verify if this effect list has passive skills.
    + * Prevents initialization. + * @return {@code true} if {@link #_passives} is not {@code null} and is not empty + */ + public boolean hasPassives() + { + return !_passives.isEmpty(); + } + + /** + * Executes a procedure for all effects.
    + * Prevents initialization. + * @param function the function to execute + * @param dances if {@code true} dances/songs will be included + */ + public void forEach(Function function, boolean dances) + { + boolean update = false; + if (hasBuffs()) + { + for (BuffInfo info : _buffs) + { + update |= function.apply(info); + } + } + + if (dances && hasDances()) + { + for (BuffInfo info : _dances) + { + update |= function.apply(info); + } + } + + if (hasToggles()) + { + for (BuffInfo info : _toggles) + { + update |= function.apply(info); + } + } + + if (hasDebuffs()) + { + for (BuffInfo info : _debuffs) + { + update |= function.apply(info); + } + } + + // Update effect flags and icons. + updateEffectList(update); + } + + /** + * Removes a set of effects from this effect list. + * @param type determines the system message that will be sent. + * @param info the effects to remove + */ + public void remove(SkillFinishType type, BuffInfo info) + { + if (info == null) + { + return; + } + + // Remove the effect from creature effects. + stopAndRemove(true, type, info, getEffectList(info.getSkill())); + + // Update effect flags and icons. + updateEffectList(true); + } + + /** + * Adds a set of effects to this effect list. + * @param info the buff info + */ + public void add(BuffInfo info) + { + if (info == null) + { + return; + } + + // Support for blocked buff slots. + final Skill skill = info.getSkill(); + if (_blockedAbnormalTypes.contains(skill.getAbnormalType())) + { + return; + } + + // Passive effects are treated specially. + if (skill.isPassive()) + { + // Passive effects don't need stack type! + if (!skill.getAbnormalType().isNone()) + { + LOGGER.warning("Passive " + skill + " with abnormal type: " + skill.getAbnormalType() + "!"); + } + + // Check for passive skill conditions. + if (!skill.checkCondition(info.getEffector(), info.getEffected(), false)) + { + return; + } + + // Puts the effects in the list. + for (BuffInfo b : _passives) + { + // Removes the old stats from the creature if the skill was present. + if (b.getSkill().getId() == skill.getId()) + { + b.setInUse(false); + b.removeStats(); + _passives.remove(b); + } + } + + _passives.add(info); + + // Initialize effects. + info.initializeEffects(); + return; + } + + // Prevent adding and initializing buffs/effects on dead creatures. + if (info.getEffected().isDead()) + { + return; + } + + // The old effect is removed using Map#remove(key) instead of Map#put(key, value) (that would be the wisest choice), + // Because order matters and put method would insert in the same place it was before, instead of, at the end of the effect list + // Where new buff should be placed + if (skill.getAbnormalType().isNone()) + { + stopSkillEffects(SkillFinishType.NORMAL, skill); + } + else // Verify stacked skills. + { + synchronized (this) + { + if (_stackedEffects.containsKey(skill.getAbnormalType())) + { + BuffInfo stackedInfo = _stackedEffects.get(skill.getAbnormalType()); + + // Skills are only replaced if the incoming buff has greater or equal abnormal level. + if ((stackedInfo != null) && (skill.getAbnormalLevel() >= stackedInfo.getSkill().getAbnormalLevel())) + { + // If it is an herb, set as not in use the lesser buff. + // Effect will be present in the effect list. + // Effects stats are removed and onActionTime() is not called. + // But finish task continues to run, and ticks as well. + if (skill.isAbnormalInstant()) + { + if (stackedInfo.getSkill().isAbnormalInstant()) + { + stopSkillEffects(SkillFinishType.NORMAL, skill.getAbnormalType()); + stackedInfo = _stackedEffects.get(skill.getAbnormalType()); + } + + if (stackedInfo != null) + { + stackedInfo.setInUse(false); + + // Remove stats + stackedInfo.removeStats(); + _hiddenBuffs.incrementAndGet(); + } + } + else // Remove buff that will stack with the abnormal type. + { + if (stackedInfo.getSkill().isAbnormalInstant()) + { + stopSkillEffects(SkillFinishType.NORMAL, skill.getAbnormalType()); + } + + stopSkillEffects(SkillFinishType.NORMAL, skill.getAbnormalType()); + } + } + else // If the new buff is a lesser buff, then don't add it. + { + return; + } + } + + _stackedEffects.put(skill.getAbnormalType(), info); + } + } + + // Select the map that holds the effects related to this skill. + final Queue effects = getEffectList(skill); + + // Remove first buff when buff list is full. + if (!skill.isDebuff() && !skill.isToggle() && !skill.is7Signs() && !doesStack(skill)) + { + final int totalBuffs = getBuffCount() + getDanceCount(); + final int maxTotalBuffs = _owner.getStat().getMaxBuffCount(); + final int maxDances = PlayerConfig.DANCES_MAX_AMOUNT; + final int currentDances = getDanceCount(); + if (((totalBuffs >= maxTotalBuffs) && !skill.isHealingPotionSkill()) || (skill.isDance() && (currentDances >= maxDances))) + { + // New list counting buff and dances/song all together. + final List allEffects = new ArrayList<>(); + allEffects.addAll(_buffs); + allEffects.addAll(_dances); + + BuffInfo toRemove = null; + for (BuffInfo buffInfo : allEffects) + { + if (!buffInfo.isInUse()) + { + continue; + } + + // Check if limit of dance/song is exceeded. + if (skill.isDance() && (currentDances >= maxDances) && !buffInfo.getSkill().isDance()) + { + continue; + } + + // Check the oldest buff to be removed. + if ((toRemove == null) || (buffInfo.getPeriodStartTicks() < toRemove.getPeriodStartTicks())) + { + toRemove = buffInfo; + } + } + + if (toRemove != null) + { + final Queue list = getEffectList(toRemove.getSkill()); + if (list != null) + { + list.remove(toRemove); + stopAndRemove(true, toRemove, list); + } + } + } + } + + // After removing old buff (same ID) or stacked buff (same abnormal type), + // Add the buff to the end of the effect list. + effects.add(info); + + // Initialize effects. + info.initializeEffects(); + + // Update effect flags and icons. + updateEffectList(true); + } + + /** + * Update effect icons.
    + * Prevents initialization. + */ + private void updateEffectIcons() + { + if ((_owner == null) || !_owner.isPlayable()) + { + return; + } + + updateEffectFlags(); + + if (_updateEffectIconTask == null) + { + _updateEffectIconTask = ThreadPool.schedule(() -> + { + AbnormalStatusUpdate asu = null; + PartySpelled ps = null; + ExOlympiadSpelledInfo os = null; + boolean isSummon = false; + + if (_owner.isPlayer()) + { + if (_partyOnly) + { + _partyOnly = false; + } + else + { + asu = new AbnormalStatusUpdate(); + } + + if (_owner.isInParty()) + { + ps = new PartySpelled(_owner); + } + + final Player player = _owner.asPlayer(); + if (player.isInOlympiadMode() && player.isOlympiadStart()) + { + os = new ExOlympiadSpelledInfo(player); + } + } + else if (_owner.isSummon()) + { + isSummon = true; + ps = new PartySpelled(_owner); + } + + // Buffs. + if (hasBuffs()) + { + for (BuffInfo info : _buffs) + { + if (info.getSkill().isHealingPotionSkill()) + { + shortBuffStatusUpdate(info); + } + else + { + addIcon(info, asu, ps, os, isSummon); + } + } + } + + // Songs and dances. + if (hasDances()) + { + for (BuffInfo info : _dances) + { + addIcon(info, asu, ps, os, isSummon); + } + } + + // Toggles. + if (hasToggles()) + { + for (BuffInfo info : _toggles) + { + addIcon(info, asu, ps, os, isSummon); + } + } + + // Debuffs. + if (hasDebuffs()) + { + for (BuffInfo info : _debuffs) + { + addIcon(info, asu, ps, os, isSummon); + } + } + + if (asu != null) + { + _owner.sendPacket(asu); + } + + if (ps != null) + { + if (_owner.isSummon()) + { + final Player summonOwner = _owner.asSummon().getOwner(); + if (summonOwner != null) + { + summonOwner.sendPacket(ps); + } + } + else if (_owner.isPlayer() && _owner.isInParty()) + { + _owner.getParty().broadcastPacket(ps); + } + } + + if (os != null) + { + final List specs = Olympiad.getInstance().getSpectators(_owner.asPlayer().getOlympiadGameId()); + if ((specs != null) && !specs.isEmpty()) + { + for (Player spec : specs) + { + if (spec != null) + { + spec.sendPacket(os); + } + } + } + } + + _updateEffectIconTask = null; + }, 300); + } + } + + private void addIcon(BuffInfo info, AbnormalStatusUpdate asu, PartySpelled ps, ExOlympiadSpelledInfo os, boolean isSummon) + { + // Avoid null and not in use buffs. + if ((info == null) || !info.isInUse()) + { + return; + } + + final Skill skill = info.getSkill(); + if (asu != null) + { + asu.addSkill(info); + } + + if ((ps != null) && (isSummon || !skill.isToggle())) + { + ps.addSkill(info); + } + + if (os != null) + { + os.addSkill(info); + } + } + + /** + * Wrapper to update abnormal icons and effect flags. + * @param update if {@code true} performs an update + */ + private void updateEffectList(boolean update) + { + if (update) + { + updateEffectIcons(); + computeEffectFlags(); + } + } + + /** + * Updates effect flags.
    + * TODO: Rework it to update in real time (add/remove/stop/activate/deactivate operations) and avoid iterations. + */ + private void updateEffectFlags() + { + if (hasBuffs()) + { + for (BuffInfo info : _buffs) + { + if (info == null) + { + continue; + } + + if (info.getSkill().isRemovedOnAnyActionExceptMove()) + { + _hasBuffsRemovedOnAnyAction = true; + } + + if (info.getSkill().isRemovedOnDamage()) + { + _hasBuffsRemovedOnDamage = true; + } + } + } + + if (hasToggles()) + { + for (BuffInfo info : _toggles) + { + if (info == null) + { + continue; + } + + if (info.getSkill().isRemovedOnAnyActionExceptMove()) + { + _hasBuffsRemovedOnAnyAction = true; + } + + if (info.getSkill().isRemovedOnDamage()) + { + _hasBuffsRemovedOnDamage = true; + } + } + } + + if (hasDebuffs()) + { + for (BuffInfo info : _debuffs) + { + if ((info != null) && info.getSkill().isRemovedOnDamage()) + { + _hasDebuffsRemovedOnDamage = true; + } + } + } + } + + /** + * Recalculate effect bits flag.
    + * TODO: Rework to update in real time and avoid iterations. + */ + private void computeEffectFlags() + { + int flags = 0; + if (hasBuffs()) + { + for (BuffInfo info : _buffs) + { + if (info != null) + { + for (AbstractEffect e : info.getEffects()) + { + flags |= e.getEffectFlags(); + } + } + } + } + + if (hasDebuffs()) + { + for (BuffInfo info : _debuffs) + { + if (info != null) + { + for (AbstractEffect e : info.getEffects()) + { + flags |= e.getEffectFlags(); + } + } + } + } + + if (hasDances()) + { + for (BuffInfo info : _dances) + { + if (info != null) + { + for (AbstractEffect e : info.getEffects()) + { + flags |= e.getEffectFlags(); + } + } + } + } + + if (hasToggles()) + { + for (BuffInfo info : _toggles) + { + if (info != null) + { + for (AbstractEffect e : info.getEffects()) + { + flags |= e.getEffectFlags(); + } + } + } + } + + _effectFlags = flags; + } + + /** + * Check if target is affected with special buff + * @param flag of special buff + * @return boolean true if affected + */ + public boolean isAffected(EffectFlag flag) + { + return (_effectFlags & flag.getMask()) != 0; + } + + /** + * Check if target is affected by AbnormalType. + * @param type the AbnormalType + * @return boolean true if affected + */ + public boolean isAffectedByAbnormalType(AbnormalType type) + { + return _stackedEffects.containsKey(type); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/creature/Hit.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/creature/Hit.java new file mode 100644 index 0000000..e094b3c --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/creature/Hit.java @@ -0,0 +1,75 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.actor.holders.creature; + +import org.l2jmobius.gameserver.entity.WorldObject; + +/** + * @author UnAfraid + */ +public class Hit +{ + private static final int HITFLAG_USESS = 0x10; + private static final int HITFLAG_CRIT = 0x20; + private static final int HITFLAG_SHLD = 0x40; + private static final int HITFLAG_MISS = 0x80; + + private final int _targetId; + private final int _damage; + private int _flags = 0; + + public Hit(WorldObject target, int damage, boolean miss, boolean crit, byte shld, boolean soulshot, int ssGrade) + { + _targetId = target.getObjectId(); + _damage = damage; + + if (soulshot) + { + _flags |= HITFLAG_USESS | ssGrade; + } + + if (crit) + { + _flags |= HITFLAG_CRIT; + } + + if (shld > 0) + { + _flags |= HITFLAG_SHLD; + } + + if (miss) + { + _flags |= HITFLAG_MISS; + } + } + + public int getTargetId() + { + return _targetId; + } + + public int getDamage() + { + return _damage; + } + + public int getFlags() + { + return _flags; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/creature/InvulSkillHolder.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/creature/InvulSkillHolder.java new file mode 100644 index 0000000..841934b --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/creature/InvulSkillHolder.java @@ -0,0 +1,54 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.actor.holders.creature; + +import java.util.concurrent.atomic.AtomicInteger; + +import org.l2jmobius.gameserver.mechanics.skill.holders.SkillHolder; + +/** + * @author UnAfraid + */ +public class InvulSkillHolder extends SkillHolder +{ + private final AtomicInteger _instances = new AtomicInteger(1); + + public InvulSkillHolder(int skillId, int skillLevel) + { + super(skillId, skillLevel); + } + + public InvulSkillHolder(SkillHolder holder) + { + super(holder.getSkill()); + } + + public int getInstances() + { + return _instances.get(); + } + + public int increaseInstances() + { + return _instances.incrementAndGet(); + } + + public int decreaseInstances() + { + return _instances.decrementAndGet(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/creature/TimeStamp.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/creature/TimeStamp.java new file mode 100644 index 0000000..7e72b66 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/creature/TimeStamp.java @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.holders.creature; + +import org.l2jmobius.gameserver.entity.item.instance.Item; +import org.l2jmobius.gameserver.mechanics.skill.Skill; + +/** + * Simple class containing all necessary information to maintain
    + * valid time stamps and reuse for skills and items reuse upon re-login.
    + * Filter this carefully as it becomes redundant to store reuse for small delays. + * @author Yesod, Zoey76, Mobius + */ +public class TimeStamp +{ + /** Item or skill ID. */ + private final int _id1; + /** Item object ID or skill level. */ + private final int _id2; + /** Item or skill reuse time. */ + private final long _reuse; + /** Time stamp. */ + private volatile long _stamp; + /** Shared reuse group. */ + private final int _group; + + /** + * Skill time stamp constructor. + * @param skill the skill upon the stamp will be created. + * @param reuse the reuse time for this skill. + * @param systime overrides the system time with a customized one. + */ + public TimeStamp(Skill skill, long reuse, long systime) + { + _id1 = skill.getId(); + _id2 = skill.getLevel(); + _reuse = reuse; + _stamp = systime > 0 ? systime : reuse != 0 ? System.currentTimeMillis() + reuse : 0; + _group = -1; + } + + /** + * Item time stamp constructor. + * @param item the item upon the stamp will be created. + * @param reuse the reuse time for this item. + * @param systime overrides the system time with a customized one. + */ + public TimeStamp(Item item, long reuse, long systime) + { + _id1 = item.getId(); + _id2 = item.getObjectId(); + _reuse = reuse; + _stamp = systime > 0 ? systime : reuse != 0 ? System.currentTimeMillis() + reuse : 0; + _group = item.getSharedReuseGroup(); + } + + /** + * Gets the time stamp. + * @return the time stamp, either the system time where this time stamp was created or the custom time assigned + */ + public long getStamp() + { + return _stamp; + } + + /** + * Gets the item ID. + * @return the item ID + */ + public int getItemId() + { + return _id1; + } + + /** + * Gets the item object ID. + * @return the item object ID + */ + public int getItemObjectId() + { + return _id2; + } + + /** + * Gets the skill ID. + * @return the skill ID + */ + public int getSkillId() + { + return _id1; + } + + /** + * Gets the skill level. + * @return the skill level + */ + public int getSkillLevel() + { + return _id2; + } + + /** + * Gets the reuse. + * @return the reuse + */ + public long getReuse() + { + return _reuse; + } + + /** + * Get the shared reuse group.
    + * Only used on items. + * @return the shared reuse group + */ + public int getSharedReuseGroup() + { + return _group; + } + + /** + * Gets the remaining time. + * @return the remaining time for this time stamp to expire + */ + public long getRemaining() + { + if (_stamp == 0) + { + return 0; + } + + final long remainingTime = _stamp - System.currentTimeMillis(); + if (remainingTime <= 0) + { + _stamp = 0; + return 0; + } + + return remainingTime; + } + + /** + * Verifies if the reuse delay has passed. + * @return {@code true} if this time stamp has expired, {@code false} otherwise + */ + public boolean hasNotPassed() + { + if (_stamp == 0) + { + return false; + } + + if (System.currentTimeMillis() >= _stamp) + { + _stamp = 0; + return false; + } + + return true; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/creature/VehiclePathPoint.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/creature/VehiclePathPoint.java new file mode 100644 index 0000000..41b4a1e --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/creature/VehiclePathPoint.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.holders.creature; + +import org.l2jmobius.gameserver.entity.Location; + +public class VehiclePathPoint extends Location +{ + private final int _moveSpeed; + private final int _rotationSpeed; + + public VehiclePathPoint(int x, int y, int z) + { + super(x, y, z); + _moveSpeed = 350; + _rotationSpeed = 4000; + } + + public VehiclePathPoint(int x, int y, int z, int moveSpeed, int rotationSpeed) + { + super(x, y, z); + _moveSpeed = moveSpeed; + _rotationSpeed = rotationSpeed; + } + + public int getMoveSpeed() + { + return _moveSpeed; + } + + public int getRotationSpeed() + { + return _rotationSpeed; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/npc/AbsorberInfo.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/npc/AbsorberInfo.java new file mode 100644 index 0000000..8df6c73 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/npc/AbsorberInfo.java @@ -0,0 +1,64 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.actor.holders.npc; + +/** + * @author xban1x + */ +public class AbsorberInfo +{ + private int _objectId; + private double _absorbedHp; + + public AbsorberInfo(int objectId, double pAbsorbedHp) + { + _objectId = objectId; + _absorbedHp = pAbsorbedHp; + } + + public double getAbsorbedHp() + { + return _absorbedHp; + } + + public void setAbsorbedHp(double absorbedHp) + { + _absorbedHp = absorbedHp; + } + + public int getObjectId() + { + return _objectId; + } + + public void setObjectId(int objectId) + { + _objectId = objectId; + } + + @Override + public boolean equals(Object obj) + { + return (this == obj) || ((obj instanceof AbsorberInfo) && (((AbsorberInfo) obj).getObjectId() == _objectId)); + } + + @Override + public int hashCode() + { + return _objectId; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/npc/AggroInfo.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/npc/AggroInfo.java new file mode 100644 index 0000000..84a69cf --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/npc/AggroInfo.java @@ -0,0 +1,246 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.holders.npc; + +import org.l2jmobius.gameserver.entity.actor.Creature; + +/** + * Holds aggro-related information for a single aggressor against an NPC. + *

    + * This holder tracks two independent counters: + *

    + *
      + *
    • Hate: used for target selection / threat calculations.
    • + *
    • Damage: used for contribution tracking and auxiliary logic.
    • + *
    + *

    + * Values are capped to avoid uncontrolled growth. This class is intentionally lightweight and mutable. Concurrency control, if needed, should be handled by the owner container (e.g. the aggro map in {@code Attackable}). + *

    + * @author BazookaRpm + */ +public class AggroInfo +{ + /** + * Upper cap applied to accumulated hate/damage. + */ + private static final long LIMIT = 1000000000000000L; + + /** + * The aggressor (attacker) this aggro entry belongs to. + */ + private final Creature _aggressor; + + /** + * Total accumulated damage credited to the aggressor. + */ + private long _damageTotal; + + /** + * Total accumulated hate credited to the aggressor. + */ + private long _hateTotal; + + /** + * Creates a new aggro entry bound to a specific aggressor. + * @param aggressor the attacker for this entry + */ + public AggroInfo(Creature aggressor) + { + _aggressor = aggressor; + _damageTotal = 0; + _hateTotal = 0; + } + + /** + * Returns the aggressor this entry belongs to. + * @return the aggressor + */ + public Creature getAttacker() + { + return _aggressor; + } + + /** + * Returns the total damage accumulated for this aggressor. + * @return the accumulated damage + */ + public long getDamage() + { + return _damageTotal; + } + + /** + * Adds damage to this entry. + *

    + * Positive values are capped to {@link #LIMIT}. Negative values are applied as-is. + *

    + * @param value the damage delta to add + */ + public void addDamage(long value) + { + if (value == 0) + { + return; + } + + if (value > 0) + { + final long current = _damageTotal; + _damageTotal = (current >= (LIMIT - value)) ? LIMIT : (current + value); + return; + } + + _damageTotal += value; + } + + /** + * Returns the current hate accumulated for this aggressor. + * @return the accumulated hate + */ + public long getHate() + { + return _hateTotal; + } + + /** + * Adds hate to this entry. + *

    + * Positive values are capped to {@link #LIMIT}. Negative values are applied as-is. + *

    + * @param value the hate delta to add + */ + public void addHate(long value) + { + if (value == 0) + { + return; + } + + if (value > 0) + { + final long current = _hateTotal; + _hateTotal = (current >= (LIMIT - value)) ? LIMIT : (current + value); + return; + } + + _hateTotal += value; + } + + /** + * Resets hate to zero. + */ + public void stopHate() + { + _hateTotal = 0; + } + + /** + * Validates whether the stored hate should remain active for the current owner context. + *

    + * Hate is reset when: + *

    + *
      + *
    • Owner or aggressor is null.
    • + *
    • Aggressor is dead or not spawned.
    • + *
    • Aggressor is outside the owner's surrounding region.
    • + *
    + * @param owner the NPC/creature owning this aggro list + * @return the current hate value after validation + */ + public long checkHate(Creature owner) + { + final Creature attacker = _aggressor; + if ((attacker == null) || (owner == null)) + { + _hateTotal = 0; + return 0; + } + + if (attacker.isAlikeDead()) + { + _hateTotal = 0; + return 0; + } + + if (!attacker.isSpawned()) + { + _hateTotal = 0; + return 0; + } + + if (!owner.isInSurroundingRegion(attacker)) + { + _hateTotal = 0; + } + + return _hateTotal; + } + + /** + * Hash is based on the aggressor object id. + * @return the aggressor object id, or {@code 0} if aggressor is null + */ + @Override + public int hashCode() + { + final Creature c = _aggressor; + return (c != null) ? c.getObjectId() : 0; + } + + /** + * Equality is based on aggressor identity (same instance). + * @param other the object to compare with + * @return {@code true} if both entries reference the same aggressor instance + */ + @Override + public boolean equals(Object other) + { + if (other == this) + { + return true; + } + + if (!(other instanceof AggroInfo)) + { + return false; + } + + return ((AggroInfo) other)._aggressor == _aggressor; + } + + /** + * Debug representation of this aggro entry. + * @return a human-readable representation of aggressor, hate and damage + */ + @Override + public String toString() + { + final StringBuilder sb = new StringBuilder(96); + sb.append("AggroInfo [attacker="); + sb.append(_aggressor); + sb.append(", hate="); + sb.append(_hateTotal); + sb.append(", damage="); + sb.append(_damageTotal); + sb.append(']'); + return sb.toString(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/npc/BuffSkillHolder.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/npc/BuffSkillHolder.java new file mode 100644 index 0000000..3fa5f5c --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/npc/BuffSkillHolder.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.holders.npc; + +/** + * A container used for schemes buffer. + */ +public class BuffSkillHolder +{ + private final int _id; + private final int _level; + private final int _price; + private final String _type; + private final String _description; + + public BuffSkillHolder(int id, int level, int price, String type, String description) + { + _id = id; + _level = level; + _price = price; + _type = type; + _description = description; + } + + public int getId() + { + return _id; + } + + public int getLevel() + { + return _level; + } + + public int getPrice() + { + return _price; + } + + public String getType() + { + return _type; + } + + public String getDescription() + { + return _description; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/npc/DamageDoneInfo.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/npc/DamageDoneInfo.java new file mode 100644 index 0000000..a8be0d3 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/npc/DamageDoneInfo.java @@ -0,0 +1,60 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.actor.holders.npc; + +import org.l2jmobius.gameserver.entity.actor.Player; + +/** + * @author xban1x + */ +public class DamageDoneInfo +{ + private final Player _attacker; + private long _damage = 0; + + public DamageDoneInfo(Player attacker) + { + _attacker = attacker; + } + + public Player getAttacker() + { + return _attacker; + } + + public void addDamage(long damage) + { + _damage += damage; + } + + public long getDamage() + { + return _damage; + } + + @Override + public boolean equals(Object obj) + { + return (this == obj) || ((obj instanceof DamageDoneInfo) && (((DamageDoneInfo) obj).getAttacker() == _attacker)); + } + + @Override + public int hashCode() + { + return _attacker.getObjectId(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/npc/DropGroupHolder.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/npc/DropGroupHolder.java new file mode 100644 index 0000000..8d2e52c --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/npc/DropGroupHolder.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.holders.npc; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author Mobius + */ +public class DropGroupHolder +{ + private final List _dropList = new ArrayList<>(); + private final double _chance; + + public DropGroupHolder(double chance) + { + _chance = chance; + } + + public List getDropList() + { + return _dropList; + } + + public void addDrop(DropHolder holder) + { + _dropList.add(holder); + } + + public double getChance() + { + return _chance; + } + + /** + * Sorts the drop list by chance in ascending order (low to high). + */ + public void sortByChance() + { + _dropList.sort((d1, d2) -> Double.compare(d1.getChance(), d2.getChance())); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/npc/DropHolder.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/npc/DropHolder.java new file mode 100644 index 0000000..c1ccc92 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/npc/DropHolder.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.holders.npc; + +import org.l2jmobius.gameserver.entity.actor.enums.npc.DropType; + +/** + * @author Mobius + */ +public class DropHolder +{ + private final DropType _dropType; + private final int _itemId; + private final int _min; + private final int _max; + private final double _chance; + + public DropHolder(DropType dropType, int itemId, int min, int max, double chance) + { + _dropType = dropType; + _itemId = itemId; + _min = min; + _max = max; + _chance = chance; + } + + public DropType getDropType() + { + return _dropType; + } + + public int getItemId() + { + return _itemId; + } + + public int getMin() + { + return _min; + } + + public int getMax() + { + return _max; + } + + public double getChance() + { + return _chance; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/npc/EventDropHolder.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/npc/EventDropHolder.java new file mode 100644 index 0000000..6fbf288 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/npc/EventDropHolder.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.holders.npc; + +import java.util.Set; + +/** + * @author Mobius + */ +public class EventDropHolder extends DropHolder +{ + private final int _minLevel; + private final int _maxLevel; + private final Set _monsterIds; + + public EventDropHolder(int itemId, int min, int max, double chance, int minLevel, int maxLevel, Set monsterIds) + { + super(null, itemId, min, max, chance); + _minLevel = minLevel; + _maxLevel = maxLevel; + _monsterIds = monsterIds; + } + + public int getMinLevel() + { + return _minLevel; + } + + public int getMaxLevel() + { + return _maxLevel; + } + + public Set getMonsterIds() + { + return _monsterIds; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/npc/FakePlayerHolder.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/npc/FakePlayerHolder.java new file mode 100644 index 0000000..b12fd32 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/npc/FakePlayerHolder.java @@ -0,0 +1,270 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.holders.npc; + +import org.l2jmobius.gameserver.data.xml.FakePlayerData; +import org.l2jmobius.gameserver.entity.actor.enums.player.PlayerClass; +import org.l2jmobius.gameserver.util.StatSet; + +/** + * @author Mobius + */ +public class FakePlayerHolder +{ + private final PlayerClass _playerClass; + private final int _hair; + private final int _hairColor; + private final int _face; + private final int _nameColor; + private final int _titleColor; + private final int _equipHead; + private final int _equipRHand; + private final int _equipLHand; + private final int _equipGloves; + private final int _equipChest; + private final int _equipLegs; + private final int _equipFeet; + private final int _equipCloak; + private final int _equipHair; + private final int _equipHair2; + private final int _agathionId; + private final int _weaponEnchantLevel; + private final int _armorEnchantLevel; + private final boolean _fishing; + private final int _baitLocationX; + private final int _baitLocationY; + private final int _baitLocationZ; + private final int _recommends; + private final int _nobleLevel; + private final boolean _hero; + private final int _clanId; + private final int _pledgeStatus; + private final boolean _isSitting; + private final int _privateStoreType; + private final String _privateStoreMessage; + private final boolean _talkable; + + public FakePlayerHolder(StatSet set) + { + _playerClass = PlayerClass.getPlayerClass(set.getInt("classId", 1)); + _hair = set.getInt("hair", 1); + _hairColor = set.getInt("hairColor", 1); + _face = set.getInt("face", 1); + _nameColor = set.getInt("nameColor", 0xFFFFFF); + _titleColor = set.getInt("titleColor", 0xECF9A2); + _equipHead = set.getInt("equipHead", 0); + _equipRHand = set.getInt("equipRHand", 0); // Or dual hand. + _equipLHand = set.getInt("equipLHand", 0); + _equipGloves = set.getInt("equipGloves", 0); + _equipChest = set.getInt("equipChest", 0); + _equipLegs = set.getInt("equipLegs", 0); + _equipFeet = set.getInt("equipFeet", 0); + _equipCloak = set.getInt("equipCloak", 0); + _equipHair = set.getInt("equipHair", 0); + _equipHair2 = set.getInt("equipHair2", 0); + _agathionId = set.getInt("agathionId", 0); + _weaponEnchantLevel = set.getInt("weaponEnchantLevel", 0); + _armorEnchantLevel = set.getInt("armorEnchantLevel", 0); + _fishing = set.getBoolean("fishing", false); + _baitLocationX = set.getInt("baitLocationX", 0); + _baitLocationY = set.getInt("baitLocationY", 0); + _baitLocationZ = set.getInt("baitLocationZ", 0); + _recommends = set.getInt("recommends", 0); + _nobleLevel = set.getInt("nobleLevel", 0); + _hero = set.getBoolean("hero", false); + _clanId = set.getInt("clanId", 0); + _pledgeStatus = set.getInt("pledgeStatus", 0); + _isSitting = set.getBoolean("sitting", false); + _privateStoreType = set.getInt("privateStoreType", 0); + _privateStoreMessage = set.getString("privateStoreMessage", ""); + _talkable = set.getBoolean("fakePlayerTalkable", true); + + // Populate FakePlayerData mappings. + final String name = set.getString("name", ""); + FakePlayerData.getInstance().addFakePlayerId(name, set.getInt("id", 0)); // Map name to npcId. + final String lowercaseName = name.toLowerCase(); + FakePlayerData.getInstance().addFakePlayerName(lowercaseName, name); // Map lowercase name to original name. + if (_talkable) + { + FakePlayerData.getInstance().addTalkableFakePlayerName(lowercaseName); + } + } + + public PlayerClass getPlayerClass() + { + return _playerClass; + } + + public int getHair() + { + return _hair; + } + + public int getHairColor() + { + return _hairColor; + } + + public int getFace() + { + return _face; + } + + public int getNameColor() + { + return _nameColor; + } + + public int getTitleColor() + { + return _titleColor; + } + + public int getEquipHead() + { + return _equipHead; + } + + public int getEquipRHand() + { + return _equipRHand; + } + + public int getEquipLHand() + { + return _equipLHand; + } + + public int getEquipGloves() + { + return _equipGloves; + } + + public int getEquipChest() + { + return _equipChest; + } + + public int getEquipLegs() + { + return _equipLegs; + } + + public int getEquipFeet() + { + return _equipFeet; + } + + public int getEquipCloak() + { + return _equipCloak; + } + + public int getEquipHair() + { + return _equipHair; + } + + public int getEquipHair2() + { + return _equipHair2; + } + + public int getAgathionId() + { + return _agathionId; + } + + public int getWeaponEnchantLevel() + { + return _weaponEnchantLevel; + } + + public int getArmorEnchantLevel() + { + return _armorEnchantLevel; + } + + public boolean isFishing() + { + return _fishing; + } + + public int getBaitLocationX() + { + return _baitLocationX; + } + + public int getBaitLocationY() + { + return _baitLocationY; + } + + public int getBaitLocationZ() + { + return _baitLocationZ; + } + + public int getRecommends() + { + return _recommends; + } + + public int getNobleLevel() + { + return _nobleLevel; + } + + public boolean isHero() + { + return _hero; + } + + public int getClanId() + { + return _clanId; + } + + public int getPledgeStatus() + { + return _pledgeStatus; + } + + public boolean isSitting() + { + return _isSitting; + } + + public int getPrivateStoreType() + { + return _privateStoreType; + } + + public String getPrivateStoreMessage() + { + return _privateStoreMessage; + } + + public boolean isTalkable() + { + return _talkable; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/npc/MinionHolder.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/npc/MinionHolder.java new file mode 100644 index 0000000..8387e1c --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/npc/MinionHolder.java @@ -0,0 +1,86 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.actor.holders.npc; + +import org.l2jmobius.commons.util.Rnd; + +/** + * This class hold info needed for minions spawns
    + * @author Zealar + */ +public class MinionHolder +{ + private final int _id; + private final int _count; + private final int _max; + private final long _respawnTime; + private final int _weightPoint; + + /** + * Constructs a minion holder. + * @param id the id + * @param count the count + * @param max the max count + * @param respawnTime the respawn time + * @param weightPoint the weight point + */ + public MinionHolder(int id, int count, int max, long respawnTime, int weightPoint) + { + _id = id; + _count = count; + _max = max; + _respawnTime = respawnTime; + _weightPoint = weightPoint; + } + + /** + * @return the Identifier of the Minion to spawn. + */ + public int getId() + { + return _id; + } + + /** + * @return the count of the Minions to spawn. + */ + public int getCount() + { + if (_max > _count) + { + return Rnd.get(_count, _max); + } + + return _count; + } + + /** + * @return the respawn time of the Minions. + */ + public long getRespawnTime() + { + return _respawnTime; + } + + /** + * @return the weight point of the Minion. + */ + public int getWeightPoint() + { + return _weightPoint; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/npc/MinionList.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/npc/MinionList.java new file mode 100644 index 0000000..9c49927 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/npc/MinionList.java @@ -0,0 +1,426 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.holders.npc; + +import java.util.Collection; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ScheduledFuture; + +import org.l2jmobius.commons.threads.ThreadPool; +import org.l2jmobius.commons.util.Rnd; +import org.l2jmobius.gameserver.config.NpcConfig; +import org.l2jmobius.gameserver.data.xml.NpcData; +import org.l2jmobius.gameserver.entity.Location; +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.instance.Monster; +import org.l2jmobius.gameserver.entity.actor.templates.NpcTemplate; + +/** + * Manages minion spawning, tracking and lifecycle for monster masters.
    + * Handles automatic minion spawning, respawning, combat assistance and teleportation synchronization. + *
      + *
    • Spawns minions based on master configuration and tracks alive minions.
    • + *
    • Manages minion respawning with configurable timers for raids and regular monsters.
    • + *
    • Coordinates combat assistance between master and minions during attacks.
    • + *
    • Synchronizes minion positions when master teleports to maintain formation.
    • + *
    + * @author luisantonioa, DS, Mobius + */ +public class MinionList +{ + // Constants. + private static final int SPAWN_OFFSET_RADIUS = 200; + private static final int MINIMUM_SPAWN_RADIUS = 30; + private static final int BASE_AGGRO_AMOUNT = 1; + private static final int MASTER_AGGRO_AMOUNT = 10; + private static final int RAID_AGGRO_MULTIPLIER = 10; + private static final int INVALID_RESPAWN_TIME = -1; + private static final int NO_RESPAWN = 0; + + // Master Monster Management. + protected final Monster _master; + + // Minion Tracking. + private final Set _spawnedMinions = ConcurrentHashMap.newKeySet(); + private final Set> _respawnTasks = ConcurrentHashMap.newKeySet(); + + /** + * Creates a new minion list for the specified master monster. + * @param master the monster that will control the minions + */ + public MinionList(Monster master) + { + if (master == null) + { + throw new NullPointerException("MinionList: Master monster cannot be null!"); + } + + _master = master; + } + + /** + * Gets the list of currently spawned and alive minions. + * @return list of spawned minions + */ + public Collection getSpawnedMinions() + { + return _spawnedMinions; + } + + /** + * Spawns all required minions based on the master's minion configuration.
    + * Only spawns minions that are missing to reach the required count for each minion type. + * @param minions the minion configuration holders specifying types and counts + */ + public void spawnMinions(Collection minions) + { + if (_master.isAlikeDead() || (minions == null)) + { + return; + } + + int minionCount; + int minionId; + int minionsToSpawn; + for (MinionHolder minion : minions) + { + minionCount = minion.getCount(); + minionId = minion.getId(); + minionsToSpawn = minionCount - countSpawnedMinionsById(minionId); + if (minionsToSpawn > 0) + { + for (int i = 0; i < minionsToSpawn; i++) + { + spawnMinion(minionId); + } + } + } + } + + /** + * Registers a newly spawned minion to the tracking list. + * @param minion the minion that was spawned + */ + public void onMinionSpawn(Monster minion) + { + _spawnedMinions.add(minion); + } + + /** + * Handles master death by cleaning up minions and respawn tasks.
    + * Minions are deleted for raid bosses or when forced, otherwise they remain alive. + * @param force when true, forces deletion of spawned minions regardless of master type + */ + public void onMasterDie(boolean force) + { + if (_master.isRaid() || force || NpcConfig.FORCE_DELETE_MINIONS) + { + if (!_spawnedMinions.isEmpty()) + { + for (Monster minion : _spawnedMinions) + { + if (minion != null) + { + minion.setLeader(null); + minion.deleteMe(); + } + } + + _spawnedMinions.clear(); + } + + if (!_respawnTasks.isEmpty()) + { + for (ScheduledFuture task : _respawnTasks) + { + if ((task != null) && !task.isCancelled() && !task.isDone()) + { + task.cancel(true); + } + } + + _respawnTasks.clear(); + } + } + } + + /** + * Handles minion death by removing from tracking and scheduling respawn if configured.
    + * Respawn behavior depends on master type and the specified respawn time parameter. + * @param minion the minion that died + * @param respawnTime respawn delay in milliseconds, -1 uses default values, 0 disables respawn + */ + public void onMinionDie(Monster minion, int respawnTime) + { + // Prevent memory leaks. + if (respawnTime == NO_RESPAWN) + { + minion.setLeader(null); + } + + _spawnedMinions.remove(minion); + + final int actualRespawnTime = respawnTime <= INVALID_RESPAWN_TIME ? _master.isRaid() ? (int) NpcConfig.RAID_MINION_RESPAWN_TIMER : NO_RESPAWN : respawnTime; + if ((actualRespawnTime > NO_RESPAWN) && !_master.isAlikeDead()) + { + _respawnTasks.add(ThreadPool.schedule(new MinionRespawnTask(minion), actualRespawnTime)); + } + } + + /** + * Handles combat assistance by distributing aggro to master and available minions.
    + * Master receives aggro if not in combat, and free minions receive scaled aggro amounts. + * @param caller the creature that was attacked (master or minion) + * @param attacker the creature that initiated the attack + */ + public void onAssist(Creature caller, Creature attacker) + { + if (attacker == null) + { + return; + } + + if (!_master.isAlikeDead() && !_master.isInCombat()) + { + _master.addDamageHate(attacker, 0, BASE_AGGRO_AMOUNT); + } + + final boolean callerIsMaster = caller == _master; + int aggroAmount = callerIsMaster ? MASTER_AGGRO_AMOUNT : BASE_AGGRO_AMOUNT; + if (_master.isRaid()) + { + aggroAmount *= RAID_AGGRO_MULTIPLIER; + } + + for (Monster minion : _spawnedMinions) + { + if ((minion != null) && !minion.isDead() && (callerIsMaster || !minion.isInCombat())) + { + minion.addDamageHate(attacker, 0, aggroAmount); + } + } + } + + /** + * Teleports all alive and mobile minions to positions around the master.
    + * Called when master teleports to maintain minion formation and proximity. + */ + public void onMasterTeleported() + { + final int spawnRadius = SPAWN_OFFSET_RADIUS; + final int minimumRadius = (int) _master.getCollisionRadius() + MINIMUM_SPAWN_RADIUS; + for (Monster minion : _spawnedMinions) + { + if ((minion != null) && !minion.isDead() && !minion.isMovementDisabled()) + { + int newX = Rnd.get(minimumRadius * 2, spawnRadius * 2); // x + int newY = Rnd.get(newX, spawnRadius * 2); // distance + newY = (int) Math.sqrt((newY * newY) - (newX * newX)); // y + if (newX > (spawnRadius + minimumRadius)) + { + newX = (_master.getX() + newX) - spawnRadius; + } + else + { + newX = (_master.getX() - newX) + minimumRadius; + } + + if (newY > (spawnRadius + minimumRadius)) + { + newY = (_master.getY() + newY) - spawnRadius; + } + else + { + newY = (_master.getY() - newY) + minimumRadius; + } + + minion.teleToLocation(new Location(newX, newY, _master.getZ())); + } + } + } + + /** + * Spawns a single minion by template ID using the master as reference. + * @param minionTemplateId the NPC template ID of the minion to spawn + */ + private void spawnMinion(int minionTemplateId) + { + if (minionTemplateId == 0) + { + return; + } + + spawnMinion(_master, minionTemplateId); + } + + /** + * Task for respawning dead minions after a configured delay.
    + * Restores the minion's state and inherits master's current aggro list. + */ + private class MinionRespawnTask implements Runnable + { + private final Monster _minion; + + /** + * Creates a respawn task for the specified minion. + * @param minion the minion to respawn + */ + public MinionRespawnTask(Monster minion) + { + _minion = minion; + } + + @Override + public void run() + { + // Minion can be already spawned or deleted. + if (!_master.isAlikeDead() && _master.isSpawned() && !_minion.isSpawned()) + { + // _minion.refreshId(); + initializeNpc(_master, _minion); + + // Assist master with existing aggro targets. + if (!_master.getAggroList().isEmpty()) + { + _minion.getAggroList().putAll(_master.getAggroList()); + _minion.getAI().setIntentionAttack(_minion.getAggroList().keySet().iterator().next()); + } + } + } + } + + /** + * Creates and initializes a new minion instance in the world.
    + * Sets up the minion's stats, position, leadership and spawns it near the master. + * @param master the monster that will control this minion + * @param minionTemplateId the NPC template ID for the minion type + * @return the spawned minion instance or null if template not found + */ + public static Monster spawnMinion(Monster master, int minionTemplateId) + { + // Get the template of the Minion to spawn. + final NpcTemplate minionTemplate = NpcData.getInstance().getTemplate(minionTemplateId); + if (minionTemplate == null) + { + return null; + } + + return initializeNpc(master, new Monster(minionTemplate)); + } + + /** + * Initializes a minion monster with full stats and spawns it near the master.
    + * Resets the minion's state, assigns leadership, sets instance and broadcasts spawn. + * @param master the controlling monster + * @param minion the minion to initialize and spawn + * @return the initialized and spawned minion + */ + protected static Monster initializeNpc(Monster master, Monster minion) + { + minion.stopAllEffects(); + minion.setDead(false); + minion.setDecayed(false); + + // Set the Minion HP, MP and Heading. + minion.setCurrentHpMp(minion.getMaxHp(), minion.getMaxMp()); + minion.setHeading(master.getHeading()); + + // Set the Minion leader to this RaidBoss. + minion.setLeader(master); + + // Move monster to masters instance. + minion.setInstanceId(master.getInstanceId()); + + // Set custom Npc server side name and title. + if (minion.getTemplate().isUsingServerSideName()) + { + minion.setName(minion.getTemplate().getName()); + } + + if (minion.getTemplate().isUsingServerSideTitle()) + { + minion.setTitle(minion.getTemplate().getTitle()); + } + + // Initialize the position of the Minion and add it in the world as a visible object. + final int spawnRadius = SPAWN_OFFSET_RADIUS; + final int minimumRadius = (int) master.getCollisionRadius() + MINIMUM_SPAWN_RADIUS; + int newX = Rnd.get(minimumRadius * 2, spawnRadius * 2); // x + int newY = Rnd.get(newX, spawnRadius * 2); // distance + newY = (int) Math.sqrt((newY * newY) - (newX * newX)); // y + if (newX > (spawnRadius + minimumRadius)) + { + newX = (master.getX() + newX) - spawnRadius; + } + else + { + newX = (master.getX() - newX) + minimumRadius; + } + + if (newY > (spawnRadius + minimumRadius)) + { + newY = (master.getY() + newY) - spawnRadius; + } + else + { + newY = (master.getY() - newY) + minimumRadius; + } + + minion.spawnMe(newX, newY, master.getZ()); + + // Make sure info is broadcasted in instances. + if (minion.isInInstance()) + { + minion.broadcastInfo(); + } + + return minion; + } + + /** + * Counts the number of spawned minions matching the specified template ID. + * @param minionTemplateId the template ID to count + * @return number of spawned minions with matching ID + */ + private int countSpawnedMinionsById(int minionTemplateId) + { + int count = 0; + for (Monster minion : _spawnedMinions) + { + if ((minion != null) && (minion.getId() == minionTemplateId)) + { + count++; + } + } + + return count; + } + + /** + * Gets the total number of spawned minions. + * @return total spawned minion count + */ + public int getSpawnedMinionCount() + { + return _spawnedMinions.size(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/npc/WalkInfo.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/npc/WalkInfo.java new file mode 100644 index 0000000..4d3dc99 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/npc/WalkInfo.java @@ -0,0 +1,295 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.holders.npc; + +import java.util.concurrent.ScheduledFuture; + +import org.l2jmobius.commons.util.Rnd; +import org.l2jmobius.gameserver.entity.actor.Npc; +import org.l2jmobius.gameserver.managers.WalkingManager; +import org.l2jmobius.gameserver.mechanics.events.EventDispatcher; +import org.l2jmobius.gameserver.mechanics.events.EventType; +import org.l2jmobius.gameserver.mechanics.events.holders.actor.npc.OnNpcMoveRouteFinished; + +/** + * Holds info about current walk progress.
    + * Maintains the route cursor and runtime state for an NPC walking task. + *
      + *
    • Supports sequential and random traversal.
    • + *
    • Dispatches route-finished event on last node reach.
    • + *
    • Stores a check task reference and debug timestamp.
    • + *
    + * @author BazookaRpm + */ +public class WalkInfo +{ + // Constants. + private static final int FLAG_BLOCKED = 0x01; + private static final int FLAG_SUSPENDED = 0x02; + private static final int FLAG_STOPPED_BY_ATTACK = 0x04; + + private static final int STEP_FORWARD = 1; + + private static final int NODE_FIRST = 0; + private static final int NODE_SECOND = 1; + + // Route Data. + private final String _routeName; + + // Task Data. + private ScheduledFuture _walkCheckTask; + + // Runtime State. + private int _stateFlags; + private int _cursor; + private int _step; + + // Debug Data. + private long _lastActionTime; + + public WalkInfo(String routeName) + { + _routeName = routeName; + _walkCheckTask = null; + + _stateFlags = 0; + _cursor = NODE_FIRST; + _step = STEP_FORWARD; + + _lastActionTime = 0L; + } + + public boolean isBlocked() + { + return (_stateFlags & FLAG_BLOCKED) != 0; + } + + public void setBlocked(boolean value) + { + int flags = _stateFlags; + if (value) + { + flags |= FLAG_BLOCKED; + } + else + { + flags &= ~FLAG_BLOCKED; + } + _stateFlags = flags; + } + + public boolean isSuspended() + { + return (_stateFlags & FLAG_SUSPENDED) != 0; + } + + public void setSuspended(boolean value) + { + int flags = _stateFlags; + if (value) + { + flags |= FLAG_SUSPENDED; + } + else + { + flags &= ~FLAG_SUSPENDED; + } + _stateFlags = flags; + } + + public boolean isStoppedByAttack() + { + return (_stateFlags & FLAG_STOPPED_BY_ATTACK) != 0; + } + + public void setStoppedByAttack(boolean value) + { + int flags = _stateFlags; + if (value) + { + flags |= FLAG_STOPPED_BY_ATTACK; + } + else + { + flags &= ~FLAG_STOPPED_BY_ATTACK; + } + _stateFlags = flags; + } + + public int getCurrentNodeId() + { + return _cursor; + } + + public long getLastAction() + { + return _lastActionTime; + } + + public void setLastAction(long value) + { + _lastActionTime = value; + } + + public ScheduledFuture getWalkCheckTask() + { + return _walkCheckTask; + } + + public void setWalkCheckTask(ScheduledFuture task) + { + _walkCheckTask = task; + } + + public WalkRoute getRoute() + { + final WalkingManager manager = WalkingManager.getInstance(); + final WalkRoute route = manager.getRoute(_routeName); + return route; + } + + public WalkNode getCurrentNode() + { + final WalkRoute route = getRoute(); + int cursor = _cursor; + if (cursor < NODE_FIRST) + { + cursor = NODE_FIRST; + } + + final int size = route.getNodeList().size(); + final int last = size - 1; + if (cursor > last) + { + cursor = last; + } + + return route.getNodeList().get(cursor); + } + + public synchronized void calculateNextNode(Npc npc) + { + final WalkRoute route = getRoute(); + final int nodesCount = route.getNodesCount(); + final byte repeatType = route.getRepeatType(); + if (repeatType == WalkingManager.REPEAT_RANDOM) + { + if (nodesCount <= NODE_SECOND) + { + _cursor = NODE_FIRST; + return; + } + + final int base = _cursor; + int selected = base; + while (selected == base) + { + final int candidate = Rnd.get(nodesCount); + if (candidate != base) + { + selected = candidate; + } + } + + _cursor = selected; + return; + } + + int cursor = _cursor; + cursor += _step; + _cursor = cursor; + + if (_cursor == nodesCount) + { + final EventDispatcher dispatcher = EventDispatcher.getInstance(); + if (dispatcher.hasListener(EventType.ON_NPC_MOVE_ROUTE_FINISHED, npc)) + { + dispatcher.notifyEventAsync(new OnNpcMoveRouteFinished(npc), npc); + } + + if (!route.repeatWalk()) + { + WalkingManager.getInstance().cancelMoving(npc); + return; + } + + switch (repeatType) + { + case WalkingManager.REPEAT_GO_BACK: + { + _step = -STEP_FORWARD; + _cursor = nodesCount - 2; + break; + } + case WalkingManager.REPEAT_GO_FIRST: + { + _step = STEP_FORWARD; + _cursor = NODE_FIRST; + break; + } + case WalkingManager.REPEAT_TELE_FIRST: + { + npc.teleToLocation(npc.getSpawn().getLocation()); + _step = STEP_FORWARD; + _cursor = NODE_FIRST; + break; + } + default: + { + break; + } + } + return; + } + + if (_cursor == WalkingManager.NO_REPEAT) + { + _cursor = NODE_SECOND; + _step = STEP_FORWARD; + } + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(220); + sb.append("WalkInfo [routeName="); + sb.append(_routeName); + sb.append(", walkCheckTask="); + sb.append(_walkCheckTask); + sb.append(", stateFlags=0x"); + sb.append(Integer.toHexString(_stateFlags)); + sb.append(", cursor="); + sb.append(_cursor); + sb.append(", step="); + sb.append(_step); + sb.append(", blocked="); + sb.append(isBlocked()); + sb.append(", suspended="); + sb.append(isSuspended()); + sb.append(", stoppedByAttack="); + sb.append(isStoppedByAttack()); + sb.append(", lastActionTime="); + sb.append(_lastActionTime); + sb.append(']'); + return sb.toString(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/npc/WalkNode.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/npc/WalkNode.java new file mode 100644 index 0000000..3102e37 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/npc/WalkNode.java @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.holders.npc; + +import org.l2jmobius.gameserver.entity.Location; + +/** + * Defines a single walking node for NPC routes.
    + * Extends {@link Location} and adds route execution metadata. + *
      + *
    • Delay tracking for per-node wait time.
    • + *
    • Movement mode (walk/run) selection.
    • + *
    • Optional chat text handling.
    • + *
    + * @author BazookaRpm + */ +public class WalkNode extends Location +{ + // Constants. + private static final int NO_DELAY = 0; + private static final byte MOVE_WALK = 0; + private static final byte MOVE_RUN = 1; + private static final byte CHAT_EMPTY = 0; + private static final byte CHAT_PRESENT = 1; + private static final String EMPTY_TEXT = ""; + + // Node Data. + private final int _delayMs; + private final byte _moveMode; + private final byte _chatState; + private final String _chatText; + + /** + * Creates a new walking node.
    + * Coordinates are stored in {@link Location} and metadata locally. + * @param moveX X coordinate. + * @param moveY Y coordinate. + * @param moveZ Z coordinate. + * @param delay Delay in milliseconds. + * @param runToLocation {@code true} to run to the node. + * @param chatText Optional chat text, or {@code null}. + */ + public WalkNode(int moveX, int moveY, int moveZ, int delay, boolean runToLocation, String chatText) + { + super(moveX, moveY, moveZ); + + _delayMs = delay; + + byte mode; + if (runToLocation) + { + mode = MOVE_RUN; + } + else + { + mode = MOVE_WALK; + } + _moveMode = mode; + + String text = chatText; + if (text == null) + { + text = EMPTY_TEXT; + } + + byte state; + if (text.isEmpty()) + { + state = CHAT_EMPTY; + } + else + { + state = CHAT_PRESENT; + } + _chatState = state; + _chatText = text; + } + + /** + * Gets the configured delay. + * @return The delay + */ + public int getDelay() + { + final int value = _delayMs; + if (value == NO_DELAY) + { + return NO_DELAY; + } + + return value; + } + + /** + * Checks whether the node should be reached running. + * @return {@code true} if running + */ + public boolean runToLocation() + { + return _moveMode == MOVE_RUN; + } + + /** + * Gets the chat text for this node. + * @return The chat text + */ + public String getChatText() + { + if (_chatState == CHAT_PRESENT) + { + return _chatText; + } + + return EMPTY_TEXT; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/npc/WalkRoute.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/npc/WalkRoute.java new file mode 100644 index 0000000..477f38b --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/npc/WalkRoute.java @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.holders.npc; + +import java.util.List; +import java.util.Objects; + +/** + * Defines an NPC walking route.
    + * Stores an ordered node list and repeat policy. + *
      + *
    • Fail-fast validation for route name and node list.
    • + *
    • Repeat is enabled only for repeat types 0..2.
    • + *
    • Provides helpers for route traversal.
    • + *
    + * @author BazookaRpm + */ +public class WalkRoute +{ + // Constants. + private static final byte REPEAT_MIN = 0; + private static final byte REPEAT_MID = 1; + private static final byte REPEAT_MAX = 2; + + // Route Definition. + private final String _name; + private final List _nodeList; + + // Repeat Policy. + private final boolean _repeatWalk; + private boolean _stopAfterCycle; + private final byte _repeatType; + + public WalkRoute(String name, List route, boolean repeat, byte repeatType) + { + _name = Objects.requireNonNull(name, "name"); + _nodeList = Objects.requireNonNull(route, "route"); + + if (_nodeList.isEmpty()) + { + throw new IllegalArgumentException("WalkRoute node list is empty: " + _name); + } + + for (int i = 0; i < _nodeList.size(); i++) + { + if (_nodeList.get(i) == null) + { + throw new IllegalArgumentException("WalkRoute has null node at index " + i + ": " + _name); + } + } + + _repeatType = repeatType; + + boolean supported = false; + switch (_repeatType) + { + case REPEAT_MIN: + case REPEAT_MID: + case REPEAT_MAX: + { + supported = true; + break; + } + default: + { + supported = false; + break; + } + } + + if ((_repeatType < REPEAT_MIN) || (_repeatType > REPEAT_MAX)) + { + supported = false; + } + + _repeatWalk = repeat && supported; + } + + public boolean repeatWalk() + { + return _repeatWalk; + } + + public byte getRepeatType() + { + return _repeatType; + } + + public boolean doOnce() + { + return _stopAfterCycle; + } + + public WalkNode getLastNode() + { + final int lastIndex = _nodeList.size() - 1; + return _nodeList.get(lastIndex); + } + + public int getNodesCount() + { + return _nodeList.size(); + } + + public List getNodeList() + { + return _nodeList; + } + + public String getName() + { + return _name; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/player/AutoPlaySettingsHolder.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/player/AutoPlaySettingsHolder.java new file mode 100644 index 0000000..8581a91 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/player/AutoPlaySettingsHolder.java @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.holders.player; + +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * @author Mobius + */ +public class AutoPlaySettingsHolder +{ + private final AtomicInteger _options = new AtomicInteger(); + private final AtomicBoolean _pickup = new AtomicBoolean(true); + private final AtomicInteger _nextTargetMode = new AtomicInteger(1); + private final AtomicBoolean _shortRange = new AtomicBoolean(); + private final AtomicBoolean _respectfulHunting = new AtomicBoolean(true); + private final AtomicInteger _autoPotionPercent = new AtomicInteger(50); + + public AutoPlaySettingsHolder() + { + } + + public int getOptions() + { + return _options.get(); + } + + public void setOptions(int options) + { + _options.set(options); + } + + public boolean doPickup() + { + return _pickup.get(); + } + + public void setPickup(boolean value) + { + _pickup.set(value); + } + + public int getNextTargetMode() + { + return _nextTargetMode.get(); + } + + public void setNextTargetMode(int nextTargetMode) + { + _nextTargetMode.set(nextTargetMode); + } + + public boolean isShortRange() + { + return _shortRange.get(); + } + + public void setShortRange(boolean value) + { + _shortRange.set(value); + } + + public boolean isRespectfulHunting() + { + return _respectfulHunting.get(); + } + + public void setRespectfulHunting(boolean value) + { + _respectfulHunting.set(value); + } + + public int getAutoPotionPercent() + { + return _autoPotionPercent.get(); + } + + public void setAutoPotionPercent(int value) + { + _autoPotionPercent.set(value); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/player/AutoUseSettingsHolder.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/player/AutoUseSettingsHolder.java new file mode 100644 index 0000000..3b8fbde --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/player/AutoUseSettingsHolder.java @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.holders.player; + +import java.util.Collection; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * @author Mobius + */ +public class AutoUseSettingsHolder +{ + private final Collection _autoSupplyItems = ConcurrentHashMap.newKeySet(); + private final Collection _autoActions = ConcurrentHashMap.newKeySet(); + private final Collection _autoBuffs = ConcurrentHashMap.newKeySet(); + private final List _autoSkills = new CopyOnWriteArrayList<>(); + private final AtomicInteger _autoPotionItem = new AtomicInteger(); + private int _skillIndex = 0; + + public AutoUseSettingsHolder() + { + } + + public Collection getAutoSupplyItems() + { + return _autoSupplyItems; + } + + public Collection getAutoActions() + { + return _autoActions; + } + + public Collection getAutoBuffs() + { + return _autoBuffs; + } + + public List getAutoSkills() + { + return _autoSkills; + } + + public int getAutoPotionItem() + { + return _autoPotionItem.get(); + } + + public void setAutoPotionItem(int itemId) + { + _autoPotionItem.set(itemId); + } + + public boolean isAutoSkill(int skillId) + { + return _autoSkills.contains(skillId) || _autoBuffs.contains(skillId); + } + + public Integer getNextSkillId() + { + if (_skillIndex >= _autoSkills.size()) + { + _skillIndex = 0; + } + + Integer skillId = Integer.MIN_VALUE; + try + { + skillId = _autoSkills.get(_skillIndex); + } + catch (Exception e) + { + resetSkillOrder(); + } + + return skillId; + } + + public void incrementSkillOrder() + { + _skillIndex++; + } + + public void resetSkillOrder() + { + _skillIndex = 0; + } + + public boolean isEmpty() + { + return _autoSupplyItems.isEmpty() && (_autoPotionItem.get() == 0) && _autoSkills.isEmpty() && _autoBuffs.isEmpty() && _autoActions.isEmpty(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/player/BlockList.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/player/BlockList.java new file mode 100644 index 0000000..ec1e13d --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/player/BlockList.java @@ -0,0 +1,273 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.holders.player; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.commons.database.DatabaseFactory; +import org.l2jmobius.gameserver.data.sql.CharInfoTable; +import org.l2jmobius.gameserver.entity.World; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; + +public class BlockList +{ + private static final Logger LOGGER = Logger.getLogger(BlockList.class.getName()); + + private static final Map> OFFLINE_LIST = new ConcurrentHashMap<>(); + + private final Player _owner; + private Set _blockList; + + public BlockList(Player owner) + { + _owner = owner; + _blockList = OFFLINE_LIST.get(owner.getObjectId()); + if (_blockList == null) + { + _blockList = loadList(_owner.getObjectId()); + } + } + + private void addToBlockList(int target) + { + _blockList.add(target); + updateInDB(target, true); + } + + private void removeFromBlockList(int target) + { + _blockList.remove(Integer.valueOf(target)); + updateInDB(target, false); + } + + public void playerLogout() + { + OFFLINE_LIST.put(_owner.getObjectId(), _blockList); + } + + private static Set loadList(int objId) + { + final Set list = new HashSet<>(); + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement statement = con.prepareStatement("SELECT friendId FROM character_friends WHERE charId=? AND relation=1")) + { + statement.setInt(1, objId); + try (ResultSet rset = statement.executeQuery()) + { + int friendId; + while (rset.next()) + { + friendId = rset.getInt("friendId"); + if (friendId == objId) + { + continue; + } + + list.add(friendId); + } + } + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Error found in " + objId + " FriendList while loading BlockList: " + e.getMessage(), e); + } + + return list; + } + + private void updateInDB(int targetId, boolean state) + { + try (Connection con = DatabaseFactory.getConnection()) + { + if (state) // add + { + try (PreparedStatement statement = con.prepareStatement("INSERT INTO character_friends (charId, friendId, relation) VALUES (?, ?, 1)")) + { + statement.setInt(1, _owner.getObjectId()); + statement.setInt(2, targetId); + statement.execute(); + } + } + else + // remove + { + try (PreparedStatement statement = con.prepareStatement("DELETE FROM character_friends WHERE charId=? AND friendId=? AND relation=1")) + { + statement.setInt(1, _owner.getObjectId()); + statement.setInt(2, targetId); + statement.execute(); + } + } + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Could not add block player: " + e.getMessage(), e); + } + } + + public boolean isInBlockList(Player target) + { + return _blockList.contains(target.getObjectId()); + } + + public boolean isInBlockList(int targetId) + { + return _blockList.contains(targetId); + } + + private boolean isBlockAll() + { + return _owner.getMessageRefusal(); + } + + public static boolean isBlocked(Player listOwner, Player target) + { + final BlockList blockList = listOwner.getBlockList(); + return blockList.isBlockAll() || blockList.isInBlockList(target); + } + + public static boolean isBlocked(Player listOwner, int targetId) + { + final BlockList blockList = listOwner.getBlockList(); + return blockList.isBlockAll() || blockList.isInBlockList(targetId); + } + + private void setBlockAll(boolean value) + { + _owner.setMessageRefusal(value); + } + + public Set getBlockList() + { + return _blockList; + } + + public static void addToBlockList(Player listOwner, int targetId) + { + if (listOwner == null) + { + return; + } + + if (listOwner.getFriendList().contains(targetId)) + { + listOwner.sendPacket(SystemMessageId.THIS_PLAYER_IS_ALREADY_REGISTERED_IN_YOUR_FRIENDS_LIST); + return; + } + + if (listOwner.getBlockList().getBlockList().contains(targetId)) + { + listOwner.sendMessage("Already in ignore list."); + return; + } + + listOwner.getBlockList().addToBlockList(targetId); + + final String charName = CharInfoTable.getInstance().getNameById(targetId); + SystemMessage sm = new SystemMessage(SystemMessageId.S1_HAS_BEEN_ADDED_TO_YOUR_IGNORE_LIST); + sm.addString(charName); + listOwner.sendPacket(sm); + + final Player player = World.getPlayer(targetId); + if (player != null) + { + sm = new SystemMessage(SystemMessageId.S1_HAS_PLACED_YOU_ON_HIS_HER_IGNORE_LIST); + sm.addString(listOwner.getName()); + player.sendPacket(sm); + } + } + + public static void removeFromBlockList(Player listOwner, int targetId) + { + if (listOwner == null) + { + return; + } + + SystemMessage sm; + if (!listOwner.getBlockList().getBlockList().contains(targetId)) + { + sm = new SystemMessage(SystemMessageId.THAT_IS_THE_INCORRECT_TARGET); + listOwner.sendPacket(sm); + return; + } + + listOwner.getBlockList().removeFromBlockList(targetId); + + final String charName = CharInfoTable.getInstance().getNameById(targetId); + sm = new SystemMessage(SystemMessageId.S1_HAS_BEEN_REMOVED_FROM_YOUR_IGNORE_LIST); + sm.addString(charName); + listOwner.sendPacket(sm); + } + + public static boolean isInBlockList(Player listOwner, Player target) + { + return listOwner.getBlockList().isInBlockList(target); + } + + public boolean isBlockAll(Player listOwner) + { + return listOwner.getBlockList().isBlockAll(); + } + + public static void setBlockAll(Player listOwner, boolean newValue) + { + listOwner.getBlockList().setBlockAll(newValue); + } + + public static void sendListToOwner(Player listOwner) + { + int i = 1; + listOwner.sendPacket(SystemMessageId.IGNORE_LIST); + for (int playerId : listOwner.getBlockList().getBlockList()) + { + listOwner.sendMessage((i++) + ". " + CharInfoTable.getInstance().getNameById(playerId)); + } + + listOwner.sendPacket(SystemMessageId.EMPTY_3); + } + + /** + * @param ownerId object id of owner block list + * @param targetId object id of potential blocked player + * @return true if blocked + */ + public static boolean isInBlockList(int ownerId, int targetId) + { + final Player player = World.getPlayer(ownerId); + if (player != null) + { + return isBlocked(player, targetId); + } + + return OFFLINE_LIST.computeIfAbsent(ownerId, k -> loadList(k)).contains(targetId); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/player/ClassInfoHolder.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/player/ClassInfoHolder.java new file mode 100644 index 0000000..72f1fea --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/player/ClassInfoHolder.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.holders.player; + +import org.l2jmobius.gameserver.entity.actor.enums.player.PlayerClass; + +/** + * A holder containing information about a player class. + * @author Mobius + */ +public class ClassInfoHolder +{ + private final PlayerClass _playerClass; + private final PlayerClass _parentClass; + private final String _className; + + /** + * Constructs a new ClassInfoHolder with the specified class information. + * @param playerClass the {@link PlayerClass} representing the player class + * @param parentClass the parent {@link PlayerClass} from which this class evolves, or {@code null} if this is a base class + * @param className the display name of the class + */ + public ClassInfoHolder(PlayerClass playerClass, PlayerClass parentClass, String className) + { + _playerClass = playerClass; + _parentClass = parentClass; + _className = className; + } + + /** + * Gets the player class type. + * @return the {@link PlayerClass} representing the player current class + */ + public PlayerClass getPlayerClass() + { + return _playerClass; + } + + /** + * Gets the parent class of this player class. + * @return the parent {@link PlayerClass}, or {@code null} if this is a base class + */ + public PlayerClass getParentClass() + { + return _parentClass; + } + + /** + * Gets the display name of the player class. + * @return the class name as a String + */ + public String getClassName() + { + return _className; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/player/ClassType.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/player/ClassType.java new file mode 100644 index 0000000..8d0311d --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/player/ClassType.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.holders.player; + +/** + * @author G-hamsteR + */ +public enum ClassType +{ + FIGTHER, + MYSTIC, + PRIEST +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/player/ContactList.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/player/ContactList.java new file mode 100644 index 0000000..87c46aa --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/player/ContactList.java @@ -0,0 +1,177 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.actor.holders.player; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.commons.database.DatabaseFactory; +import org.l2jmobius.gameserver.data.sql.CharInfoTable; +import org.l2jmobius.gameserver.entity.actor.Player; + +/** + * TODO: System messages:
    + * ADD: 3223: The previous name is being registered. Please try again later.
    + * DEL 3219: $s1 was successfully deleted from your Contact List.
    + * DEL 3217: The name is not currently registered. + * @author UnAfraid, mrTJO + */ +public class ContactList +{ + private static final Logger LOGGER = Logger.getLogger(ContactList.class.getName()); + + private final Player _player; + private final Set _contacts = ConcurrentHashMap.newKeySet(); + + private static final String QUERY_ADD = "REPLACE INTO character_contacts (charId, contactId) VALUES (?, ?)"; + private static final String QUERY_REMOVE = "DELETE FROM character_contacts WHERE charId = ? and contactId = ?"; + private static final String QUERY_LOAD = "SELECT contactId FROM character_contacts WHERE charId = ?"; + + public ContactList(Player player) + { + _player = player; + restore(); + } + + public void restore() + { + _contacts.clear(); + + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement statement = con.prepareStatement(QUERY_LOAD)) + { + statement.setInt(1, _player.getObjectId()); + try (ResultSet rset = statement.executeQuery()) + { + int contactId; + String contactName; + while (rset.next()) + { + contactId = rset.getInt(1); + contactName = CharInfoTable.getInstance().getNameById(contactId); + if ((contactName == null) || contactName.equals(_player.getName()) || (contactId == _player.getObjectId())) + { + continue; + } + + _contacts.add(contactName); + } + } + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Error found in " + _player.getName() + "'s ContactsList: " + e.getMessage(), e); + } + } + + public boolean add(String name) + { + if (_contacts.contains(name)) + { + _player.sendMessage("The name already exists on the added list."); + return false; + } + + if (_player.getName().equals(name)) + { + _player.sendMessage("You cannot add your own name."); + return false; + } + + if (_contacts.size() >= 100) + { + _player.sendMessage("The maximum number of names (100) has been reached. You cannot register any more."); + return false; + } + + final int contactId = CharInfoTable.getInstance().getIdByName(name); + if (contactId < 1) + { + _player.sendMessage("The name " + name + " doesn't exist. Please try another name."); + return false; + } + + for (String contactName : _contacts) + { + if (contactName.equalsIgnoreCase(name)) + { + _player.sendMessage("The name already exists on the added list."); + return false; + } + } + + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement statement = con.prepareStatement(QUERY_ADD)) + { + statement.setInt(1, _player.getObjectId()); + statement.setInt(2, contactId); + statement.execute(); + + _contacts.add(name); + + _player.sendMessage(name + " was successfully added to your Contact List."); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Error found in " + _player.getName() + "'s ContactsList: " + e.getMessage(), e); + } + + return true; + } + + public void remove(String name) + { + if (!_contacts.contains(name)) + { + _player.sendMessage("The name is not currently registered."); + return; + } + + final int contactId = CharInfoTable.getInstance().getIdByName(name); + if (contactId < 1) + { + // TODO: Message? + return; + } + + _contacts.remove(name); + + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement statement = con.prepareStatement(QUERY_REMOVE)) + { + statement.setInt(1, _player.getObjectId()); + statement.setInt(2, contactId); + statement.execute(); + + _player.sendMessage(name + " was successfully deleted from your Contact List."); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Error found in " + _player.getName() + "'s ContactsList: " + e.getMessage(), e); + } + } + + public Set getAllContacts() + { + return _contacts; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/player/Couple.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/player/Couple.java new file mode 100644 index 0000000..a610081 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/player/Couple.java @@ -0,0 +1,164 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.actor.holders.player; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.util.Calendar; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.commons.database.DatabaseFactory; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.managers.IdManager; + +/** + * @author evill33t + */ +public class Couple +{ + private static final Logger LOGGER = Logger.getLogger(Couple.class.getName()); + + private int _id = 0; + private int _player1Id = 0; + private int _player2Id = 0; + private boolean _maried = false; + private Calendar _affiancedDate; + private Calendar _weddingDate; + + public Couple(int coupleId) + { + _id = coupleId; + + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement("SELECT * FROM mods_wedding WHERE id = ?")) + { + ps.setInt(1, _id); + try (ResultSet rs = ps.executeQuery()) + { + while (rs.next()) + { + _player1Id = rs.getInt("player1Id"); + _player2Id = rs.getInt("player2Id"); + _maried = Boolean.parseBoolean(rs.getString("married")); + _affiancedDate = Calendar.getInstance(); + _affiancedDate.setTimeInMillis(rs.getLong("affianceDate")); + + _weddingDate = Calendar.getInstance(); + _weddingDate.setTimeInMillis(rs.getLong("weddingDate")); + } + } + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "Exception: Couple.load(): " + e.getMessage(), e); + } + } + + public Couple(Player player1, Player player2) + { + final long currentTime = System.currentTimeMillis(); + _player1Id = player1.getObjectId(); + _player2Id = player2.getObjectId(); + + _affiancedDate = Calendar.getInstance(); + _affiancedDate.setTimeInMillis(currentTime); + + _weddingDate = Calendar.getInstance(); + _weddingDate.setTimeInMillis(currentTime); + + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement("INSERT INTO mods_wedding (id, player1Id, player2Id, married, affianceDate, weddingDate) VALUES (?, ?, ?, ?, ?, ?)")) + { + _id = IdManager.getInstance().getNextId(); + ps.setInt(1, _id); + ps.setInt(2, _player1Id); + ps.setInt(3, _player2Id); + ps.setBoolean(4, false); + ps.setLong(5, _affiancedDate.getTimeInMillis()); + ps.setLong(6, _weddingDate.getTimeInMillis()); + ps.execute(); + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "Could not create couple: " + e.getMessage(), e); + } + } + + public void marry() + { + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement("UPDATE mods_wedding set married = ?, weddingDate = ? where id = ?")) + { + ps.setBoolean(1, true); + _weddingDate = Calendar.getInstance(); + ps.setLong(2, _weddingDate.getTimeInMillis()); + ps.setInt(3, _id); + ps.execute(); + _maried = true; + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "Could not marry: " + e.getMessage(), e); + } + } + + public void divorce() + { + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement("DELETE FROM mods_wedding WHERE id=?")) + { + ps.setInt(1, _id); + ps.execute(); + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "Exception: Couple.divorce(): " + e.getMessage(), e); + } + } + + public int getId() + { + return _id; + } + + public int getPlayer1Id() + { + return _player1Id; + } + + public int getPlayer2Id() + { + return _player2Id; + } + + public boolean getMaried() + { + return _maried; + } + + public Calendar getAffiancedDate() + { + return _affiancedDate; + } + + public Calendar getWeddingDate() + { + return _weddingDate; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/player/CursedWeapon.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/player/CursedWeapon.java new file mode 100644 index 0000000..351ff16 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/player/CursedWeapon.java @@ -0,0 +1,660 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.holders.player; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.concurrent.ScheduledFuture; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.commons.database.DatabaseFactory; +import org.l2jmobius.commons.threads.ThreadPool; +import org.l2jmobius.commons.util.Rnd; +import org.l2jmobius.gameserver.data.xml.SkillData; +import org.l2jmobius.gameserver.entity.Location; +import org.l2jmobius.gameserver.entity.World; +import org.l2jmobius.gameserver.entity.actor.Attackable; +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.groups.PartyMessageType; +import org.l2jmobius.gameserver.entity.item.enums.BodyPart; +import org.l2jmobius.gameserver.entity.item.enums.ItemProcessType; +import org.l2jmobius.gameserver.entity.item.instance.Item; +import org.l2jmobius.gameserver.managers.CursedWeaponsManager; +import org.l2jmobius.gameserver.mechanics.skill.CommonSkill; +import org.l2jmobius.gameserver.mechanics.skill.Skill; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.serverpackets.Earthquake; +import org.l2jmobius.gameserver.network.serverpackets.ExRedSky; +import org.l2jmobius.gameserver.network.serverpackets.SocialAction; +import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; + +public class CursedWeapon +{ + private static final Logger LOGGER = Logger.getLogger(CursedWeapon.class.getName()); + + // _name is the name of the cursed weapon associated with its ID. + private final String _name; + // _itemId is the Item ID of the cursed weapon. + private final int _itemId; + // _skillId is the skills ID. + private final int _skillId; + private final int _skillMaxLevel; + private int _dropRate; + private int _duration; + private int _durationLost; + private int _disapearChance; + private int _stageKills; + + // This should be false unless if the cursed weapon is dropped, in that case it would be true. + private boolean _isDropped = false; + + // This sets the cursed weapon status to true only if a player has the cursed weapon, otherwise this should be false. + private boolean _isActivated = false; + private ScheduledFuture _removeTask; + + private int _nbKills = 0; + long _endTime = 0; + + private int _playerId = 0; + protected Player _player = null; + private Item _item = null; + private int _playerKarma = 0; + private int _playerPkKills = 0; + + public CursedWeapon(int itemId, int skillId, String name) + { + _name = name; + _itemId = itemId; + _skillId = skillId; + _skillMaxLevel = SkillData.getInstance().getMaxLevel(_skillId); + } + + public void endOfLife() + { + if (_isActivated) + { + if ((_player != null) && _player.isOnline()) + { + // Remove from player + LOGGER.info(_name + " being removed online."); + _player.abortAttack(); + + _player.setKarma(_playerKarma); + _player.setPkKills(_playerPkKills); + _player.setCursedWeaponEquippedId(0); + removeSkill(); + + // Remove + _player.getInventory().unEquipItemInBodySlot(BodyPart.LR_HAND); + _player.storeMe(); + + // Destroy + _player.getInventory().destroyItemByItemId(ItemProcessType.NONE, _itemId, 1, _player, null); + _player.sendItemList(true); + _player.broadcastUserInfo(); + } + else + { + // Remove from Db + LOGGER.info(_name + " being removed offline."); + + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement del = con.prepareStatement("DELETE FROM items WHERE owner_id=? AND item_id=?"); + PreparedStatement ps = con.prepareStatement("UPDATE characters SET karma=?, pkkills=? WHERE charId=?")) + { + // Delete the item + del.setInt(1, _playerId); + del.setInt(2, _itemId); + if (del.executeUpdate() != 1) + { + LOGGER.warning("Error while deleting itemId " + _itemId + " from userId " + _playerId); + } + + // Restore the karma + ps.setInt(1, _playerKarma); + ps.setInt(2, _playerPkKills); + ps.setInt(3, _playerId); + if (ps.executeUpdate() != 1) + { + LOGGER.warning("Error while updating karma & pkkills for userId " + _playerId); + } + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Could not delete : " + e.getMessage(), e); + } + } + } + + // either this cursed weapon is in the inventory of someone who has another cursed weapon equipped, + // OR this cursed weapon is on the ground. + else if ((_player != null) && (_player.getInventory().getItemByItemId(_itemId) != null)) + { + // Destroy + _player.getInventory().destroyItemByItemId(ItemProcessType.NONE, _itemId, 1, _player, null); + _player.sendItemList(true); + _player.broadcastUserInfo(); + } + // is dropped on the ground + else if (_item != null) + { + _item.decayMe(); + // World.removeObject(_item); // redundant - decayMe() already calls World.removeObject + LOGGER.info(_name + " item has been removed from World."); + } + + // Delete infos from table if any. + CursedWeaponsManager.removeFromDb(_itemId); + + final SystemMessage sm = new SystemMessage(SystemMessageId.S1_HAS_DISAPPEARED); + sm.addItemName(_itemId); + CursedWeaponsManager.announce(sm); + + // Reset state + cancelTask(); + _isActivated = false; + _isDropped = false; + _endTime = 0; + _player = null; + _playerId = 0; + _playerKarma = 0; + _playerPkKills = 0; + _item = null; + _nbKills = 0; + } + + private void cancelTask() + { + if (_removeTask != null) + { + _removeTask.cancel(true); + _removeTask = null; + } + } + + private class RemoveTask implements Runnable + { + protected RemoveTask() + { + } + + @Override + public void run() + { + if (System.currentTimeMillis() >= _endTime) + { + endOfLife(); + } + } + } + + private void dropIt(Attackable attackable, Player player) + { + dropIt(attackable, player, null, true); + } + + private void dropIt(Attackable attackable, Player player, Creature killer, boolean fromMonster) + { + _isActivated = false; + if (fromMonster) + { + _item = attackable.dropItem(player, _itemId, 1); + _item.setDropTime(0); // Prevent item from being removed by ItemsAutoDestroy. + + // RedSky and Earthquake + final ExRedSky rs = new ExRedSky(10); + final Earthquake eq = new Earthquake(player.getX(), player.getY(), player.getZ(), 14, 3); + World.broadcastToAllOnlinePlayers(rs); + World.broadcastToAllOnlinePlayers(eq); + } + else + { + _item = _player.getInventory().getItemByItemId(_itemId); + _player.dropItem(ItemProcessType.DEATH, _item, killer, true); + _player.setKarma(_playerKarma); + _player.setPkKills(_playerPkKills); + _player.setCursedWeaponEquippedId(0); + removeSkill(); + _player.abortAttack(); + + // Item item = _player.getInventory().getItemByItemId(_itemId); + // _player.getInventory().dropItem("DieDrop", item, _player, null); + // _player.getInventory().getItemByItemId(_itemId).dropMe(_player, _player.getX(), _player.getY(), _player.getZ()); + } + + _isDropped = true; + final SystemMessage sm = new SystemMessage(SystemMessageId.S2_WAS_DROPPED_IN_THE_S1_REGION); + if (player != null) + { + sm.addZoneName(player.getX(), player.getY(), player.getZ()); // Region Name + } + else if (_player != null) + { + sm.addZoneName(_player.getX(), _player.getY(), _player.getZ()); // Region Name + } + else + { + sm.addZoneName(killer.getX(), killer.getY(), killer.getZ()); // Region Name + } + + sm.addItemName(_itemId); + CursedWeaponsManager.announce(sm); // in the Hot Spring region + } + + public void cursedOnLogin() + { + giveSkill(); + + final SystemMessage msg = new SystemMessage(SystemMessageId.S2_S_OWNER_HAS_LOGGED_INTO_THE_S1_REGION); + msg.addZoneName(_player.getX(), _player.getY(), _player.getZ()); + msg.addItemName(_player.getCursedWeaponEquippedId()); + CursedWeaponsManager.announce(msg); + + final CursedWeapon cw = CursedWeaponsManager.getInstance().getCursedWeapon(_player.getCursedWeaponEquippedId()); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.S1_HAS_S2_MINUTE_S_OF_USAGE_TIME_REMAINING); + final int timeLeft = (int) (cw.getTimeLeft() / 60000); + msg2.addItemName(_player.getCursedWeaponEquippedId()); + msg2.addInt(timeLeft); + _player.sendPacket(msg2); + } + + /** + * Yesod:
    + * Rebind the passive skill belonging to the CursedWeapon. Invoke this method if the weapon owner switches to a subclass. + */ + public void giveSkill() + { + int level = 1 + (_nbKills / _stageKills); + if (level > _skillMaxLevel) + { + level = _skillMaxLevel; + } + + final Skill skill = SkillData.getInstance().getSkill(_skillId, level); + _player.addSkill(skill, false); + + // Void Burst, Void Flow. + _player.addSkill(CommonSkill.VOID_BURST.getSkill(), false); + _player.addSkill(CommonSkill.VOID_FLOW.getSkill(), false); + _player.sendSkillList(); + } + + public void removeSkill() + { + _player.removeSkill(_skillId); + _player.removeSkill(CommonSkill.VOID_BURST.getSkill().getId()); + _player.removeSkill(CommonSkill.VOID_FLOW.getSkill().getId()); + _player.sendSkillList(); + } + + public void reActivate() + { + _isActivated = true; + if ((_endTime - System.currentTimeMillis()) <= 0) + { + endOfLife(); + } + else + { + _removeTask = ThreadPool.scheduleAtFixedRate(new RemoveTask(), _durationLost * 12000, _durationLost * 12000); + } + } + + public boolean checkDrop(Attackable attackable, Player player) + { + if (Rnd.get(100000) < _dropRate) + { + // Drop the item + dropIt(attackable, player); + + // Start the Life Task. + _endTime = System.currentTimeMillis() + (_duration * 60000); + _removeTask = ThreadPool.scheduleAtFixedRate(new RemoveTask(), _durationLost * 12000, _durationLost * 12000); + return true; + } + + return false; + } + + public void activate(Player player, Item item) + { + // If the player is mounted, attempt to unmount first. + // Only allow picking up the cursed weapon if unmounting is successful. + if (player.isMounted() && !player.dismount()) + { + // TODO: Verify the following system message, may still be custom. + player.sendPacket(SystemMessageId.YOU_HAVE_FAILED_TO_PICK_UP_S1); + player.dropItem(ItemProcessType.DROP, item, null, true); + return; + } + + _isActivated = true; + + // Player holding it data. + _player = player; + _playerId = _player.getObjectId(); + _playerKarma = _player.getKarma(); + _playerPkKills = _player.getPkKills(); + saveData(); + + // Change player stats + _player.setCursedWeaponEquippedId(_itemId); + _player.setKarma(9999999); + _player.setPkKills(0); + if (_player.isInParty()) + { + _player.getParty().removePartyMember(_player, PartyMessageType.EXPELLED); + } + + // Add skill + giveSkill(); + + // Equip with the weapon. + _item = item; + _player.getInventory().equipItem(_item); + SystemMessage sm = new SystemMessage(SystemMessageId.YOU_HAVE_EQUIPPED_YOUR_S1); + sm.addItemName(_item); + _player.sendPacket(sm); + + // Fully heal player + _player.setCurrentHpMp(_player.getMaxHp(), _player.getMaxMp()); + _player.setCurrentCp(_player.getMaxCp()); + + // Refresh inventory + _player.sendItemList(false); + + // Refresh player stats + _player.broadcastUserInfo(); + + ThreadPool.schedule(() -> _player.broadcastPacket(new SocialAction(_player.getObjectId(), 17)), 300); + + sm = new SystemMessage(SystemMessageId.THE_OWNER_OF_S2_HAS_APPEARED_IN_THE_S1_REGION); + sm.addZoneName(_player.getX(), _player.getY(), _player.getZ()); // Region Name + sm.addItemName(_item); + CursedWeaponsManager.announce(sm); + } + + public void saveData() + { + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement del = con.prepareStatement("DELETE FROM cursed_weapons WHERE itemId = ?"); + PreparedStatement ps = con.prepareStatement("INSERT INTO cursed_weapons (itemId, charId, playerKarma, playerPkKills, nbKills, endTime) VALUES (?, ?, ?, ?, ?, ?)")) + { + // Delete previous datas + del.setInt(1, _itemId); + del.executeUpdate(); + + if (_isActivated) + { + ps.setInt(1, _itemId); + ps.setInt(2, _playerId); + ps.setInt(3, _playerKarma); + ps.setInt(4, _playerPkKills); + ps.setInt(5, _nbKills); + ps.setLong(6, _endTime); + ps.executeUpdate(); + } + } + catch (SQLException e) + { + LOGGER.log(Level.SEVERE, "CursedWeapon: Failed to save data.", e); + } + } + + public void dropIt(Creature killer) + { + if (Rnd.get(100) <= _disapearChance) + { + // Remove it + endOfLife(); + } + else + { + // Unequip & Drop + dropIt(null, null, killer, false); + + // Reset player stats + _player.setKarma(_playerKarma); + _player.setPkKills(_playerPkKills); + _player.setCursedWeaponEquippedId(0); + removeSkill(); + + _player.abortAttack(); + + _player.broadcastUserInfo(); + } + } + + public void increaseKills() + { + _nbKills++; + + if ((_player != null) && _player.isOnline()) + { + _player.setPkKills(_nbKills); + _player.updateUserInfo(); + if (((_nbKills % _stageKills) == 0) && (_nbKills <= (_stageKills * (_skillMaxLevel - 1)))) + { + giveSkill(); + } + } + + // Reduce time-to-live + _endTime -= _durationLost * 60000; + saveData(); + } + + public void setDisapearChance(int disapearChance) + { + _disapearChance = disapearChance; + } + + public void setDropRate(int dropRate) + { + _dropRate = dropRate; + } + + public void setDuration(int duration) + { + _duration = duration; + } + + public void setDurationLost(int durationLost) + { + _durationLost = durationLost; + } + + public void setStageKills(int stageKills) + { + _stageKills = stageKills; + } + + public void setNbKills(int nbKills) + { + _nbKills = nbKills; + } + + public void setPlayerId(int playerId) + { + _playerId = playerId; + } + + public void setPlayerKarma(int playerKarma) + { + _playerKarma = playerKarma; + } + + public void setPlayerPkKills(int playerPkKills) + { + _playerPkKills = playerPkKills; + } + + public void setActivated(boolean isActivated) + { + _isActivated = isActivated; + } + + public void setDropped(boolean isDropped) + { + _isDropped = isDropped; + } + + public void setEndTime(long endTime) + { + _endTime = endTime; + } + + public void setPlayer(Player player) + { + _player = player; + } + + public void setItem(Item item) + { + _item = item; + } + + public boolean isActivated() + { + return _isActivated; + } + + public boolean isDropped() + { + return _isDropped; + } + + public long getEndTime() + { + return _endTime; + } + + public String getName() + { + return _name; + } + + public int getItemId() + { + return _itemId; + } + + public int getSkillId() + { + return _skillId; + } + + public int getPlayerId() + { + return _playerId; + } + + public Player getPlayer() + { + return _player; + } + + public int getPlayerKarma() + { + return _playerKarma; + } + + public int getPlayerPkKills() + { + return _playerPkKills; + } + + public int getNbKills() + { + return _nbKills; + } + + public int getStageKills() + { + return _stageKills; + } + + public boolean isActive() + { + return _isActivated || _isDropped; + } + + public int getLevel() + { + if (_nbKills > (_stageKills * _skillMaxLevel)) + { + return _skillMaxLevel; + } + + return (_nbKills / _stageKills); + } + + public long getTimeLeft() + { + return _endTime - System.currentTimeMillis(); + } + + public void goTo(Player player) + { + if (player == null) + { + return; + } + + if (_isActivated && (_player != null)) + { + // Go to player holding the weapon. + player.teleToLocation(_player.getLocation(), true); + } + else if (_isDropped && (_item != null)) + { + // Go to item on the ground. + player.teleToLocation(_item.getLocation(), true); + } + else + { + player.sendMessage(_name + " isn't in the World."); + } + } + + public Location getWorldPosition() + { + if (_isActivated && (_player != null)) + { + return _player.getLocation(); + } + + if (_isDropped && (_item != null)) + { + return _item.getLocation(); + } + + return null; + } + + public long getDuration() + { + return _duration; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/player/Duel.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/player/Duel.java new file mode 100644 index 0000000..cd46cde --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/player/Duel.java @@ -0,0 +1,1091 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.holders.player; + +import java.util.Calendar; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.commons.threads.ThreadPool; +import org.l2jmobius.gameserver.entity.Location; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.enums.creature.Team; +import org.l2jmobius.gameserver.entity.actor.enums.player.DuelResult; +import org.l2jmobius.gameserver.entity.actor.instance.Door; +import org.l2jmobius.gameserver.entity.zone.ZoneId; +import org.l2jmobius.gameserver.managers.DuelManager; +import org.l2jmobius.gameserver.managers.InstanceManager; +import org.l2jmobius.gameserver.mechanics.skill.Skill; +import org.l2jmobius.gameserver.mechanics.skill.enums.SkillFinishType; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.serverpackets.ActionFailed; +import org.l2jmobius.gameserver.network.serverpackets.ExDuelEnd; +import org.l2jmobius.gameserver.network.serverpackets.ExDuelReady; +import org.l2jmobius.gameserver.network.serverpackets.ExDuelStart; +import org.l2jmobius.gameserver.network.serverpackets.ExDuelUpdateUserInfo; +import org.l2jmobius.gameserver.network.serverpackets.PlaySound; +import org.l2jmobius.gameserver.network.serverpackets.ServerPacket; +import org.l2jmobius.gameserver.network.serverpackets.SocialAction; +import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; + +public class Duel +{ + protected static final Logger LOGGER = Logger.getLogger(Duel.class.getName()); + + public static final int DUELSTATE_NODUEL = 0; + public static final int DUELSTATE_DUELLING = 1; + public static final int DUELSTATE_DEAD = 2; + public static final int DUELSTATE_WINNER = 3; + public static final int DUELSTATE_INTERRUPTED = 4; + + private static final PlaySound B04_S01 = new PlaySound(1, "B04_S01", 0, 0, 0, 0, 0); + + private static final int PARTY_DUEL_DURATION = 300; + private static final int PLAYER_DUEL_DURATION = 120; + + private final int _duelId; + private Player _playerA; + private Player _playerB; + private final boolean _partyDuel; + private final Calendar _duelEndTime; + private int _surrenderRequest = 0; + private int _countdown = 5; + private boolean _finished = false; + + private final Map _playerConditions = new ConcurrentHashMap<>(); + private int _duelInstanceId; + + public Duel(Player playerA, Player playerB, int partyDuel, int duelId) + { + _duelId = duelId; + _playerA = playerA; + _playerB = playerB; + _partyDuel = partyDuel == 1; + if (_partyDuel) + { + for (Player member : _playerA.getParty().getMembers()) + { + member.setStartingDuel(); + } + + for (Player member : _playerB.getParty().getMembers()) + { + member.setStartingDuel(); + } + } + else + { + _playerA.setStartingDuel(); + _playerB.setStartingDuel(); + } + + _duelEndTime = Calendar.getInstance(); + _duelEndTime.add(Calendar.SECOND, _partyDuel ? PARTY_DUEL_DURATION : PLAYER_DUEL_DURATION); + setFinished(false); + + if (_partyDuel) + { + // Inform players that they will be ported shortly. + final SystemMessage sm = new SystemMessage(SystemMessageId.IN_A_MOMENT_YOU_WILL_BE_TRANSPORTED_TO_THE_SITE_WHERE_THE_DUEL_WILL_TAKE_PLACE); + broadcastToTeam1(sm); + broadcastToTeam2(sm); + } + + // Schedule duel start + ThreadPool.schedule(new ScheduleStartDuelTask(this), 3000); + } + + public static class PlayerCondition + { + private Player _player; + private double _hp; + private double _mp; + private double _cp; + private boolean _paDuel; + private int _x; + private int _y; + private int _z; + private Set _debuffs; + + public PlayerCondition(Player player, boolean partyDuel) + { + if (player == null) + { + return; + } + + _player = player; + _hp = _player.getCurrentHp(); + _mp = _player.getCurrentMp(); + _cp = _player.getCurrentCp(); + _paDuel = partyDuel; + if (_paDuel) + { + _x = _player.getX(); + _y = _player.getY(); + _z = _player.getZ(); + } + } + + public void restoreCondition() + { + if (_player == null) + { + return; + } + + _player.setCurrentHp(_hp); + _player.setCurrentMp(_mp); + _player.setCurrentCp(_cp); + + if (_paDuel) + { + teleportBack(); + } + + if (_debuffs != null) // Debuff removal + { + for (Skill skill : _debuffs) + { + if (skill != null) + { + _player.stopSkillEffects(SkillFinishType.REMOVED, skill.getId()); + } + } + } + } + + public void registerDebuff(Skill debuff) + { + if (_debuffs == null) + { + _debuffs = ConcurrentHashMap.newKeySet(); + } + + _debuffs.add(debuff); + } + + public void teleportBack() + { + if (_paDuel) + { + _player.teleToLocation(_x, _y, _z); + } + } + + public Player getPlayer() + { + return _player; + } + } + + public class ScheduleDuelTask implements Runnable + { + private final Duel _duel; + + public ScheduleDuelTask(Duel duel) + { + _duel = duel; + } + + @Override + public void run() + { + try + { + switch (_duel.checkEndDuelCondition()) + { + case CANCELED: + { + // do not schedule duel end if it was interrupted + setFinished(true); + _duel.endDuel(DuelResult.CANCELED); + break; + } + case CONTINUE: + { + ThreadPool.schedule(this, 1000); + break; + } + default: + { + setFinished(true); + playKneelAnimation(); + ThreadPool.schedule(new ScheduleEndDuelTask(_duel, _duel.checkEndDuelCondition()), 5000); + InstanceManager.getInstance().destroyInstance(_duel.getDueldInstanceId()); + break; + } + } + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "There has been a problem while runing a duel task!", e); + } + } + } + + public static class ScheduleStartDuelTask implements Runnable + { + private final Duel _duel; + + public ScheduleStartDuelTask(Duel duel) + { + _duel = duel; + } + + @Override + public void run() + { + try + { + // start/continue countdown + final int count = _duel.countdown(); + if (count == 4) + { + // Save player conditions before teleporting players. + _duel.savePlayerConditions(); + + // TODO: stadia manager needs a function to return an unused stadium for duels + // currently only teleports to the same stadium + _duel.teleportPlayers(-83760, -238825, -3331); + + // give players 20 seconds to complete teleport and get ready (its ought to be 30 on official..) + ThreadPool.schedule(this, _duel.isPartyDuel() ? 20000 : 1); + } + else if (count > 0) // Duel not started yet - continue countdown. + { + ThreadPool.schedule(this, 1000); + } + else + { + _duel.startDuel(); + } + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "", e); + } + } + } + + public static class ScheduleEndDuelTask implements Runnable + { + private final Duel _duel; + private final DuelResult _result; + + public ScheduleEndDuelTask(Duel duel, DuelResult result) + { + _duel = duel; + _result = result; + } + + @Override + public void run() + { + try + { + _duel.endDuel(_result); + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "There has been a problem while runing a duel end task!", e); + } + } + } + + public int getDueldInstanceId() + { + return _duelInstanceId; + } + + /** + * Stops all players from attacking. Used for duel timeout / interrupt. + */ + private void stopFighting() + { + final ActionFailed af = ActionFailed.STATIC_PACKET; + if (_partyDuel) + { + for (Player temp : _playerA.getParty().getMembers()) + { + temp.abortCast(); + temp.getAI().setIntentionActive(); + temp.setTarget(null); + temp.sendPacket(af); + if (temp.hasSummon() && (temp.getSummon() != null) && !temp.getSummon().isDead()) + { + temp.getSummon().abortCast(); + temp.getSummon().getAI().setIntentionActive(); + temp.getSummon().setTarget(null); + temp.getSummon().sendPacket(af); + } + } + + for (Player temp : _playerB.getParty().getMembers()) + { + temp.abortCast(); + temp.getAI().setIntentionActive(); + temp.setTarget(null); + temp.sendPacket(af); + if (temp.hasSummon() && (temp.getSummon() != null) && !temp.getSummon().isDead()) + { + temp.getSummon().abortCast(); + temp.getSummon().getAI().setIntentionActive(); + temp.getSummon().setTarget(null); + temp.getSummon().sendPacket(af); + } + } + } + else + { + _playerA.abortCast(); + _playerB.abortCast(); + _playerA.getAI().setIntentionActive(); + _playerA.setTarget(null); + _playerB.getAI().setIntentionActive(); + _playerB.setTarget(null); + _playerA.sendPacket(af); + _playerB.sendPacket(af); + if (_playerA.hasSummon() && (_playerA.getSummon() != null) && !_playerA.getSummon().isDead()) + { + _playerA.getSummon().abortCast(); + _playerA.getSummon().getAI().setIntentionActive(); + _playerA.getSummon().setTarget(null); + _playerA.getSummon().sendPacket(af); + } + + if (_playerB.hasSummon() && (_playerB.getSummon() != null) && !_playerB.getSummon().isDead()) + { + _playerB.getSummon().abortCast(); + _playerB.getSummon().getAI().setIntentionActive(); + _playerB.getSummon().setTarget(null); + _playerB.getSummon().sendPacket(af); + } + } + } + + /** + * Check if a player engaged in pvp combat (only for 1on1 duels) + * @param sendMessage + * @return returns true if a duelist is engaged in Pvp combat + */ + public boolean isDuelistInPvp(boolean sendMessage) + { + if (_partyDuel) + { + // Party duels take place in arenas - should be no other players there. + return false; + } + else if ((_playerA.getPvpFlag() != 0) || (_playerB.getPvpFlag() != 0)) + { + if (sendMessage) + { + final String engagedInPvP = "The duel was canceled because a duelist engaged in PvP combat."; + _playerA.sendMessage(engagedInPvP); + _playerB.sendMessage(engagedInPvP); + } + + return true; + } + + return false; + } + + /** + * Starts the duel + */ + public void startDuel() + { + if ((_playerA == null) || (_playerB == null) || _playerA.isInDuel() || _playerB.isInDuel()) + { + _playerConditions.clear(); + DuelManager.getInstance().removeDuel(this); + return; + } + + if (_partyDuel) + { + // Set duel state and team. + for (Player temp : _playerA.getParty().getMembers()) + { + temp.cancelActiveTrade(); + temp.setInDuel(_duelId); + temp.setTeam(Team.BLUE); + temp.broadcastUserInfo(); + broadcastToTeam2(new ExDuelUpdateUserInfo(temp)); + } + + for (Player temp : _playerB.getParty().getMembers()) + { + temp.cancelActiveTrade(); + temp.setInDuel(_duelId); + temp.setTeam(Team.RED); + temp.broadcastUserInfo(); + broadcastToTeam1(new ExDuelUpdateUserInfo(temp)); + } + + // Send duel packets + broadcastToTeam1(ExDuelReady.PARTY_DUEL); + broadcastToTeam2(ExDuelReady.PARTY_DUEL); + broadcastToTeam1(ExDuelStart.PARTY_DUEL); + broadcastToTeam2(ExDuelStart.PARTY_DUEL); + + // Open arena doors + for (Door door : InstanceManager.getInstance().getInstance(getDueldInstanceId()).getDoors()) + { + if ((door != null) && !door.isOpen()) + { + door.openMe(); + } + } + } + else + { + // set isInDuel() state + _playerA.setInDuel(_duelId); + _playerA.setTeam(Team.BLUE); + _playerB.setInDuel(_duelId); + _playerB.setTeam(Team.RED); + + // Send duel packets + broadcastToTeam1(ExDuelReady.PLAYER_DUEL); + broadcastToTeam2(ExDuelReady.PLAYER_DUEL); + broadcastToTeam1(ExDuelStart.PLAYER_DUEL); + broadcastToTeam2(ExDuelStart.PLAYER_DUEL); + + broadcastToTeam1(new ExDuelUpdateUserInfo(_playerB)); + broadcastToTeam2(new ExDuelUpdateUserInfo(_playerA)); + _playerA.broadcastUserInfo(); + _playerB.broadcastUserInfo(); + } + + // play sound + broadcastToTeam1(B04_S01); + broadcastToTeam2(B04_S01); + + // start duelling task + ThreadPool.schedule(new ScheduleDuelTask(this), 1000); + } + + /** + * Save the current player condition: hp, mp, cp, location + */ + public void savePlayerConditions() + { + if (_partyDuel) + { + for (Player player : _playerA.getParty().getMembers()) + { + _playerConditions.put(player.getObjectId(), new PlayerCondition(player, _partyDuel)); + } + + for (Player player : _playerB.getParty().getMembers()) + { + _playerConditions.put(player.getObjectId(), new PlayerCondition(player, _partyDuel)); + } + } + else + { + _playerConditions.put(_playerA.getObjectId(), new PlayerCondition(_playerA, _partyDuel)); + _playerConditions.put(_playerB.getObjectId(), new PlayerCondition(_playerB, _partyDuel)); + } + } + + /** + * Restore player conditions + * @param abnormalDuelEnd true if the duel was the duel canceled + */ + public void restorePlayerConditions(boolean abnormalDuelEnd) + { + // update isInDuel() state for all players + if (_partyDuel) + { + for (Player temp : _playerA.getParty().getMembers()) + { + temp.setInDuel(0); + temp.setTeam(Team.NONE); + temp.broadcastUserInfo(); + } + + for (Player temp : _playerB.getParty().getMembers()) + { + temp.setInDuel(0); + temp.setTeam(Team.NONE); + temp.broadcastUserInfo(); + } + } + else + { + _playerA.setInDuel(0); + _playerA.setTeam(Team.NONE); + _playerA.broadcastUserInfo(); + _playerB.setInDuel(0); + _playerB.setTeam(Team.NONE); + _playerB.broadcastUserInfo(); + } + + // if it is an abnormal DuelEnd do not restore hp, mp, cp + if (abnormalDuelEnd) + { + return; + } + + // restore player conditions + _playerConditions.values().forEach(PlayerCondition::restoreCondition); + } + + /** + * Get the duel id + * @return id + */ + public int getId() + { + return _duelId; + } + + /** + * Returns the remaining time + * @return remaining time + */ + public int getRemainingTime() + { + return (int) (_duelEndTime.getTimeInMillis() - System.currentTimeMillis()); + } + + /** + * Get the player that requested the duel + * @return duel requester + */ + public Player getPlayerA() + { + return _playerA; + } + + /** + * Get the player that was challenged + * @return challenged player + */ + public Player getPlayerB() + { + return _playerB; + } + + /** + * Returns whether this is a party duel or not + * @return is party duel + */ + public boolean isPartyDuel() + { + return _partyDuel; + } + + public void setFinished(boolean mode) + { + _finished = mode; + } + + public boolean getFinished() + { + return _finished; + } + + /** + * teleport all players to the given coordinates + * @param x + * @param y + * @param z + */ + public void teleportPlayers(int x, int y, int z) + { + // TODO: adjust the values if needed... or implement something better (especially using more then 1 arena) + if (!_partyDuel) + { + return; + } + + int offset = 0; + for (Player temp : _playerA.getParty().getMembers()) + { + temp.teleToLocation(new Location((x + offset) - 180, y - 150, z)); + offset += 40; + } + + offset = 0; + for (Player temp : _playerB.getParty().getMembers()) + { + temp.teleToLocation(new Location((x + offset) - 180, y + 150, z)); + offset += 40; + } + } + + /** + * Broadcast a packet to the challenger team + * @param packet + */ + public void broadcastToTeam1(ServerPacket packet) + { + if (_playerA == null) + { + return; + } + + if (_partyDuel && (_playerA.getParty() != null)) + { + for (Player temp : _playerA.getParty().getMembers()) + { + temp.sendPacket(packet); + } + } + else + { + _playerA.sendPacket(packet); + } + } + + /** + * Broadcast a packet to the challenged team + * @param packet + */ + public void broadcastToTeam2(ServerPacket packet) + { + if (_playerB == null) + { + return; + } + + if (_partyDuel && (_playerB.getParty() != null)) + { + for (Player temp : _playerB.getParty().getMembers()) + { + temp.sendPacket(packet); + } + } + else + { + _playerB.sendPacket(packet); + } + } + + /** + * Get the duel winner + * @return winner + */ + public Player getWinner() + { + if (!_finished || (_playerA == null) || (_playerB == null)) + { + return null; + } + + if (_playerA.getDuelState() == DUELSTATE_WINNER) + { + return _playerA; + } + + if (_playerB.getDuelState() == DUELSTATE_WINNER) + { + return _playerB; + } + + return null; + } + + /** + * Get the duel looser + * @return looser + */ + public Player getLooser() + { + if (!_finished || (_playerA == null) || (_playerB == null)) + { + return null; + } + + if (_playerA.getDuelState() == DUELSTATE_WINNER) + { + return _playerB; + } + else if (_playerB.getDuelState() == DUELSTATE_WINNER) + { + return _playerA; + } + + return null; + } + + /** + * Playback the bow animation for all loosers + */ + public void playKneelAnimation() + { + final Player looser = getLooser(); + if (looser == null) + { + return; + } + + if (_partyDuel && (looser.getParty() != null)) + { + for (Player temp : looser.getParty().getMembers()) + { + temp.broadcastPacket(new SocialAction(temp.getObjectId(), 7)); + } + } + else + { + looser.broadcastPacket(new SocialAction(looser.getObjectId(), 7)); + } + } + + /** + * Do the countdown and send message to players if necessary + * @return current count + */ + public int countdown() + { + _countdown--; + + if (_countdown > 3) + { + return _countdown; + } + + // Broadcast countdown to duelists. + SystemMessage sm = null; + if (_countdown > 0) + { + sm = new SystemMessage(SystemMessageId.THE_DUEL_WILL_BEGIN_IN_S1_SECOND_S); + sm.addInt(_countdown); + } + else + { + sm = new SystemMessage(SystemMessageId.LET_THE_DUEL_BEGIN); + } + + broadcastToTeam1(sm); + broadcastToTeam2(sm); + + return _countdown; + } + + /** + * The duel has reached a state in which it can no longer continue + * @param result the duel result. + */ + public void endDuel(DuelResult result) + { + if ((_playerA == null) || (_playerB == null)) + { + // clean up + _playerConditions.clear(); + DuelManager.getInstance().removeDuel(this); + return; + } + + // inform players of the result + SystemMessage sm = null; + switch (result) + { + case TEAM_1_WIN: + case TEAM_2_SURRENDER: + { + restorePlayerConditions(false); + sm = _partyDuel ? new SystemMessage(SystemMessageId.S1_S_PARTY_HAS_WON_THE_DUEL) : new SystemMessage(SystemMessageId.S1_HAS_WON_THE_DUEL); + sm.addString(_playerA.getName()); + + broadcastToTeam1(sm); + broadcastToTeam2(sm); + break; + } + case TEAM_1_SURRENDER: + case TEAM_2_WIN: + { + restorePlayerConditions(false); + sm = _partyDuel ? new SystemMessage(SystemMessageId.S1_S_PARTY_HAS_WON_THE_DUEL) : new SystemMessage(SystemMessageId.S1_HAS_WON_THE_DUEL); + sm.addString(_playerB.getName()); + + broadcastToTeam1(sm); + broadcastToTeam2(sm); + break; + } + case CANCELED: + { + stopFighting(); + + // Don't restore hp, mp, cp. + restorePlayerConditions(true); + + // TODO: is there no other message for a canceled duel? + // send SystemMessage + sm = new SystemMessage(SystemMessageId.THE_DUEL_HAS_ENDED_IN_A_TIE); + broadcastToTeam1(sm); + broadcastToTeam2(sm); + break; + } + case TIMEOUT: + { + stopFighting(); + + // hp,mp,cp seem to be restored in a timeout too... + restorePlayerConditions(false); + + // send SystemMessage + sm = new SystemMessage(SystemMessageId.THE_DUEL_HAS_ENDED_IN_A_TIE); + broadcastToTeam1(sm); + broadcastToTeam2(sm); + break; + } + } + + // Send end duel packet. + final ExDuelEnd duelEnd = _partyDuel ? ExDuelEnd.PARTY_DUEL : ExDuelEnd.PLAYER_DUEL; + broadcastToTeam1(duelEnd); + broadcastToTeam2(duelEnd); + + // clean up + _playerConditions.clear(); + DuelManager.getInstance().removeDuel(this); + } + + /** + * Did a situation occur in which the duel has to be ended? + * @return DuelResult duel status + */ + public DuelResult checkEndDuelCondition() + { + // one of the players might leave during duel + if ((_playerA == null) || (_playerB == null) || !_playerA.isOnline() || !_playerB.isOnline()) + { + return DuelResult.CANCELED; + } + + // got a duel surrender request? + if (_surrenderRequest != 0) + { + return _surrenderRequest == 1 ? DuelResult.TEAM_1_SURRENDER : DuelResult.TEAM_2_SURRENDER; + } + // duel timed out + else if (getRemainingTime() <= 0) + { + return DuelResult.TIMEOUT; + } + // Has a player been declared winner yet? + else if (_playerA.getDuelState() == DUELSTATE_WINNER) + { + // If there is a Winner already there should be no more fighting going on. + stopFighting(); + return DuelResult.TEAM_1_WIN; + } + else if (_playerB.getDuelState() == DUELSTATE_WINNER) + { + // If there is a Winner already there should be no more fighting going on. + stopFighting(); + return DuelResult.TEAM_2_WIN; + } + + // More end duel conditions for 1on1 duels. + else if (!_partyDuel) + { + // Duel was interrupted e.g.: player was attacked by mobs / other players. + if ((_playerA.getDuelState() == DUELSTATE_INTERRUPTED) || (_playerB.getDuelState() == DUELSTATE_INTERRUPTED)) + { + return DuelResult.CANCELED; + } + + // Are the players too far apart? + if (!_playerA.isInsideRadius2D(_playerB, 1600)) + { + return DuelResult.CANCELED; + } + + // Did one of the players engage in PvP combat? + if (isDuelistInPvp(true)) + { + return DuelResult.CANCELED; + } + + // Is one of the players in a Siege, Peace or PvP zone? + if (_playerA.isInsideZone(ZoneId.PEACE) || _playerB.isInsideZone(ZoneId.PEACE) || _playerA.isInsideZone(ZoneId.NO_PVP) || _playerB.isInsideZone(ZoneId.NO_PVP) || _playerA.isInsideZone(ZoneId.SIEGE) || _playerB.isInsideZone(ZoneId.SIEGE) || _playerA.isInsideZone(ZoneId.PVP) || _playerB.isInsideZone(ZoneId.PVP)) + { + return DuelResult.CANCELED; + } + } + + return DuelResult.CONTINUE; + } + + /** + * Register a surrender request + * @param player the player that surrenders. + */ + public void doSurrender(Player player) + { + // Already received a surrender request. + if (_surrenderRequest != 0) + { + return; + } + + // stop the fight + stopFighting(); + + // TODO: Can every party member cancel a party duel? or only the party leaders? + if (_partyDuel) + { + if (_playerA.getParty().getMembers().contains(player)) + { + _surrenderRequest = 1; + for (Player temp : _playerA.getParty().getMembers()) + { + temp.setDuelState(DUELSTATE_DEAD); + } + + for (Player temp : _playerB.getParty().getMembers()) + { + temp.setDuelState(DUELSTATE_WINNER); + } + } + else if (_playerB.getParty().getMembers().contains(player)) + { + _surrenderRequest = 2; + for (Player temp : _playerB.getParty().getMembers()) + { + temp.setDuelState(DUELSTATE_DEAD); + } + + for (Player temp : _playerA.getParty().getMembers()) + { + temp.setDuelState(DUELSTATE_WINNER); + } + } + } + else if (player == _playerA) + { + _surrenderRequest = 1; + _playerA.setDuelState(DUELSTATE_DEAD); + _playerB.setDuelState(DUELSTATE_WINNER); + } + else if (player == _playerB) + { + _surrenderRequest = 2; + _playerB.setDuelState(DUELSTATE_DEAD); + _playerA.setDuelState(DUELSTATE_WINNER); + } + } + + /** + * This function is called whenever a player was defeated in a duel + * @param player the player defeated. + */ + public void onPlayerDefeat(Player player) + { + // Set player as defeated. + player.setDuelState(DUELSTATE_DEAD); + + if (_partyDuel) + { + boolean teamdefeated = true; + for (Player temp : player.getParty().getMembers()) + { + if (temp.getDuelState() == DUELSTATE_DUELLING) + { + teamdefeated = false; + break; + } + } + + if (teamdefeated) + { + final Player winner = _playerA.getParty().getMembers().contains(player) ? _playerB : _playerA; + for (Player temp : winner.getParty().getMembers()) + { + temp.setDuelState(DUELSTATE_WINNER); + } + } + } + else + { + if ((player != _playerA) && (player != _playerB)) + { + LOGGER.warning("Error in onPlayerDefeat(): player is not part of this 1vs1 duel"); + } + + if (_playerA == player) + { + _playerB.setDuelState(DUELSTATE_WINNER); + } + else + { + _playerA.setDuelState(DUELSTATE_WINNER); + } + } + } + + /** + * This function is called whenever a player leaves a party + * @param player the player quitting. + */ + public void onRemoveFromParty(Player player) + { + // If it isn't a party duel ignore this. + if (!_partyDuel) + { + return; + } + + // This player is leaving his party during party duel. + // If he's either playerA or playerB cancel the duel and port the players back. + if ((player == _playerA) || (player == _playerB)) + { + for (PlayerCondition cond : _playerConditions.values()) + { + cond.teleportBack(); + cond.getPlayer().setInDuel(0); + } + + _playerA = null; + _playerB = null; + } + else + // Teleport the player back & delete his PlayerCondition record. + { + final PlayerCondition cond = _playerConditions.remove(player.getObjectId()); + if (cond != null) + { + cond.teleportBack(); + } + + player.setInDuel(0); + } + } + + public void onBuff(Player player, Skill debuff) + { + final PlayerCondition cond = _playerConditions.get(player.getObjectId()); + if (cond != null) + { + cond.registerDebuff(debuff); + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/player/Macro.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/player/Macro.java new file mode 100644 index 0000000..449e9e9 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/player/Macro.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.holders.player; + +import java.util.List; + +public class Macro +{ + private int _id; + private final int _icon; + private final String _name; + private final String _descr; + private final String _acronym; + private final List _commands; + + /** + * Constructor for macros. + * @param id the macro ID + * @param icon the icon ID + * @param name the macro name + * @param descr the macro description + * @param acronym the macro acronym + * @param list the macro command list + */ + public Macro(int id, int icon, String name, String descr, String acronym, List list) + { + _id = id; + _icon = icon; + _name = name; + _descr = descr; + _acronym = acronym; + _commands = list; + } + + /** + * Gets the macro ID. + * @return the macro ID + */ + public int getId() + { + return _id; + } + + /** + * Sets the macro ID. + * @param id the macro ID + */ + public void setId(int id) + { + _id = id; + } + + /** + * Gets the macro icon ID. + * @return the icon + */ + public int getIcon() + { + return _icon; + } + + /** + * Gets the macro name. + * @return the name + */ + public String getName() + { + return _name; + } + + /** + * Gets the macro description. + * @return the description + */ + public String getDescr() + { + return _descr; + } + + /** + * Gets the macro acronym. + * @return the acronym + */ + public String getAcronym() + { + return _acronym; + } + + /** + * Gets the macro command list. + * @return the macro command list + */ + public List getCommands() + { + return _commands; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/player/MacroCmd.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/player/MacroCmd.java new file mode 100644 index 0000000..a2fdc50 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/player/MacroCmd.java @@ -0,0 +1,86 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.actor.holders.player; + +import org.l2jmobius.gameserver.entity.actor.enums.player.MacroType; + +/** + * Macro Cmd DTO. + * @author Zoey76 + */ +public class MacroCmd +{ + private final int _entry; + private final MacroType _type; + private final int _d1; // skill_id or page for shortcuts + private final int _d2; // shortcut + private final String _cmd; + + public MacroCmd(int entry, MacroType type, int d1, int d2, String cmd) + { + _entry = entry; + _type = type; + _d1 = d1; + _d2 = d2; + _cmd = cmd; + } + + /** + * Gets the entry index. + * @return the entry index + */ + public int getEntry() + { + return _entry; + } + + /** + * Gets the macro type. + * @return the macro type + */ + public MacroType getType() + { + return _type; + } + + /** + * Gets the skill ID, item ID, page ID, depending on the marco use. + * @return the first value + */ + public int getD1() + { + return _d1; + } + + /** + * Gets the skill level, shortcut ID, depending on the marco use. + * @return the second value + */ + public int getD2() + { + return _d2; + } + + /** + * Gets the command. + * @return the command + */ + public String getCmd() + { + return _cmd; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/player/MacroList.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/player/MacroList.java new file mode 100644 index 0000000..4e14621 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/player/MacroList.java @@ -0,0 +1,235 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.holders.player; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.StringTokenizer; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.commons.database.DatabaseFactory; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.enums.player.MacroType; +import org.l2jmobius.gameserver.entity.actor.enums.player.ShortcutType; +import org.l2jmobius.gameserver.network.serverpackets.SendMacroList; + +public class MacroList +{ + private static final Logger LOGGER = Logger.getLogger(MacroList.class.getName()); + + private final Player _owner; + private int _revision; + private int _macroId; + private final Map _macroses = Collections.synchronizedMap(new LinkedHashMap<>()); + + public MacroList(Player owner) + { + _owner = owner; + _revision = 1; + _macroId = 1000; + } + + public int getRevision() + { + return _revision; + } + + public Map getAllMacroses() + { + return _macroses; + } + + public void registerMacro(Macro macro) + { + if (macro.getId() == 0) + { + macro.setId(_macroId++); + while (_macroses.containsKey(macro.getId())) + { + macro.setId(_macroId++); + } + + _macroses.put(macro.getId(), macro); + registerMacroInDb(macro); + } + else + { + final Macro old = _macroses.put(macro.getId(), macro); + if (old != null) + { + deleteMacroFromDb(old); + } + + registerMacroInDb(macro); + } + + sendUpdate(); + } + + public void deleteMacro(int id) + { + final Macro removed = _macroses.remove(id); + if (removed != null) + { + deleteMacroFromDb(removed); + } + + for (Shortcut sc : _owner.getAllShortcuts()) + { + if ((sc.getId() == id) && (sc.getType() == ShortcutType.MACRO)) + { + _owner.deleteShortcut(sc.getSlot(), sc.getPage()); + } + } + + sendUpdate(); + } + + public void sendUpdate() + { + _revision++; + final Collection allMacros = _macroses.values(); + synchronized (_macroses) + { + if (allMacros.isEmpty()) + { + _owner.sendPacket(new SendMacroList(_revision, 0, null)); + } + else + { + for (Macro m : allMacros) + { + _owner.sendPacket(new SendMacroList(_revision, allMacros.size(), m)); + } + } + } + } + + private void registerMacroInDb(Macro macro) + { + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement("INSERT INTO character_macroses (charId,id,icon,name,descr,acronym,commands) values(?,?,?,?,?,?,?)")) + { + ps.setInt(1, _owner.getObjectId()); + ps.setInt(2, macro.getId()); + ps.setInt(3, macro.getIcon()); + ps.setString(4, macro.getName()); + ps.setString(5, macro.getDescr()); + ps.setString(6, macro.getAcronym()); + final StringBuilder sb = new StringBuilder(300); + for (MacroCmd cmd : macro.getCommands()) + { + sb.append(cmd.getType().ordinal() + "," + cmd.getD1() + "," + cmd.getD2()); + if ((cmd.getCmd() != null) && !cmd.getCmd().isEmpty()) + { + sb.append("," + cmd.getCmd()); + } + sb.append(';'); + } + + if (sb.length() > 255) + { + sb.setLength(255); + } + + ps.setString(7, sb.toString()); + ps.execute(); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "could not store macro:", e); + } + } + + private void deleteMacroFromDb(Macro macro) + { + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement("DELETE FROM character_macroses WHERE charId=? AND id=?")) + { + ps.setInt(1, _owner.getObjectId()); + ps.setInt(2, macro.getId()); + ps.execute(); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "could not delete macro:", e); + } + } + + public boolean restoreMe() + { + _macroses.clear(); + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement("SELECT charId, id, icon, name, descr, acronym, commands FROM character_macroses WHERE charId=?")) + { + ps.setInt(1, _owner.getObjectId()); + try (ResultSet rset = ps.executeQuery()) + { + while (rset.next()) + { + final int id = rset.getInt("id"); + final int icon = rset.getInt("icon"); + final String name = rset.getString("name"); + final String descr = rset.getString("descr"); + final String acronym = rset.getString("acronym"); + final List commands = new ArrayList<>(); + final StringTokenizer st1 = new StringTokenizer(rset.getString("commands"), ";"); + while (st1.hasMoreTokens()) + { + final StringTokenizer st = new StringTokenizer(st1.nextToken(), ","); + if (st.countTokens() < 3) + { + continue; + } + + final MacroType type = MacroType.values()[Integer.parseInt(st.nextToken())]; + final int d1 = Integer.parseInt(st.nextToken()); + final int d2 = Integer.parseInt(st.nextToken()); + String cmd = ""; + if (st.hasMoreTokens()) + { + cmd = st.nextToken(); + } + + commands.add(new MacroCmd(commands.size(), type, d1, d2, cmd)); + } + + _macroses.put(id, new Macro(id, icon, name, descr, acronym, commands)); + } + } + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "could not store shortcuts:", e); + return false; + } + + return true; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/player/Radar.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/player/Radar.java new file mode 100644 index 0000000..18cea95 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/player/Radar.java @@ -0,0 +1,133 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.actor.holders.player; + +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.network.serverpackets.RadarControl; + +/** + * @author dalrond + */ +public class Radar +{ + private final Player _player; + private final Set _markers = ConcurrentHashMap.newKeySet(); + + public Radar(Player player) + { + _player = player; + } + + // Add a marker to player's radar. + public void addMarker(int x, int y, int z) + { + final RadarMarker newMarker = new RadarMarker(x, y, z); + _markers.add(newMarker); + _player.sendPacket(new RadarControl(2, 2, x, y, z)); + _player.sendPacket(new RadarControl(0, 1, x, y, z)); + } + + // Remove a marker from player's radar. + public void removeMarker(int x, int y, int z) + { + for (RadarMarker rm : _markers) + { + if ((rm._x == x) && (rm._y == y) && (rm._z == z)) + { + _markers.remove(rm); + } + } + + _player.sendPacket(new RadarControl(1, 1, x, y, z)); + } + + public void removeAllMarkers() + { + for (RadarMarker tempMarker : _markers) + { + _player.sendPacket(new RadarControl(2, 2, tempMarker._x, tempMarker._y, tempMarker._z)); + } + + _markers.clear(); + } + + public void loadMarkers() + { + _player.sendPacket(new RadarControl(2, 2, _player.getX(), _player.getY(), _player.getZ())); + for (RadarMarker tempMarker : _markers) + { + _player.sendPacket(new RadarControl(0, 1, tempMarker._x, tempMarker._y, tempMarker._z)); + } + } + + public static class RadarMarker + { + // Simple class to model radar points. + public int _type; + public int _x; + public int _y; + public int _z; + + public RadarMarker(int type, int x, int y, int z) + { + _type = type; + _x = x; + _y = y; + _z = z; + } + + public RadarMarker(int x, int y, int z) + { + _type = 1; + _x = x; + _y = y; + _z = z; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = (prime * result) + _type; + result = (prime * result) + _x; + result = (prime * result) + _y; + result = (prime * result) + _z; + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + + if (!(obj instanceof RadarMarker)) + { + return false; + } + + final RadarMarker other = (RadarMarker) obj; + return (_type == other._type) && (_x == other._x) && (_y == other._y) && (_z == other._z); + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/player/RecoBonus.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/player/RecoBonus.java new file mode 100644 index 0000000..3dd089b --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/player/RecoBonus.java @@ -0,0 +1,161 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.actor.holders.player; + +import org.l2jmobius.gameserver.entity.actor.Player; + +/** + * @author Gnacik + */ +public class RecoBonus +{ + private static final int[][] _recoBonus = + { + { + 25, + 50, + 50, + 50, + 50, + 50, + 50, + 50, + 50, + 50 + }, + { + 16, + 33, + 50, + 50, + 50, + 50, + 50, + 50, + 50, + 50 + }, + { + 12, + 25, + 37, + 50, + 50, + 50, + 50, + 50, + 50, + 50 + }, + { + 10, + 20, + 30, + 40, + 50, + 50, + 50, + 50, + 50, + 50 + }, + { + 8, + 16, + 25, + 33, + 41, + 50, + 50, + 50, + 50, + 50 + }, + { + 7, + 14, + 21, + 28, + 35, + 42, + 50, + 50, + 50, + 50 + }, + { + 6, + 12, + 18, + 25, + 31, + 37, + 43, + 50, + 50, + 50 + }, + { + 5, + 11, + 16, + 22, + 27, + 33, + 38, + 44, + 50, + 50 + }, + { + 5, + 10, + 15, + 20, + 25, + 30, + 35, + 40, + 45, + 50 + } + }; + + public static int getRecoBonus(Player player) + { + if ((player != null) && player.isOnline() && (player.getRecomHave() != 0)) + { + final int level = player.getLevel() / 10; + final int exp = (Math.min(100, player.getRecomHave()) - 1) / 10; + return _recoBonus[level][exp]; + } + + return 0; + } + + public static double getRecoMultiplier(Player player) + { + double multiplier = 1.0; + final double bonus = getRecoBonus(player); + if (bonus > 0) + { + multiplier += (bonus / 100); + } + + return multiplier; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/player/Shortcut.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/player/Shortcut.java new file mode 100644 index 0000000..c3ef6a5 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/player/Shortcut.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.holders.player; + +import org.l2jmobius.gameserver.entity.actor.enums.player.ShortcutType; + +public class Shortcut +{ + /** Slot from 0 to 11. */ + private final int _slot; + /** Page from 0 to 9. */ + private final int _page; + /** Type: item, skill, action, macro, recipe, bookmark. */ + private final ShortcutType _type; + /** Shortcut ID. */ + private final int _id; + /** Shortcut level (skills). */ + private final int _level; + + public Shortcut(int slot, int page, ShortcutType type, int id, int level) + { + _slot = slot; + _page = page; + _type = type; + _id = id; + _level = level; + } + + /** + * Gets the shortcut ID. + * @return the ID + */ + public int getId() + { + return _id; + } + + /** + * Gets the shortcut level. + * @return the level + */ + public int getLevel() + { + return _level; + } + + /** + * Gets the shortcut page. + * @return the page + */ + public int getPage() + { + return _page; + } + + /** + * Gets the shortcut slot. + * @return the slot + */ + public int getSlot() + { + return _slot; + } + + /** + * Gets the shortcut type. + * @return the type + */ + public ShortcutType getType() + { + return _type; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/player/Shortcuts.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/player/Shortcuts.java new file mode 100644 index 0000000..79fd16f --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/player/Shortcuts.java @@ -0,0 +1,226 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.holders.player; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.util.Collection; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.commons.database.DatabaseFactory; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.enums.player.ShortcutType; +import org.l2jmobius.gameserver.entity.item.instance.Item; +import org.l2jmobius.gameserver.entity.item.type.EtcItemType; +import org.l2jmobius.gameserver.network.serverpackets.ExAutoSoulShot; +import org.l2jmobius.gameserver.network.serverpackets.ShortcutInit; +import org.l2jmobius.gameserver.network.serverpackets.ShortcutRegister; + +public class Shortcuts +{ + private static final Logger LOGGER = Logger.getLogger(Shortcuts.class.getName()); + private static final int MAX_SHORTCUTS_PER_BAR = 12; + private final Player _owner; + private final Map _shortcuts = new ConcurrentHashMap<>(); + + public Shortcuts(Player owner) + { + _owner = owner; + } + + public Collection getAllShortcuts() + { + return _shortcuts.values(); + } + + public Shortcut getShortcut(int slot, int page) + { + Shortcut sc = _shortcuts.get(slot + (page * MAX_SHORTCUTS_PER_BAR)); + + // Verify shortcut. + if ((sc != null) && (sc.getType() == ShortcutType.ITEM) && (_owner.getInventory().getItemByObjectId(sc.getId()) == null)) + { + deleteShortcut(sc.getSlot(), sc.getPage()); + sc = null; + } + + return sc; + } + + public void registerShortcut(Shortcut shortcut) + { + // Verify shortcut. + if (shortcut.getType() == ShortcutType.ITEM) + { + final Item item = _owner.getInventory().getItemByObjectId(shortcut.getId()); + if (item == null) + { + return; + } + } + + registerShortcutInDb(shortcut, _shortcuts.put(shortcut.getSlot() + (shortcut.getPage() * MAX_SHORTCUTS_PER_BAR), shortcut)); + } + + private void registerShortcutInDb(Shortcut shortcut, Shortcut oldShortcut) + { + if (oldShortcut != null) + { + deleteShortcutFromDb(oldShortcut); + } + + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement statement = con.prepareStatement("REPLACE INTO character_shortcuts (charId,slot,page,type,shortcut_id,level,class_index) values(?,?,?,?,?,?,?)")) + { + statement.setInt(1, _owner.getObjectId()); + statement.setInt(2, shortcut.getSlot()); + statement.setInt(3, shortcut.getPage()); + statement.setInt(4, shortcut.getType().ordinal()); + statement.setInt(5, shortcut.getId()); + statement.setInt(6, shortcut.getLevel()); + statement.setInt(7, _owner.getClassIndex()); + statement.execute(); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Could not store character shortcut: " + e.getMessage(), e); + } + } + + public void deleteShortcut(int slot, int page) + { + final Shortcut old = _shortcuts.remove(slot + (page * MAX_SHORTCUTS_PER_BAR)); + if ((old == null) || (_owner == null)) + { + return; + } + + deleteShortcutFromDb(old); + if (old.getType() == ShortcutType.ITEM) + { + final Item item = _owner.getInventory().getItemByObjectId(old.getId()); + if ((item != null) && (item.getItemType() == EtcItemType.SHOT) && _owner.removeAutoSoulShot(item.getId())) + { + _owner.sendPacket(new ExAutoSoulShot(item.getId(), 0)); + } + } + + _owner.sendPacket(new ShortcutInit(_owner)); + + for (int shotId : _owner.getAutoSoulShot()) + { + _owner.sendPacket(new ExAutoSoulShot(shotId, 1)); + } + } + + public void deleteShortcutByObjectId(int objectId) + { + for (Shortcut shortcut : _shortcuts.values()) + { + if ((shortcut.getType() == ShortcutType.ITEM) && (shortcut.getId() == objectId)) + { + deleteShortcut(shortcut.getSlot(), shortcut.getPage()); + break; + } + } + } + + private void deleteShortcutFromDb(Shortcut shortcut) + { + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement statement = con.prepareStatement("DELETE FROM character_shortcuts WHERE charId=? AND slot=? AND page=? AND class_index=?")) + { + statement.setInt(1, _owner.getObjectId()); + statement.setInt(2, shortcut.getSlot()); + statement.setInt(3, shortcut.getPage()); + statement.setInt(4, _owner.getClassIndex()); + statement.execute(); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Could not delete character shortcut: " + e.getMessage(), e); + } + } + + public boolean restoreMe() + { + _shortcuts.clear(); + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement statement = con.prepareStatement("SELECT charId, slot, page, type, shortcut_id, level FROM character_shortcuts WHERE charId=? AND class_index=?")) + { + statement.setInt(1, _owner.getObjectId()); + statement.setInt(2, _owner.getClassIndex()); + + try (ResultSet rset = statement.executeQuery()) + { + while (rset.next()) + { + final int slot = rset.getInt("slot"); + final int page = rset.getInt("page"); + _shortcuts.put(slot + (page * MAX_SHORTCUTS_PER_BAR), new Shortcut(slot, page, ShortcutType.values()[rset.getInt("type")], rset.getInt("shortcut_id"), rset.getInt("level"))); + } + } + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Could not restore character shortcuts: " + e.getMessage(), e); + return false; + } + + // Verify shortcuts + for (Shortcut sc : getAllShortcuts()) + { + if (sc.getType() == ShortcutType.ITEM) + { + final Item item = _owner.getInventory().getItemByObjectId(sc.getId()); + if (item == null) + { + deleteShortcut(sc.getSlot(), sc.getPage()); + } + } + } + + return true; + } + + /** + * Updates the shortcut bars with the new skill. + * @param skillId the skill Id to search and update. + * @param skillLevel the skill level to update. + */ + public void updateShortcuts(int skillId, int skillLevel) + { + // Update all the shortcuts for this skill. + for (Shortcut sc : _shortcuts.values()) + { + if ((sc.getId() == skillId) && (sc.getType() == ShortcutType.SKILL)) + { + final Shortcut newsc = new Shortcut(sc.getSlot(), sc.getPage(), sc.getType(), sc.getId(), skillLevel); + _owner.sendPacket(new ShortcutRegister(newsc)); + _owner.registerShortcut(newsc); + } + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/player/SubClassHolder.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/player/SubClassHolder.java new file mode 100644 index 0000000..874fd15 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/player/SubClassHolder.java @@ -0,0 +1,157 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.actor.holders.player; + +import org.l2jmobius.gameserver.config.PlayerConfig; +import org.l2jmobius.gameserver.data.xml.ExperienceData; +import org.l2jmobius.gameserver.entity.actor.enums.player.PlayerClass; + +/** + * Character Sub-Class Definition
    + * Used to store key information about a character's sub-class. + * @author Tempy + */ +public class SubClassHolder +{ + private static final byte MAX_LEVEL = PlayerConfig.MAX_SUBCLASS_LEVEL < ExperienceData.getInstance().getMaxLevel() ? PlayerConfig.MAX_SUBCLASS_LEVEL : (byte) (ExperienceData.getInstance().getMaxLevel() - 1); + + private PlayerClass _playerClass; + private long _exp = ExperienceData.getInstance().getExpForLevel(PlayerConfig.BASE_SUBCLASS_LEVEL); + private long _sp = 0; + private byte _level = PlayerConfig.BASE_SUBCLASS_LEVEL; + private int _classIndex = 1; + + public SubClassHolder(int classId, long exp, int sp, byte level, int classIndex) + { + _playerClass = PlayerClass.getPlayerClass(classId); + _exp = exp; + _sp = sp; + _level = level; + _classIndex = classIndex; + } + + public SubClassHolder(int classId, int classIndex) + { + // Used for defining a sub class using default values for XP, SP and player level. + _playerClass = PlayerClass.getPlayerClass(classId); + _classIndex = classIndex; + } + + public SubClassHolder() + { + // Used for specifying ALL attributes of a sub class directly, + // using the preset default values. + } + + public PlayerClass getPlayerClass() + { + return _playerClass; + } + + public int getId() + { + return _playerClass.getId(); + } + + public long getExp() + { + return _exp; + } + + public long getSp() + { + return _sp; + } + + public byte getLevel() + { + return _level; + } + + /** + * First Sub-Class is index 1. + * @return int _classIndex + */ + public int getClassIndex() + { + return _classIndex; + } + + public void setPlayerClass(int id) + { + _playerClass = PlayerClass.getPlayerClass(id); + } + + public void setExp(long expValue) + { + if (expValue > (ExperienceData.getInstance().getExpForLevel(MAX_LEVEL + 1) - 1)) + { + _exp = ExperienceData.getInstance().getExpForLevel(MAX_LEVEL + 1) - 1; + return; + } + + _exp = expValue; + } + + public void setSp(long spValue) + { + _sp = spValue; + } + + public void setClassIndex(int classIndex) + { + _classIndex = classIndex; + } + + public void setLevel(byte levelValue) + { + if (levelValue > MAX_LEVEL) + { + _level = MAX_LEVEL; + return; + } + else if (levelValue < PlayerConfig.BASE_SUBCLASS_LEVEL) + { + _level = PlayerConfig.BASE_SUBCLASS_LEVEL; + return; + } + + _level = levelValue; + } + + public void incLevel() + { + if (_level == MAX_LEVEL) + { + return; + } + + _level++; + setExp(ExperienceData.getInstance().getExpForLevel(getLevel())); + } + + public void decLevel() + { + if (_level == PlayerConfig.BASE_SUBCLASS_LEVEL) + { + return; + } + + _level--; + setExp(ExperienceData.getInstance().getExpForLevel(getLevel())); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/player/SummonRequestHolder.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/player/SummonRequestHolder.java new file mode 100644 index 0000000..4887a95 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/holders/player/SummonRequestHolder.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.holders.player; + +import org.l2jmobius.gameserver.entity.Location; +import org.l2jmobius.gameserver.entity.actor.Player; + +/** + * @author Mobius + */ +public class SummonRequestHolder +{ + private final Player _summoner; + private final Location _location; + + public SummonRequestHolder(Player summoner) + { + _summoner = summoner; + _location = summoner == null ? null : new Location(summoner.getX(), summoner.getY(), summoner.getZ(), summoner.getHeading()); + } + + public Player getSummoner() + { + return _summoner; + } + + public Location getLocation() + { + return _location; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/Adventurer.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/Adventurer.java new file mode 100644 index 0000000..86a1bf1 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/Adventurer.java @@ -0,0 +1,49 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.actor.instance; + +import org.l2jmobius.gameserver.entity.actor.enums.creature.InstanceType; +import org.l2jmobius.gameserver.entity.actor.templates.NpcTemplate; + +/** + * @version $Revision: $ $Date: $ + * @author Luca Baldi + */ +public class Adventurer extends Folk +{ + public Adventurer(NpcTemplate template) + { + super(template); + setInstanceType(InstanceType.Adventurer); + } + + @Override + public String getHtmlPath(int npcId, int value) + { + String pom = ""; + if (value == 0) + { + pom = Integer.toString(npcId); + } + else + { + pom = npcId + "-" + value; + } + + return "data/html/adventurer_guildsman/" + pom + ".htm"; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/Artefact.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/Artefact.java new file mode 100644 index 0000000..916be62 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/Artefact.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.instance; + +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.Npc; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.enums.creature.InstanceType; +import org.l2jmobius.gameserver.entity.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.mechanics.skill.Skill; +import org.l2jmobius.gameserver.network.serverpackets.ActionFailed; + +public class Artefact extends Npc +{ + /** + * Creates a castle siege artifact. + * @param template the artifact NPC template + */ + public Artefact(NpcTemplate template) + { + super(template); + setInstanceType(InstanceType.Artefact); + } + + @Override + public void onSpawn() + { + super.onSpawn(); + getCastle().registerArtefact(this); + } + + @Override + public boolean isArtefact() + { + return true; + } + + @Override + public boolean isAutoAttackable(Creature attacker) + { + return false; + } + + @Override + public boolean canBeAttacked() + { + return false; + } + + @Override + public void onForcedAttack(Player player) + { + // Send a Server->Client ActionFailed to the Player in order to avoid that the client wait another packet. + player.sendPacket(ActionFailed.STATIC_PACKET); + } + + @Override + public void reduceCurrentHp(double damage, Creature attacker, Skill skill) + { + } + + @Override + public void reduceCurrentHp(double damage, Creature attacker, boolean awake, boolean isDOT, Skill skill) + { + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/Auctioneer.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/Auctioneer.java new file mode 100644 index 0000000..f5473a0 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/Auctioneer.java @@ -0,0 +1,728 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.instance; + +import static org.l2jmobius.gameserver.entity.itemcontainer.Inventory.MAX_ADENA; + +import java.time.Instant; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.Calendar; +import java.util.List; +import java.util.Map; +import java.util.StringTokenizer; +import java.util.concurrent.ConcurrentHashMap; + +import org.l2jmobius.gameserver.data.sql.ClanHallTable; +import org.l2jmobius.gameserver.data.xml.MapRegionData; +import org.l2jmobius.gameserver.entity.actor.Npc; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.enums.creature.InstanceType; +import org.l2jmobius.gameserver.entity.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.entity.clan.ClanAccess; +import org.l2jmobius.gameserver.entity.residences.ClanHallAuction; +import org.l2jmobius.gameserver.entity.residences.ClanHallAuction.Bidder; +import org.l2jmobius.gameserver.managers.ClanHallAuctionManager; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.serverpackets.NpcHtmlMessage; + +public class Auctioneer extends Npc +{ + private static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm"); + private static final DateTimeFormatter DATE_FORMAT_SHORT = DateTimeFormatter.ofPattern("yy/MM/dd"); + + private static final int COND_ALL_FALSE = 0; + private static final int COND_BUSY_BECAUSE_OF_SIEGE = 1; + private static final int COND_REGULAR = 3; + + private final Map _pendingAuctions = new ConcurrentHashMap<>(); + + public Auctioneer(NpcTemplate template) + { + super(template); + setInstanceType(InstanceType.Auctioneer); + } + + @Override + public void onBypassFeedback(Player player, String command) + { + final int condition = validateCondition(); + if (condition <= COND_ALL_FALSE) + { + player.sendMessage("Wrong conditions."); + return; + } + else if (condition == COND_BUSY_BECAUSE_OF_SIEGE) + { + final String filename = "data/html/auction/auction-busy.htm"; + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, filename); + html.replace("%objectId%", String.valueOf(getObjectId())); + player.sendPacket(html); + return; + } + else if (condition == COND_REGULAR) + { + final StringTokenizer st = new StringTokenizer(command, " "); + final String actualCommand = st.nextToken(); // Get actual command + + String val = ""; + if (st.countTokens() >= 1) + { + val = st.nextToken(); + } + + if (actualCommand.equalsIgnoreCase("auction")) + { + if (val.isEmpty()) + { + return; + } + + try + { + final int days = Integer.parseInt(val); + try + { + int bid = 0; + if (st.countTokens() >= 1) + { + bid = Math.min(Integer.parseInt(st.nextToken()), MAX_ADENA); + } + + final ClanHallAuction a = new ClanHallAuction(player.getClan().getHideoutId(), player.getClan(), days * 86400000, bid, ClanHallTable.getInstance().getClanHallByOwner(player.getClan()).getName()); + if (_pendingAuctions.get(a.getId()) != null) + { + _pendingAuctions.remove(a.getId()); + } + + _pendingAuctions.put(a.getId(), a); + + final String filename = "data/html/auction/AgitSale3.htm"; + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, filename); + html.replace("%x%", val); + html.replace("%AGIT_AUCTION_END%", Instant.ofEpochMilli(a.getEndDate()).atZone(ZoneId.systemDefault()).format(DATE_FORMAT)); + html.replace("%AGIT_AUCTION_MINBID%", String.valueOf(a.getStartingBid())); + html.replace("%AGIT_AUCTION_MIN%", String.valueOf(a.getStartingBid())); + html.replace("%AGIT_AUCTION_DESC%", ClanHallTable.getInstance().getClanHallByOwner(player.getClan()).getDesc()); + html.replace("%AGIT_LINK_BACK%", "bypass -h npc_" + getObjectId() + "_sale2"); + html.replace("%objectId%", String.valueOf((getObjectId()))); + player.sendPacket(html); + } + catch (Exception e) + { + player.sendMessage("Invalid bid!"); + } + } + catch (Exception e) + { + player.sendMessage("Invalid auction duration!"); + } + return; + } + else if (actualCommand.equalsIgnoreCase("confirmAuction")) + { + try + { + final ClanHallAuction a = _pendingAuctions.get(player.getClan().getHideoutId()); + a.confirmAuction(); + _pendingAuctions.remove(player.getClan().getHideoutId()); + } + catch (Exception e) + { + player.sendMessage("Invalid auction"); + } + return; + } + else if (actualCommand.equalsIgnoreCase("bidding")) + { + if (val.isEmpty()) + { + return; + } + + try + { + final int auctionId = Integer.parseInt(val); + + final String filename = "data/html/auction/AgitAuctionInfo.htm"; + final ClanHallAuction a = ClanHallAuctionManager.getInstance().getAuction(auctionId); + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, filename); + if (a != null) + { + html.replace("%AGIT_NAME%", a.getItemName()); + html.replace("%OWNER_PLEDGE_NAME%", a.getSellerClanName()); + html.replace("%OWNER_PLEDGE_MASTER%", a.getSellerName()); + html.replace("%AGIT_SIZE%", String.valueOf(ClanHallTable.getInstance().getAuctionableHallById(a.getItemId()).getGrade() * 10)); + html.replace("%AGIT_LEASE%", String.valueOf(ClanHallTable.getInstance().getAuctionableHallById(a.getItemId()).getLease())); + html.replace("%AGIT_LOCATION%", ClanHallTable.getInstance().getAuctionableHallById(a.getItemId()).getLocation()); + html.replace("%AGIT_AUCTION_END%", Instant.ofEpochMilli(a.getEndDate()).atZone(ZoneId.systemDefault()).format(DATE_FORMAT)); + html.replace("%AGIT_AUCTION_REMAIN%", ((a.getEndDate() - System.currentTimeMillis()) / 3600000) + " hours " + (((a.getEndDate() - System.currentTimeMillis()) / 60000) % 60) + " minutes"); + html.replace("%AGIT_AUCTION_MINBID%", String.valueOf(a.getStartingBid())); + html.replace("%AGIT_AUCTION_COUNT%", String.valueOf(a.getBidders().size())); + html.replace("%AGIT_AUCTION_DESC%", ClanHallTable.getInstance().getAuctionableHallById(a.getItemId()).getDesc()); + html.replace("%AGIT_LINK_BACK%", "bypass -h npc_" + getObjectId() + "_list"); + html.replace("%AGIT_LINK_BIDLIST%", "bypass -h npc_" + getObjectId() + "_bidlist " + a.getId()); + html.replace("%AGIT_LINK_RE%", "bypass -h npc_" + getObjectId() + "_bid1 " + a.getId()); + } + else + { + LOGGER.warning("Auctioneer Auction null for AuctionId : " + auctionId); + } + + player.sendPacket(html); + } + catch (Exception e) + { + player.sendMessage("Invalid auction!"); + } + return; + } + else if (actualCommand.equalsIgnoreCase("bid")) + { + if (val.isEmpty()) + { + return; + } + + try + { + final int auctionId = Integer.parseInt(val); + try + { + int bid = 0; + if (st.countTokens() >= 1) + { + bid = Math.min(Integer.parseInt(st.nextToken()), MAX_ADENA); + } + + ClanHallAuctionManager.getInstance().getAuction(auctionId).setBid(player, bid); + } + catch (Exception e) + { + player.sendMessage("Invalid bid!"); + } + } + catch (Exception e) + { + player.sendMessage("Invalid auction!"); + } + return; + } + else if (actualCommand.equalsIgnoreCase("bid1")) + { + if ((player.getClan() == null) || (player.getClan().getLevel() < 2)) + { + player.sendPacket(SystemMessageId.ONLY_A_CLAN_LEADER_WHOSE_CLAN_IS_OF_LEVEL_2_OR_HIGHER_IS_ALLOWED_TO_PARTICIPATE_IN_A_CLAN_HALL_AUCTION); + return; + } + + if (val.isEmpty()) + { + return; + } + + if (((player.getClan().getAuctionBiddedAt() > 0) && (player.getClan().getAuctionBiddedAt() != Integer.parseInt(val))) || (player.getClan().getHideoutId() > 0)) + { + player.sendPacket(SystemMessageId.SINCE_YOU_HAVE_ALREADY_SUBMITTED_A_BID_YOU_ARE_NOT_ALLOWED_TO_PARTICIPATE_IN_ANOTHER_AUCTION_AT_THIS_TIME); + return; + } + + try + { + final String filename = "data/html/auction/AgitBid1.htm"; + + long minimumBid = ClanHallAuctionManager.getInstance().getAuction(Integer.parseInt(val)).getHighestBidderMaxBid(); + if (minimumBid == 0) + { + minimumBid = ClanHallAuctionManager.getInstance().getAuction(Integer.parseInt(val)).getStartingBid(); + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, filename); + html.replace("%AGIT_LINK_BACK%", "bypass -h npc_" + getObjectId() + "_bidding " + val); + html.replace("%PLEDGE_ADENA%", String.valueOf(player.getClan().getWarehouse().getAdena())); + html.replace("%AGIT_AUCTION_MINBID%", String.valueOf(minimumBid)); + html.replace("npc_%objectId%_bid", "npc_" + getObjectId() + "_bid " + val); + player.sendPacket(html); + } + catch (Exception e) + { + player.sendMessage("Invalid auction!"); + } + return; + } + else if (actualCommand.equalsIgnoreCase("list")) + { + final List auctions = ClanHallAuctionManager.getInstance().getAuctions(); + /** Limit for make new page, prevent client crash **/ + int limit = 15; + int start; + int i = 1; + final int npage = Math.ceilDiv(auctions.size(), limit); + + if (val.isEmpty()) + { + start = 1; + } + else + { + start = (limit * (Integer.parseInt(val) - 1)) + 1; + limit *= Integer.parseInt(val); + } + + final StringBuilder items = new StringBuilder(); + items.append(""); + for (int j = 1; j <= npage; j++) + { + items.append(""); + } + + items.append("
    Page "); + items.append(j); + items.append("
    "); + items.append(""); + + for (ClanHallAuction a : auctions) + { + if (a == null) + { + continue; + } + + if (i > limit) + { + break; + } + else if (i < start) + { + i++; + continue; + } + else + { + i++; + } + + items.append(""); + items.append(""); + items.append(""); + items.append(""); + items.append(""); + items.append(""); + } + + items.append("
    "); + items.append(ClanHallTable.getInstance().getAuctionableHallById(a.getItemId()).getLocation()); + items.append(""); + items.append(a.getItemName()); + items.append("" + Instant.ofEpochMilli(a.getEndDate()).atZone(ZoneId.systemDefault()).format(DATE_FORMAT_SHORT)); + items.append(""); + items.append(a.getStartingBid()); + items.append("
    "); + final String filename = "data/html/auction/AgitAuctionList.htm"; + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, filename); + html.replace("%AGIT_LINK_BACK%", "bypass -h npc_" + getObjectId() + "_start"); + html.replace("%itemsField%", items.toString()); + player.sendPacket(html); + return; + } + else if (actualCommand.equalsIgnoreCase("bidlist")) + { + int auctionId = 0; + if (val.isEmpty()) + { + if (player.getClan().getAuctionBiddedAt() <= 0) + { + return; + } + + auctionId = player.getClan().getAuctionBiddedAt(); + } + else + { + auctionId = Integer.parseInt(val); + } + + String biders = ""; + final Map bidders = ClanHallAuctionManager.getInstance().getAuction(auctionId).getBidders(); + for (Bidder b : bidders.values()) + { + biders += "" + b.getClanName() + "" + b.getName() + "" + b.getTimeBid().get(Calendar.YEAR) + "/" + (b.getTimeBid().get(Calendar.MONTH) + 1) + "/" + b.getTimeBid().get(Calendar.DATE) + "" + b.getBid() + ""; + } + + final String filename = "data/html/auction/AgitBidderList.htm"; + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, filename); + html.replace("%AGIT_LIST%", biders); + html.replace("%AGIT_LINK_BACK%", "bypass -h npc_" + getObjectId() + "_selectedItems"); + html.replace("%x%", val); + html.replace("%objectId%", String.valueOf(getObjectId())); + player.sendPacket(html); + return; + } + else if (actualCommand.equalsIgnoreCase("selectedItems")) + { + if ((player.getClan() != null) && (player.getClan().getHideoutId() == 0) && (player.getClan().getAuctionBiddedAt() > 0)) + { + final String filename = "data/html/auction/AgitBidInfo.htm"; + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, filename); + final ClanHallAuction a = ClanHallAuctionManager.getInstance().getAuction(player.getClan().getAuctionBiddedAt()); + if (a != null) + { + html.replace("%AGIT_NAME%", a.getItemName()); + html.replace("%OWNER_PLEDGE_NAME%", a.getSellerClanName()); + html.replace("%OWNER_PLEDGE_MASTER%", a.getSellerName()); + html.replace("%AGIT_SIZE%", String.valueOf(ClanHallTable.getInstance().getAuctionableHallById(a.getItemId()).getGrade() * 10)); + html.replace("%AGIT_LEASE%", String.valueOf(ClanHallTable.getInstance().getAuctionableHallById(a.getItemId()).getLease())); + html.replace("%AGIT_LOCATION%", ClanHallTable.getInstance().getAuctionableHallById(a.getItemId()).getLocation()); + html.replace("%AGIT_AUCTION_END%", Instant.ofEpochMilli(a.getEndDate()).atZone(ZoneId.systemDefault()).format(DATE_FORMAT)); + html.replace("%AGIT_AUCTION_REMAIN%", ((a.getEndDate() - System.currentTimeMillis()) / 3600000) + " hours " + (((a.getEndDate() - System.currentTimeMillis()) / 60000) % 60) + " minutes"); + html.replace("%AGIT_AUCTION_MINBID%", String.valueOf(a.getStartingBid())); + html.replace("%AGIT_AUCTION_MYBID%", String.valueOf(a.getBidders().get(player.getClanId()).getBid())); + html.replace("%AGIT_AUCTION_DESC%", ClanHallTable.getInstance().getAuctionableHallById(a.getItemId()).getDesc()); + html.replace("%objectId%", String.valueOf(getObjectId())); + html.replace("%AGIT_LINK_BACK%", "bypass -h npc_" + getObjectId() + "_start"); + } + else + { + LOGGER.warning("Auctioneer Auction null for AuctionBiddedAt : " + player.getClan().getAuctionBiddedAt()); + } + + player.sendPacket(html); + return; + } + else if ((player.getClan() != null) && (ClanHallAuctionManager.getInstance().getAuction(player.getClan().getHideoutId()) != null)) + { + final String filename = "data/html/auction/AgitSaleInfo.htm"; + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, filename); + final ClanHallAuction a = ClanHallAuctionManager.getInstance().getAuction(player.getClan().getHideoutId()); + if (a != null) + { + html.replace("%AGIT_NAME%", a.getItemName()); + html.replace("%AGIT_OWNER_PLEDGE_NAME%", a.getSellerClanName()); + html.replace("%OWNER_PLEDGE_MASTER%", a.getSellerName()); + html.replace("%AGIT_SIZE%", String.valueOf(ClanHallTable.getInstance().getAuctionableHallById(a.getItemId()).getGrade() * 10)); + html.replace("%AGIT_LEASE%", String.valueOf(ClanHallTable.getInstance().getAuctionableHallById(a.getItemId()).getLease())); + html.replace("%AGIT_LOCATION%", ClanHallTable.getInstance().getAuctionableHallById(a.getItemId()).getLocation()); + html.replace("%AGIT_AUCTION_END%", Instant.ofEpochMilli(a.getEndDate()).atZone(ZoneId.systemDefault()).format(DATE_FORMAT)); + html.replace("%AGIT_AUCTION_REMAIN%", ((a.getEndDate() - System.currentTimeMillis()) / 3600000) + " hours " + (((a.getEndDate() - System.currentTimeMillis()) / 60000) % 60) + " minutes"); + html.replace("%AGIT_AUCTION_MINBID%", String.valueOf(a.getStartingBid())); + html.replace("%AGIT_AUCTION_BIDCOUNT%", String.valueOf(a.getBidders().size())); + html.replace("%AGIT_AUCTION_DESC%", ClanHallTable.getInstance().getAuctionableHallById(a.getItemId()).getDesc()); + html.replace("%AGIT_LINK_BACK%", "bypass -h npc_" + getObjectId() + "_start"); + html.replace("%id%", String.valueOf(a.getId())); + html.replace("%objectId%", String.valueOf(getObjectId())); + } + else + { + LOGGER.warning("Auctioneer Auction null for getHideoutId : " + player.getClan().getHideoutId()); + } + + player.sendPacket(html); + return; + } + else if ((player.getClan() != null) && (player.getClan().getHideoutId() != 0)) + { + final int ItemId = player.getClan().getHideoutId(); + final String filename = "data/html/auction/AgitInfo.htm"; + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, filename); + if (ClanHallTable.getInstance().getAuctionableHallById(ItemId) != null) + { + html.replace("%AGIT_NAME%", ClanHallTable.getInstance().getAuctionableHallById(ItemId).getName()); + html.replace("%AGIT_OWNER_PLEDGE_NAME%", player.getClan().getName()); + html.replace("%OWNER_PLEDGE_MASTER%", player.getClan().getLeaderName()); + html.replace("%AGIT_SIZE%", String.valueOf(ClanHallTable.getInstance().getAuctionableHallById(ItemId).getGrade() * 10)); + html.replace("%AGIT_LEASE%", String.valueOf(ClanHallTable.getInstance().getAuctionableHallById(ItemId).getLease())); + html.replace("%AGIT_LOCATION%", ClanHallTable.getInstance().getAuctionableHallById(ItemId).getLocation()); + html.replace("%AGIT_LINK_BACK%", "bypass -h npc_" + getObjectId() + "_start"); + html.replace("%objectId%", String.valueOf(getObjectId())); + } + else + { + LOGGER.warning("Clan Hall ID NULL : " + ItemId + " Can be caused by concurent write in ClanHallManager"); + } + + player.sendPacket(html); + return; + } + else if ((player.getClan() != null) && (player.getClan().getHideoutId() == 0)) + { + player.sendPacket(SystemMessageId.THERE_ARE_NO_OFFERINGS_I_OWN_OR_I_MADE_A_BID_FOR); + return; + } + else if (player.getClan() == null) + { + player.sendPacket(SystemMessageId.YOU_CANNOT_PARTICIPATE_IN_AN_AUCTION); + return; + } + } + else if (actualCommand.equalsIgnoreCase("cancelBid")) + { + final long bid = ClanHallAuctionManager.getInstance().getAuction(player.getClan().getAuctionBiddedAt()).getBidders().get(player.getClanId()).getBid(); + final String filename = "data/html/auction/AgitBidCancel.htm"; + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, filename); + html.replace("%AGIT_BID%", String.valueOf(bid)); + html.replace("%AGIT_BID_REMAIN%", String.valueOf((long) (bid * 0.9))); + html.replace("%AGIT_LINK_BACK%", "bypass -h npc_" + getObjectId() + "_selectedItems"); + html.replace("%objectId%", String.valueOf(getObjectId())); + player.sendPacket(html); + return; + } + else if (actualCommand.equalsIgnoreCase("doCancelBid")) + { + if (ClanHallAuctionManager.getInstance().getAuction(player.getClan().getAuctionBiddedAt()) != null) + { + ClanHallAuctionManager.getInstance().getAuction(player.getClan().getAuctionBiddedAt()).cancelBid(player.getClanId()); + player.sendPacket(SystemMessageId.YOU_HAVE_CANCELED_YOUR_BID); + } + return; + } + else if (actualCommand.equalsIgnoreCase("cancelAuction")) + { + if (!player.hasAccess(ClanAccess.HALL_AUCTION)) + { + final String filename = "data/html/auction/not_authorized.htm"; + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, filename); + html.replace("%objectId%", String.valueOf(getObjectId())); + player.sendPacket(html); + return; + } + + final String filename = "data/html/auction/AgitSaleCancel.htm"; + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, filename); + html.replace("%AGIT_DEPOSIT%", String.valueOf(ClanHallTable.getInstance().getClanHallByOwner(player.getClan()).getLease())); + html.replace("%AGIT_LINK_BACK%", "bypass -h npc_" + getObjectId() + "_selectedItems"); + html.replace("%objectId%", String.valueOf(getObjectId())); + player.sendPacket(html); + return; + } + else if (actualCommand.equalsIgnoreCase("doCancelAuction")) + { + if (ClanHallAuctionManager.getInstance().getAuction(player.getClan().getHideoutId()) != null) + { + ClanHallAuctionManager.getInstance().getAuction(player.getClan().getHideoutId()).cancelAuction(); + player.sendMessage("Your auction has been canceled"); + } + return; + } + else if (actualCommand.equalsIgnoreCase("sale2")) + { + final String filename = "data/html/auction/AgitSale2.htm"; + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, filename); + html.replace("%AGIT_LAST_PRICE%", String.valueOf(ClanHallTable.getInstance().getClanHallByOwner(player.getClan()).getLease())); + html.replace("%AGIT_LINK_BACK%", "bypass -h npc_" + getObjectId() + "_sale"); + html.replace("%objectId%", String.valueOf(getObjectId())); + player.sendPacket(html); + return; + } + else if (actualCommand.equalsIgnoreCase("sale")) + { + if (!player.hasAccess(ClanAccess.HALL_AUCTION)) + { + final String filename = "data/html/auction/not_authorized.htm"; + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, filename); + html.replace("%objectId%", String.valueOf(getObjectId())); + player.sendPacket(html); + return; + } + + final String filename = "data/html/auction/AgitSale1.htm"; + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, filename); + html.replace("%AGIT_DEPOSIT%", String.valueOf(ClanHallTable.getInstance().getClanHallByOwner(player.getClan()).getLease())); + html.replace("%AGIT_PLEDGE_ADENA%", String.valueOf(player.getClan().getWarehouse().getAdena())); + html.replace("%AGIT_LINK_BACK%", "bypass -h npc_" + getObjectId() + "_selectedItems"); + html.replace("%objectId%", String.valueOf(getObjectId())); + player.sendPacket(html); + return; + } + else if (actualCommand.equalsIgnoreCase("rebid")) + { + if (!player.hasAccess(ClanAccess.HALL_AUCTION)) + { + final String filename = "data/html/auction/not_authorized.htm"; + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, filename); + html.replace("%objectId%", String.valueOf(getObjectId())); + player.sendPacket(html); + return; + } + + try + { + final String filename = "data/html/auction/AgitBid2.htm"; + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, filename); + final ClanHallAuction a = ClanHallAuctionManager.getInstance().getAuction(player.getClan().getAuctionBiddedAt()); + if (a != null) + { + html.replace("%AGIT_AUCTION_BID%", String.valueOf(a.getBidders().get(player.getClanId()).getBid())); + html.replace("%AGIT_AUCTION_MINBID%", String.valueOf(a.getStartingBid())); + html.replace("%AGIT_AUCTION_END%", Instant.ofEpochMilli(a.getEndDate()).atZone(ZoneId.systemDefault()).format(DATE_FORMAT)); + html.replace("%AGIT_LINK_BACK%", "bypass -h npc_" + getObjectId() + "_selectedItems"); + html.replace("npc_%objectId%_bid1", "npc_" + getObjectId() + "_bid1 " + a.getId()); + } + else + { + LOGGER.warning("Auctioneer Auction null for AuctionBiddedAt : " + player.getClan().getAuctionBiddedAt()); + } + + player.sendPacket(html); + } + catch (Exception e) + { + player.sendMessage("Invalid auction!"); + } + return; + } + else if (actualCommand.equalsIgnoreCase("location")) + { + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, "data/html/auction/location.htm"); + html.replace("%location%", MapRegionData.getInstance().getClosestTownName(player)); + html.replace("%LOCATION%", getPictureName(player)); + html.replace("%AGIT_LINK_BACK%", "bypass -h npc_" + getObjectId() + "_start"); + player.sendPacket(html); + return; + } + else if (actualCommand.equalsIgnoreCase("start")) + { + showChatWindow(player); + return; + } + } + + super.onBypassFeedback(player, command); + } + + @Override + public void showChatWindow(Player player) + { + String filename; // = "data/html/auction/auction-no.htm"; + + final int condition = validateCondition(); + if (condition == COND_BUSY_BECAUSE_OF_SIEGE) + { + filename = "data/html/auction/auction-busy.htm"; // Busy because of siege + } + else + { + filename = "data/html/auction/auction.htm"; + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, filename); + html.replace("%objectId%", String.valueOf(getObjectId())); + html.replace("%npcId%", String.valueOf(getId())); + html.replace("%npcname%", getName()); + player.sendPacket(html); + } + + private int validateCondition() + { + if ((getCastle() != null) && (getCastle().getResidenceId() > 0)) + { + if (getCastle().getSiege().isInProgress()) + { + return COND_BUSY_BECAUSE_OF_SIEGE; // Busy because of siege + } + + return COND_REGULAR; + } + + return COND_ALL_FALSE; + } + + private String getPictureName(Player plyr) + { + final int nearestTownId = MapRegionData.getInstance().getMapRegionLocId(plyr); + String nearestTown; + + switch (nearestTownId) + { + case 911: + { + nearestTown = "GLUDIN"; + break; + } + case 912: + { + nearestTown = "GLUDIO"; + break; + } + case 916: + { + nearestTown = "DION"; + break; + } + case 918: + { + nearestTown = "GIRAN"; + break; + } + case 1537: + { + nearestTown = "RUNE"; + break; + } + case 1538: + { + nearestTown = "GODARD"; + break; + } + case 1714: + { + nearestTown = "SCHUTTGART"; + break; + } + default: + { + nearestTown = "ADEN"; + break; + } + } + + return nearestTown; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/BabyPet.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/BabyPet.java new file mode 100644 index 0000000..2c1261c --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/BabyPet.java @@ -0,0 +1,371 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.instance; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Future; + +import org.l2jmobius.commons.threads.ThreadPool; +import org.l2jmobius.commons.util.Rnd; +import org.l2jmobius.gameserver.ai.Intention; +import org.l2jmobius.gameserver.data.enums.CategoryType; +import org.l2jmobius.gameserver.data.holders.PetData.PetSkillLearn; +import org.l2jmobius.gameserver.data.xml.PetDataTable; +import org.l2jmobius.gameserver.data.xml.SkillData; +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.enums.creature.InstanceType; +import org.l2jmobius.gameserver.entity.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.entity.item.instance.Item; +import org.l2jmobius.gameserver.mechanics.effects.EffectType; +import org.l2jmobius.gameserver.mechanics.skill.BuffInfo; +import org.l2jmobius.gameserver.mechanics.skill.Skill; +import org.l2jmobius.gameserver.mechanics.skill.holders.SkillHolder; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; + +public class BabyPet extends Pet +{ + private static final int BUFF_CONTROL = 5771; + private static final int AWAKENING = 5753; + + protected List _buffs = null; + protected SkillHolder _majorHeal = null; + protected SkillHolder _minorHeal = null; + protected SkillHolder _recharge = null; + + private Future _castTask; + + protected boolean _bufferMode = true; + + /** + * Creates a baby pet. + * @param template the baby pet NPC template + * @param owner the owner + * @param control the summoning item + */ + public BabyPet(NpcTemplate template, Player owner, Item control) + { + super(template, owner, control); + setInstanceType(InstanceType.BabyPet); + } + + /** + * Creates a baby pet. + * @param template the baby pet NPC template + * @param owner the owner + * @param control the summoning item + * @param level the level + */ + public BabyPet(NpcTemplate template, Player owner, Item control, byte level) + { + super(template, owner, control, level); + setInstanceType(InstanceType.BabyPet); + } + + @Override + public void onSpawn() + { + super.onSpawn(); + + double healPower = 0; + for (PetSkillLearn psl : PetDataTable.getInstance().getPetData(getId()).getAvailableSkills()) + { + final int id = psl.getSkillId(); + final int level = PetDataTable.getInstance().getPetData(getId()).getAvailableLevel(id, getLevel()); + if (level == 0) + { + continue; + } + + final Skill skill = SkillData.getInstance().getSkill(id, level); + if (skill == null) + { + continue; + } + + if ((skill.getId() == BUFF_CONTROL) || (skill.getId() == AWAKENING)) + { + continue; + } + + if (skill.hasEffectType(EffectType.MANAHEAL_BY_LEVEL)) + { + _recharge = new SkillHolder(skill); + continue; + } + + if (skill.hasEffectType(EffectType.HEAL)) + { + if (healPower == 0) + { + // Set both heal types to the same skill. + _majorHeal = new SkillHolder(skill); + _minorHeal = _majorHeal; + healPower = skill.getPower(); + } + else + { + // another heal skill found - search for most powerful + if (skill.getPower() > healPower) + { + _majorHeal = new SkillHolder(skill); + } + else + { + _minorHeal = new SkillHolder(skill); + } + } + continue; + } + + if (skill.isContinuous() && !skill.isDebuff()) + { + if (_buffs == null) + { + _buffs = new ArrayList<>(); + } + + _buffs.add(new SkillHolder(skill)); + } + } + + startCastTask(); + } + + @Override + public boolean doDie(Creature killer) + { + if (!super.doDie(killer)) + { + return false; + } + + stopCastTask(); + abortCast(); + return true; + } + + @Override + public synchronized void unSummon(Player owner) + { + stopCastTask(); + abortCast(); + super.unSummon(owner); + } + + @Override + public void doRevive() + { + super.doRevive(); + startCastTask(); + } + + @Override + public void onDecay() + { + super.onDecay(); + + if (_buffs != null) + { + _buffs.clear(); + } + } + + private void startCastTask() + { + if ((_majorHeal != null) || (_buffs != null) || ((_recharge != null) && (_castTask == null) && !isDead())) + { + _castTask = ThreadPool.scheduleAtFixedRate(new CastTask(this), 3000, 2000); + } + } + + @Override + public void switchMode() + { + _bufferMode = !_bufferMode; + } + + /** + * Verify if this pet is in support mode. + * @return {@code true} if this baby pet is in support mode, {@code false} otherwise + */ + public boolean isInSupportMode() + { + return _bufferMode; + } + + private void stopCastTask() + { + if (_castTask != null) + { + _castTask.cancel(false); + _castTask = null; + } + } + + protected void castSkill(Skill skill) + { + // casting automatically stops any other action (such as autofollow or a move-to). + // We need to gather the necessary info to restore the previous state. + final boolean previousFollowStatus = getFollowStatus(); + + // Pet not following and owner outside cast range. + if (!previousFollowStatus && !isInsideRadius3D(getOwner(), skill.getCastRange())) + { + return; + } + + setTarget(getOwner()); + useMagic(skill, false, false); + + final SystemMessage msg = new SystemMessage(SystemMessageId.YOUR_PET_USES_S1); + msg.addSkillName(skill); + sendPacket(msg); + + // calling useMagic changes the follow status, if the babypet actually casts + // (as opposed to failing due some factors, such as too low MP, etc). + // if the status has actually been changed, revert it. Else, allow the pet to + // continue whatever it was trying to do. + // NOTE: This is important since the pet may have been told to attack a target. + // reverting the follow status will abort this attack! While aborting the attack + // in order to heal is natural, it is not acceptable to abort the attack on its own, + // merely because the timer stroke and without taking any other action... + if (previousFollowStatus != getFollowStatus()) + { + setFollowStatus(previousFollowStatus); + } + } + + private class CastTask implements Runnable + { + private final BabyPet _baby; + private final List _currentBuffs = new ArrayList<>(); + + public CastTask(BabyPet baby) + { + _baby = baby; + } + + @Override + public void run() + { + final Player owner = _baby.getOwner(); + + // If the owner doesn't meet the conditions avoid casting. + if ((owner == null) || owner.isDead() || owner.isInvul()) + { + return; + } + + // If the pet doesn't meet the conditions avoid casting. + if (_baby.isDead() || _baby.isCastingNow() || _baby.isBetrayed() || _baby.isMuted() || _baby.isOutOfControl() || !_bufferMode || (_baby.getAI().getIntention() == Intention.CAST)) + { + return; + } + + Skill skill = null; + if (_majorHeal != null) + { + // If the owner's HP is more than 80% for Baby Pets and 70% for Improved Baby pets, do nothing. + // If the owner's HP is very low, under 15% for Baby pets and under 30% for Improved Baby Pets, have 75% chances of using a strong heal. + // Otherwise, have 25% chances for weak heal. + final double hpPercent = owner.getCurrentHp() / owner.getMaxHp(); + final boolean isImprovedBaby = isInCategory(CategoryType.BABY_PET_GROUP); + if ((isImprovedBaby && (hpPercent < 0.3)) || (!isImprovedBaby && (hpPercent < 0.15))) + { + skill = _majorHeal.getSkill(); + if (!_baby.isSkillDisabled(skill) && (Rnd.get(100) <= 75) && (_baby.getCurrentMp() >= skill.getMpConsume())) + { + castSkill(skill); + return; + } + } + else if ((_majorHeal.getSkill() != _minorHeal.getSkill()) && ((isImprovedBaby && (hpPercent < 0.7)) || (!isImprovedBaby && (hpPercent < 0.8)))) + { + // Cast _minorHeal only if it's different than _majorHeal, then pet has two heals available. + skill = _minorHeal.getSkill(); + if (!_baby.isSkillDisabled(skill) && (Rnd.get(100) <= 25) && (_baby.getCurrentMp() >= skill.getMpConsume())) + { + castSkill(skill); + return; + } + } + } + + // Buff Control is not active. + if (!_baby.isAffectedBySkill(BUFF_CONTROL)) + { + // searching for usable buffs + if ((_buffs != null) && !_buffs.isEmpty()) + { + for (SkillHolder buff : _buffs) + { + skill = buff.getSkill(); + if (_baby.isSkillDisabled(skill)) + { + continue; + } + + if (_baby.getCurrentMp() < skill.getMpConsume()) + { + continue; + } + + // If owner already have the buff, continue. + final BuffInfo buffInfo = owner.getEffectList().getBuffInfoByAbnormalType(skill.getAbnormalType()); + if ((buffInfo != null) && (skill.getAbnormalLevel() <= buffInfo.getSkill().getAbnormalLevel())) + { + continue; + } + + // If owner have the buff blocked, continue. + if ((owner.getEffectList().getBlockedAbnormalTypes() != null) && owner.getEffectList().getBlockedAbnormalTypes().contains(skill.getAbnormalType())) + { + continue; + } + + _currentBuffs.add(skill); + } + } + + if (!_currentBuffs.isEmpty()) + { + skill = _currentBuffs.get(Rnd.get(_currentBuffs.size())); + castSkill(skill); + _currentBuffs.clear(); + return; + } + } + + // buffs/heal not casted, trying recharge, if exist recharge casted only if owner in combat stance. + if ((_recharge != null) && owner.isInCombat() && ((owner.getCurrentMp() / owner.getMaxMp()) < 0.6) && (Rnd.get(100) <= 60)) + { + skill = _recharge.getSkill(); + if (!_baby.isSkillDisabled(skill) && (_baby.getCurrentMp() >= skill.getMpConsume())) + { + castSkill(skill); + } + } + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/Boat.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/Boat.java new file mode 100644 index 0000000..ddee50a --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/Boat.java @@ -0,0 +1,103 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.actor.instance; + +import java.util.logging.Logger; + +import org.l2jmobius.gameserver.ai.BoatAI; +import org.l2jmobius.gameserver.entity.Location; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.Vehicle; +import org.l2jmobius.gameserver.entity.actor.enums.creature.InstanceType; +import org.l2jmobius.gameserver.entity.actor.templates.CreatureTemplate; +import org.l2jmobius.gameserver.network.serverpackets.VehicleDeparture; +import org.l2jmobius.gameserver.network.serverpackets.VehicleInfo; +import org.l2jmobius.gameserver.network.serverpackets.VehicleStarted; + +/** + * @author Maktakien, DS + */ +public class Boat extends Vehicle +{ + protected static final Logger LOGGER_BOAT = Logger.getLogger(Boat.class.getName()); + + /** + * Creates a boat. + * @param template the boat template + */ + public Boat(CreatureTemplate template) + { + super(template); + setInstanceType(InstanceType.Boat); + setAI(new BoatAI(this)); + } + + @Override + public boolean isBoat() + { + return true; + } + + @Override + public int getId() + { + return 0; + } + + @Override + public boolean moveToNextRoutePoint() + { + final boolean result = super.moveToNextRoutePoint(); + if (result) + { + broadcastPacket(new VehicleDeparture(this)); + } + + return result; + } + + @Override + public void oustPlayer(Player player) + { + super.oustPlayer(player); + + final Location loc = getOustLoc(); + if (player.isOnline()) + { + player.teleToLocation(loc.getX(), loc.getY(), loc.getZ()); + } + else + { + player.setXYZInvisible(loc.getX(), loc.getY(), loc.getZ()); // disconnects handling + } + } + + @Override + public void stopMove(Location loc) + { + super.stopMove(loc); + + broadcastPacket(new VehicleStarted(this, 0)); + broadcastPacket(new VehicleInfo(this)); + } + + @Override + public void sendInfo(Player player) + { + player.sendPacket(new VehicleInfo(this)); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/BroadcastingTower.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/BroadcastingTower.java new file mode 100644 index 0000000..acd6796 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/BroadcastingTower.java @@ -0,0 +1,73 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.actor.instance; + +import org.l2jmobius.gameserver.entity.actor.Npc; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.enums.creature.InstanceType; +import org.l2jmobius.gameserver.entity.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.network.serverpackets.NpcHtmlMessage; + +/** + * @author NightMarez + * @version $Revision: 1.3.2.2.2.5 $ $Date: 2005/03/27 15:29:32 $ + */ +public class BroadcastingTower extends Npc +{ + /** + * Creates an observation. + * @param template the observation NPC template + */ + public BroadcastingTower(NpcTemplate template) + { + super(template); + setInstanceType(InstanceType.BroadcastingTower); + } + + @Override + public void showChatWindow(Player player, int value) + { + String filename = null; + if (isInsideRadius2D(-79884, 86529, 0, 50) || isInsideRadius2D(-78858, 111358, 0, 50) || isInsideRadius2D(-76973, 87136, 0, 50) || isInsideRadius2D(-75850, 111968, 0, 50)) + { + if (value == 0) + { + filename = "data/html/observation/" + getId() + "-Oracle.htm"; + } + else + { + filename = "data/html/observation/" + getId() + "-Oracle-" + value + ".htm"; + } + } + else + { + if (value == 0) + { + filename = "data/html/observation/" + getId() + ".htm"; + } + else + { + filename = "data/html/observation/" + getId() + "-" + value + ".htm"; + } + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, filename); + html.replace("%objectId%", String.valueOf(getObjectId())); + player.sendPacket(html); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/CastleDoorman.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/CastleDoorman.java new file mode 100644 index 0000000..92b0028 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/CastleDoorman.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.instance; + +import java.util.StringTokenizer; + +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.enums.creature.InstanceType; +import org.l2jmobius.gameserver.entity.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.entity.clan.ClanAccess; +import org.l2jmobius.gameserver.mechanics.siege.clanhalls.SiegableHall; + +public class CastleDoorman extends Doorman +{ + public CastleDoorman(NpcTemplate template) + { + super(template); + setInstanceType(InstanceType.CastleDoorman); + } + + @Override + protected void openDoors(Player player, String command) + { + final StringTokenizer st = new StringTokenizer(command.substring(10), ", "); + st.nextToken(); + + while (st.hasMoreTokens()) + { + if (getConquerableHall() != null) + { + getConquerableHall().openCloseDoor(Integer.parseInt(st.nextToken()), true); + } + else + { + getCastle().openDoor(player, Integer.parseInt(st.nextToken())); + } + } + } + + @Override + protected void closeDoors(Player player, String command) + { + final StringTokenizer st = new StringTokenizer(command.substring(11), ", "); + st.nextToken(); + + while (st.hasMoreTokens()) + { + if (getConquerableHall() != null) + { + getConquerableHall().openCloseDoor(Integer.parseInt(st.nextToken()), false); + } + else + { + getCastle().closeDoor(player, Integer.parseInt(st.nextToken())); + } + } + } + + @Override + protected boolean isOwnerClan(Player player) + { + if ((player.getClan() != null) && player.hasAccess(ClanAccess.CASTLE_OPEN_DOOR)) + { + final SiegableHall hall = getConquerableHall(); + + // Save in variable because it's a costly call. + if (hall != null) + { + if (player.getClanId() == hall.getOwnerId()) + { + return true; + } + } + else if (getCastle() != null) + { + if (player.getClanId() == getCastle().getOwnerId()) + { + return true; + } + } + } + + return false; + } + + @Override + protected boolean isUnderSiege() + { + final SiegableHall hall = getConquerableHall(); + if (hall != null) + { + return hall.isInSiege(); + } + + return getCastle().getZone().isActive(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/Chest.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/Chest.java new file mode 100644 index 0000000..f79a048 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/Chest.java @@ -0,0 +1,162 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.actor.instance; + +import org.l2jmobius.gameserver.data.xml.NpcData; +import org.l2jmobius.gameserver.data.xml.SkillData; +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.enums.creature.InstanceType; +import org.l2jmobius.gameserver.entity.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.mechanics.skill.Skill; +import org.l2jmobius.gameserver.util.ArrayUtil; + +/** + * This class manages all chest. + * @author Julian + */ +public class Chest extends Monster +{ + // Chest NPC ids that are real boxes (openable with a key, explode when hit). + // Every other chest id is a mimic: it cannot be opened and turns hostile instead. + // @formatter:off + private static final int[] BOXES = + { + 18265, 18266, 18267, 18268, 18269, 18270, 18271, 18272, 18273, 18274, + 18275, 18276, 18277, 18278, 18279, 18280, 18281, 18282, 18283, 18284, + 18285, 18286, 18287, 18288, 18289, 18290, 18291, 18292, 18293, 18294, + 18295, 18296, 18297, 18298 + }; + // @formatter:on + + private final boolean _isBox; + private volatile boolean _isInteracted; + private volatile boolean _specialDrop; + + /** + * Creates a chest. + * @param template the chest NPC template + */ + public Chest(NpcTemplate template) + { + super(template); + setInstanceType(InstanceType.Chest); + _isBox = ArrayUtil.contains(BOXES, template.getId()); + setRandomWalking(false); + _specialDrop = false; + } + + @Override + public void onSpawn() + { + super.onSpawn(); + _isInteracted = false; + _specialDrop = false; + setMustRewardExpSp(true); + // onSpawn restores random walking from the template; a chest (box or mimic) must stay put until provoked. + setRandomWalking(false); + } + + /** + * @return {@code true} if this chest is a box (can be opened with a key and explodes when hit), {@code false} if it is a mimic. + */ + public boolean isBox() + { + return _isBox; + } + + public synchronized boolean isInteracted() + { + return _isInteracted; + } + + public synchronized void setInteracted() + { + _isInteracted = true; + } + + public synchronized void setSpecialDrop() + { + _specialDrop = true; + } + + @Override + public void doItemDrop(NpcTemplate npcTemplate, Creature lastAttacker) + { + int id = getTemplate().getId(); + if (!_specialDrop) + { + if ((id >= 18265) && (id <= 18286)) + { + id += 3536; + } + else if ((id == 18287) || (id == 18288)) + { + id = 21671; + } + else if ((id == 18289) || (id == 18290)) + { + id = 21694; + } + else if ((id == 18291) || (id == 18292)) + { + id = 21717; + } + else if ((id == 18293) || (id == 18294)) + { + id = 21740; + } + else if ((id == 18295) || (id == 18296)) + { + id = 21763; + } + else if ((id == 18297) || (id == 18298)) + { + id = 21786; + } + } + + super.doItemDrop(NpcData.getInstance().getTemplate(id), lastAttacker); + } + + /** + * Makes the chest explode by casting Treasure Bomb (skill 4143) on itself.
    + * The skill is an area effect centered on the chest, so it must target self - otherwise a distant attacker would be out of cast range and the chest would never explode. + */ + public void treasureBomb() + { + final int skillLevel = Math.max(1, Math.min(10, Math.round(getLevel() / 10f))); + final Skill skill = SkillData.getInstance().getSkill(4143, skillLevel); + if (skill != null) + { + setTarget(this); + doCast(skill); + } + } + + @Override + public boolean isMovementDisabled() + { + // A box never moves; a mimic must be able to chase whoever provokes it. + return _isBox || super.isMovementDisabled(); + } + + @Override + public boolean hasRandomAnimation() + { + return false; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/ClanHallDoorman.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/ClanHallDoorman.java new file mode 100644 index 0000000..b7e4c03 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/ClanHallDoorman.java @@ -0,0 +1,222 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.instance; + +import java.util.Arrays; +import java.util.StringTokenizer; + +import org.l2jmobius.gameserver.data.sql.ClanHallTable; +import org.l2jmobius.gameserver.data.sql.ClanTable; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.enums.creature.InstanceType; +import org.l2jmobius.gameserver.entity.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.entity.clan.Clan; +import org.l2jmobius.gameserver.entity.residences.ClanHall; +import org.l2jmobius.gameserver.network.serverpackets.ActionFailed; +import org.l2jmobius.gameserver.network.serverpackets.NpcHtmlMessage; + +public class ClanHallDoorman extends Doorman +{ + private volatile boolean _init = false; + private ClanHall _clanHall = null; + private boolean _hasEvolve = false; + + // List of clan halls with evolve function, should be sorted. + private static final int[] CH_WITH_EVOLVE = + { + 36, + 37, + 38, + 39, + 40, + 41, + 51, + 52, + 53, + 54, + 55, + 56, + 57 + }; + + /** + * Creates a clan hall doorman. + * @param template the doorman NPC template + */ + public ClanHallDoorman(NpcTemplate template) + { + super(template); + setInstanceType(InstanceType.ClanHallDoorman); + } + + @Override + public void onBypassFeedback(Player player, String command) + { + if (_hasEvolve && command.startsWith("evolve") && isOwnerClan(player)) + { + final StringTokenizer st = new StringTokenizer(command, " "); + if (st.countTokens() < 2) + { + return; + } + + st.nextToken(); + boolean ok = false; + switch (Integer.parseInt(st.nextToken())) + { + case 1: + { + ok = PetManager.doEvolve(player, this, 9882, 10307, 55); + break; + } + case 2: + { + ok = PetManager.doEvolve(player, this, 4422, 10308, 55); + break; + } + case 3: + { + ok = PetManager.doEvolve(player, this, 4423, 10309, 55); + break; + } + case 4: + { + ok = PetManager.doEvolve(player, this, 4424, 10310, 55); + break; + } + case 5: + { + ok = PetManager.doEvolve(player, this, 10426, 10611, 70); + break; + } + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + if (ok) + { + html.setFile(player, "data/html/clanHallDoorman/evolve-ok.htm"); + } + else + { + html.setFile(player, "data/html/clanHallDoorman/evolve-no.htm"); + } + + player.sendPacket(html); + return; + } + + super.onBypassFeedback(player, command); + } + + @Override + public void showChatWindow(Player player) + { + player.sendPacket(ActionFailed.STATIC_PACKET); + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + if (getClanHall() != null) + { + final Clan owner = ClanTable.getInstance().getClan(getClanHall().getOwnerId()); + if (isOwnerClan(player)) + { + if (_hasEvolve) + { + html.setFile(player, "data/html/clanHallDoorman/doorman2.htm"); + html.replace("%clanname%", owner.getName()); + } + else + { + html.setFile(player, "data/html/clanHallDoorman/doorman1.htm"); + html.replace("%clanname%", owner.getName()); + } + } + else + { + if ((owner != null) && (owner.getLeader() != null)) + { + html.setFile(player, "data/html/clanHallDoorman/doorman-no.htm"); + html.replace("%leadername%", owner.getLeaderName()); + html.replace("%clanname%", owner.getName()); + } + else + { + html.setFile(player, "data/html/clanHallDoorman/emptyowner.htm"); + html.replace("%hallname%", getClanHall().getName()); + } + } + } + else + { + return; + } + + html.replace("%objectId%", String.valueOf(getObjectId())); + player.sendPacket(html); + } + + @Override + protected void openDoors(Player player, String command) + { + getClanHall().openCloseDoors(true); + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, "data/html/clanHallDoorman/doorman-opened.htm"); + html.replace("%objectId%", String.valueOf(getObjectId())); + player.sendPacket(html); + } + + @Override + protected void closeDoors(Player player, String command) + { + getClanHall().openCloseDoors(false); + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, "data/html/clanHallDoorman/doorman-closed.htm"); + html.replace("%objectId%", String.valueOf(getObjectId())); + player.sendPacket(html); + } + + private ClanHall getClanHall() + { + if (!_init) + { + synchronized (this) + { + if (!_init) + { + _clanHall = ClanHallTable.getInstance().getNearbyClanHall(getX(), getY(), 500); + if (_clanHall != null) + { + _hasEvolve = Arrays.binarySearch(CH_WITH_EVOLVE, _clanHall.getId()) >= 0; + } + + _init = true; + } + } + } + + return _clanHall; + } + + @Override + protected boolean isOwnerClan(Player player) + { + return (player.getClan() != null) && (getClanHall() != null) && (player.getClanId() == getClanHall().getOwnerId()); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/ClanHallManager.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/ClanHallManager.java new file mode 100644 index 0000000..4d46479 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/ClanHallManager.java @@ -0,0 +1,1836 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.instance; + +import java.io.File; +import java.time.Instant; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.StringTokenizer; + +import org.l2jmobius.gameserver.config.FeatureConfig; +import org.l2jmobius.gameserver.config.ServerConfig; +import org.l2jmobius.gameserver.data.sql.ClanHallTable; +import org.l2jmobius.gameserver.data.xml.SkillData; +import org.l2jmobius.gameserver.data.xml.TeleporterData; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.enums.creature.InstanceType; +import org.l2jmobius.gameserver.entity.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.entity.clan.ClanAccess; +import org.l2jmobius.gameserver.entity.residences.AuctionableHall; +import org.l2jmobius.gameserver.entity.residences.ClanHall; +import org.l2jmobius.gameserver.entity.residences.ClanHall.ClanHallFunction; +import org.l2jmobius.gameserver.entity.teleporter.TeleportHolder; +import org.l2jmobius.gameserver.managers.CHSiegeManager; +import org.l2jmobius.gameserver.mechanics.effects.EffectType; +import org.l2jmobius.gameserver.mechanics.siege.clanhalls.SiegableHall; +import org.l2jmobius.gameserver.mechanics.skill.Skill; +import org.l2jmobius.gameserver.network.serverpackets.ActionFailed; +import org.l2jmobius.gameserver.network.serverpackets.AgitDecoInfo; +import org.l2jmobius.gameserver.network.serverpackets.NpcHtmlMessage; + +public class ClanHallManager extends Merchant +{ + private static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm"); + + protected static final int COND_OWNER_FALSE = 0; + protected static final int COND_ALL_FALSE = 1; + protected static final int COND_BUSY_BECAUSE_OF_SIEGE = 2; + protected static final int COND_OWNER = 3; + + private int _clanHallId = -1; + + /** + * Creates clan hall manager. + * @param template the clan hall manager NPC template + */ + public ClanHallManager(NpcTemplate template) + { + super(template); + setInstanceType(InstanceType.ClanHallManager); + } + + @Override + public boolean isWarehouse() + { + return true; + } + + @Override + public void onBypassFeedback(Player player, String command) + { + if (getClanHall().isSiegableHall() && ((SiegableHall) getClanHall()).isInSiege()) + { + return; + } + + final int condition = validateCondition(player); + if (condition <= COND_ALL_FALSE) + { + return; + } + + if (condition == COND_OWNER) + { + final StringTokenizer st = new StringTokenizer(command, " "); + final String actualCommand = st.nextToken(); // Get actual command + String val = ""; + if (st.countTokens() >= 1) + { + val = st.nextToken(); + } + + if (actualCommand.equalsIgnoreCase("banish_foreigner")) + { + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + if (player.hasAccess(ClanAccess.HALL_BANISH)) + { + if (val.equalsIgnoreCase("list")) + { + html.setFile(player, "data/html/clanHallManager/banish-list.htm"); + } + else if (val.equalsIgnoreCase("banish")) + { + getClanHall().banishForeigners(); + html.setFile(player, "data/html/clanHallManager/banish.htm"); + } + } + else + { + html.setFile(player, "data/html/clanHallManager/not_authorized.htm"); + } + + sendHtmlMessage(player, html); + return; + } + else if (actualCommand.equalsIgnoreCase("manage_vault")) + { + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + if (player.hasAccess(ClanAccess.ACCESS_WAREHOUSE)) + { + if (getClanHall().getLease() <= 0) + { + html.setFile(player, "data/html/clanHallManager/vault-chs.htm"); + } + else + { + html.setFile(player, "data/html/clanHallManager/vault.htm"); + html.replace("%rent%", String.valueOf(getClanHall().getLease())); + html.replace("%date%", Instant.ofEpochMilli(getClanHall().getPaidUntil()).atZone(ZoneId.systemDefault()).format(DATE_FORMAT)); + } + + sendHtmlMessage(player, html); + } + else + { + html.setFile(player, "data/html/clanHallManager/not_authorized.htm"); + sendHtmlMessage(player, html); + } + return; + } + else if (actualCommand.equalsIgnoreCase("door")) + { + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + if (player.hasAccess(ClanAccess.HALL_OPEN_DOOR)) + { + if (val.equalsIgnoreCase("open")) + { + getClanHall().openCloseDoors(true); + html.setFile(player, "data/html/clanHallManager/door-open.htm"); + } + else if (val.equalsIgnoreCase("close")) + { + getClanHall().openCloseDoors(false); + html.setFile(player, "data/html/clanHallManager/door-close.htm"); + } + else + { + html.setFile(player, "data/html/clanHallManager/door.htm"); + } + + sendHtmlMessage(player, html); + } + else + { + html.setFile(player, "data/html/clanHallManager/not_authorized.htm"); + sendHtmlMessage(player, html); + } + return; + } + else if (actualCommand.equalsIgnoreCase("functions")) + { + if (val.equalsIgnoreCase("tele")) + { + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + if (getClanHall().getFunction(ClanHall.FUNC_TELEPORT) == null) + { + html.setFile(player, "data/html/clanHallManager/chamberlain-nac.htm"); + } + else + { + html.setFile(player, "data/html/clanHallManager/tele" + getClanHall().getLocation() + getClanHall().getFunction(ClanHall.FUNC_TELEPORT).getLevel() + ".htm"); + } + + sendHtmlMessage(player, html); + } + else if (val.equalsIgnoreCase("item_creation")) + { + if (getClanHall().getFunction(ClanHall.FUNC_ITEM_CREATE) == null) + { + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, "data/html/clanHallManager/chamberlain-nac.htm"); + sendHtmlMessage(player, html); + return; + } + + if (st.countTokens() < 1) + { + return; + } + + final int valbuy = Integer.parseInt(st.nextToken()) + (getClanHall().getFunction(ClanHall.FUNC_ITEM_CREATE).getLevel() * 100000); + showBuyWindow(player, valbuy); + } + else if (val.equalsIgnoreCase("support")) + { + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + if (getClanHall().getFunction(ClanHall.FUNC_SUPPORT) == null) + { + html.setFile(player, "data/html/clanHallManager/chamberlain-nac.htm"); + } + else + { + html.setFile(player, "data/html/clanHallManager/support" + getClanHall().getFunction(ClanHall.FUNC_SUPPORT).getLevel() + ".htm"); + html.replace("%mp%", String.valueOf((int) getCurrentMp())); + } + + sendHtmlMessage(player, html); + } + else if (val.equalsIgnoreCase("back")) + { + showChatWindow(player); + } + else + { + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, "data/html/clanHallManager/functions.htm"); + if (getClanHall().getFunction(ClanHall.FUNC_RESTORE_EXP) != null) + { + html.replace("%xp_regen%", String.valueOf(getClanHall().getFunction(ClanHall.FUNC_RESTORE_EXP).getLevel())); + } + else + { + html.replace("%xp_regen%", "0"); + } + + if (getClanHall().getFunction(ClanHall.FUNC_RESTORE_HP) != null) + { + html.replace("%hp_regen%", String.valueOf(getClanHall().getFunction(ClanHall.FUNC_RESTORE_HP).getLevel())); + } + else + { + html.replace("%hp_regen%", "0"); + } + + if (getClanHall().getFunction(ClanHall.FUNC_RESTORE_MP) != null) + { + html.replace("%mp_regen%", String.valueOf(getClanHall().getFunction(ClanHall.FUNC_RESTORE_MP).getLevel())); + } + else + { + html.replace("%mp_regen%", "0"); + } + + sendHtmlMessage(player, html); + } + return; + } + else if (actualCommand.equalsIgnoreCase("manage")) + { + if (player.hasAccess(ClanAccess.HALL_MANAGE_FUNCTIONS)) + { + if (val.equalsIgnoreCase("recovery")) + { + if (st.countTokens() >= 1) + { + if (getClanHall().getOwnerId() == 0) + { + player.sendMessage("This clan hall has no owner, you cannot change the configuration."); + return; + } + + val = st.nextToken(); + if (val.equalsIgnoreCase("hp_cancel")) + { + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, "data/html/clanHallManager/functions-cancel.htm"); + html.replace("%apply%", "recovery hp 0"); + sendHtmlMessage(player, html); + return; + } + else if (val.equalsIgnoreCase("mp_cancel")) + { + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, "data/html/clanHallManager/functions-cancel.htm"); + html.replace("%apply%", "recovery mp 0"); + sendHtmlMessage(player, html); + return; + } + else if (val.equalsIgnoreCase("exp_cancel")) + { + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, "data/html/clanHallManager/functions-cancel.htm"); + html.replace("%apply%", "recovery exp 0"); + sendHtmlMessage(player, html); + return; + } + else if (val.equalsIgnoreCase("edit_hp")) + { + val = st.nextToken(); + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, "data/html/clanHallManager/functions-apply.htm"); + html.replace("%name%", "Fireplace (HP Recovery Device)"); + final int percent = Integer.parseInt(val); + int cost; + switch (percent) + { + case 20: + { + cost = FeatureConfig.CH_HPREG1_FEE; + break; + } + case 40: + { + cost = FeatureConfig.CH_HPREG2_FEE; + break; + } + case 80: + { + cost = FeatureConfig.CH_HPREG3_FEE; + break; + } + case 100: + { + cost = FeatureConfig.CH_HPREG4_FEE; + break; + } + case 120: + { + cost = FeatureConfig.CH_HPREG5_FEE; + break; + } + case 140: + { + cost = FeatureConfig.CH_HPREG6_FEE; + break; + } + case 160: + { + cost = FeatureConfig.CH_HPREG7_FEE; + break; + } + case 180: + { + cost = FeatureConfig.CH_HPREG8_FEE; + break; + } + case 200: + { + cost = FeatureConfig.CH_HPREG9_FEE; + break; + } + case 220: + { + cost = FeatureConfig.CH_HPREG10_FEE; + break; + } + case 240: + { + cost = FeatureConfig.CH_HPREG11_FEE; + break; + } + case 260: + { + cost = FeatureConfig.CH_HPREG12_FEE; + break; + } + default: + { + cost = FeatureConfig.CH_HPREG13_FEE; + break; + } + } + + html.replace("%cost%", cost + "Adena /" + (FeatureConfig.CH_HPREG_FEE_RATIO / 1000 / 60 / 60 / 24) + " Day)"); + html.replace("%use%", "Provides additional HP recovery for clan members in the clan hall." + percent + "%"); + html.replace("%apply%", "recovery hp " + percent); + sendHtmlMessage(player, html); + return; + } + else if (val.equalsIgnoreCase("edit_mp")) + { + val = st.nextToken(); + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, "data/html/clanHallManager/functions-apply.htm"); + html.replace("%name%", "Carpet (MP Recovery)"); + final int percent = Integer.parseInt(val); + int cost; + switch (percent) + { + case 5: + { + cost = FeatureConfig.CH_MPREG1_FEE; + break; + } + case 10: + { + cost = FeatureConfig.CH_MPREG2_FEE; + break; + } + case 15: + { + cost = FeatureConfig.CH_MPREG3_FEE; + break; + } + case 30: + { + cost = FeatureConfig.CH_MPREG4_FEE; + break; + } + default: + { + cost = FeatureConfig.CH_MPREG5_FEE; + break; + } + } + + html.replace("%cost%", cost + "Adena /" + (FeatureConfig.CH_MPREG_FEE_RATIO / 1000 / 60 / 60 / 24) + " Day)"); + html.replace("%use%", "Provides additional MP recovery for clan members in the clan hall." + percent + "%"); + html.replace("%apply%", "recovery mp " + percent); + sendHtmlMessage(player, html); + return; + } + else if (val.equalsIgnoreCase("edit_exp")) + { + val = st.nextToken(); + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, "data/html/clanHallManager/functions-apply.htm"); + html.replace("%name%", "Chandelier (EXP Recovery Device)"); + final int percent = Integer.parseInt(val); + int cost; + switch (percent) + { + case 5: + { + cost = FeatureConfig.CH_EXPREG1_FEE; + break; + } + case 10: + { + cost = FeatureConfig.CH_EXPREG2_FEE; + break; + } + case 15: + { + cost = FeatureConfig.CH_EXPREG3_FEE; + break; + } + case 25: + { + cost = FeatureConfig.CH_EXPREG4_FEE; + break; + } + case 35: + { + cost = FeatureConfig.CH_EXPREG5_FEE; + break; + } + case 40: + { + cost = FeatureConfig.CH_EXPREG6_FEE; + break; + } + default: + { + cost = FeatureConfig.CH_EXPREG7_FEE; + break; + } + } + + html.replace("%cost%", cost + "Adena /" + (FeatureConfig.CH_EXPREG_FEE_RATIO / 1000 / 60 / 60 / 24) + " Day)"); + html.replace("%use%", "Restores the Exp of any clan member who is resurrected in the clan hall." + percent + "%"); + html.replace("%apply%", "recovery exp " + percent); + sendHtmlMessage(player, html); + return; + } + else if (val.equalsIgnoreCase("hp")) + { + if (st.countTokens() >= 1) + { + int fee; + val = st.nextToken(); + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, "data/html/clanHallManager/functions-apply_confirmed.htm"); + if ((getClanHall().getFunction(ClanHall.FUNC_RESTORE_HP) != null) && (getClanHall().getFunction(ClanHall.FUNC_RESTORE_HP).getLevel() == Integer.parseInt(val))) + { + html.setFile(player, "data/html/clanHallManager/functions-used.htm"); + html.replace("%val%", val + "%"); + sendHtmlMessage(player, html); + return; + } + + final int percent = Integer.parseInt(val); + switch (percent) + { + case 0: + { + fee = 0; + html.setFile(player, "data/html/clanHallManager/functions-cancel_confirmed.htm"); + break; + } + case 20: + { + fee = FeatureConfig.CH_HPREG1_FEE; + break; + } + case 40: + { + fee = FeatureConfig.CH_HPREG2_FEE; + break; + } + case 80: + { + fee = FeatureConfig.CH_HPREG3_FEE; + break; + } + case 100: + { + fee = FeatureConfig.CH_HPREG4_FEE; + break; + } + case 120: + { + fee = FeatureConfig.CH_HPREG5_FEE; + break; + } + case 140: + { + fee = FeatureConfig.CH_HPREG6_FEE; + break; + } + case 160: + { + fee = FeatureConfig.CH_HPREG7_FEE; + break; + } + case 180: + { + fee = FeatureConfig.CH_HPREG8_FEE; + break; + } + case 200: + { + fee = FeatureConfig.CH_HPREG9_FEE; + break; + } + case 220: + { + fee = FeatureConfig.CH_HPREG10_FEE; + break; + } + case 240: + { + fee = FeatureConfig.CH_HPREG11_FEE; + break; + } + case 260: + { + fee = FeatureConfig.CH_HPREG12_FEE; + break; + } + default: + { + fee = FeatureConfig.CH_HPREG13_FEE; + break; + } + } + + if (!getClanHall().updateFunctions(player, ClanHall.FUNC_RESTORE_HP, percent, fee, FeatureConfig.CH_HPREG_FEE_RATIO, (getClanHall().getFunction(ClanHall.FUNC_RESTORE_HP) == null))) + { + html.setFile(player, "data/html/clanHallManager/low_adena.htm"); + sendHtmlMessage(player, html); + } + else + { + revalidateDeco(player); + } + + sendHtmlMessage(player, html); + } + return; + } + else if (val.equalsIgnoreCase("mp")) + { + if (st.countTokens() >= 1) + { + int fee; + val = st.nextToken(); + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, "data/html/clanHallManager/functions-apply_confirmed.htm"); + if ((getClanHall().getFunction(ClanHall.FUNC_RESTORE_MP) != null) && (getClanHall().getFunction(ClanHall.FUNC_RESTORE_MP).getLevel() == Integer.parseInt(val))) + { + html.setFile(player, "data/html/clanHallManager/functions-used.htm"); + html.replace("%val%", val + "%"); + sendHtmlMessage(player, html); + return; + } + + final int percent = Integer.parseInt(val); + switch (percent) + { + case 0: + { + fee = 0; + html.setFile(player, "data/html/clanHallManager/functions-cancel_confirmed.htm"); + break; + } + case 5: + { + fee = FeatureConfig.CH_MPREG1_FEE; + break; + } + case 10: + { + fee = FeatureConfig.CH_MPREG2_FEE; + break; + } + case 15: + { + fee = FeatureConfig.CH_MPREG3_FEE; + break; + } + case 30: + { + fee = FeatureConfig.CH_MPREG4_FEE; + break; + } + default: + { + fee = FeatureConfig.CH_MPREG5_FEE; + break; + } + } + + if (!getClanHall().updateFunctions(player, ClanHall.FUNC_RESTORE_MP, percent, fee, FeatureConfig.CH_MPREG_FEE_RATIO, (getClanHall().getFunction(ClanHall.FUNC_RESTORE_MP) == null))) + { + html.setFile(player, "data/html/clanHallManager/low_adena.htm"); + sendHtmlMessage(player, html); + } + else + { + revalidateDeco(player); + } + + sendHtmlMessage(player, html); + } + return; + } + else if (val.equalsIgnoreCase("exp")) + { + if (st.countTokens() >= 1) + { + int fee; + val = st.nextToken(); + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, "data/html/clanHallManager/functions-apply_confirmed.htm"); + if ((getClanHall().getFunction(ClanHall.FUNC_RESTORE_EXP) != null) && (getClanHall().getFunction(ClanHall.FUNC_RESTORE_EXP).getLevel() == Integer.parseInt(val))) + { + html.setFile(player, "data/html/clanHallManager/functions-used.htm"); + html.replace("%val%", val + "%"); + sendHtmlMessage(player, html); + return; + } + + final int percent = Integer.parseInt(val); + switch (percent) + { + case 0: + { + fee = 0; + html.setFile(player, "data/html/clanHallManager/functions-cancel_confirmed.htm"); + break; + } + case 5: + { + fee = FeatureConfig.CH_EXPREG1_FEE; + break; + } + case 10: + { + fee = FeatureConfig.CH_EXPREG2_FEE; + break; + } + case 15: + { + fee = FeatureConfig.CH_EXPREG3_FEE; + break; + } + case 25: + { + fee = FeatureConfig.CH_EXPREG4_FEE; + break; + } + case 35: + { + fee = FeatureConfig.CH_EXPREG5_FEE; + break; + } + case 40: + { + fee = FeatureConfig.CH_EXPREG6_FEE; + break; + } + default: + { + fee = FeatureConfig.CH_EXPREG7_FEE; + break; + } + } + + if (!getClanHall().updateFunctions(player, ClanHall.FUNC_RESTORE_EXP, percent, fee, FeatureConfig.CH_EXPREG_FEE_RATIO, (getClanHall().getFunction(ClanHall.FUNC_RESTORE_EXP) == null))) + { + html.setFile(player, "data/html/clanHallManager/low_adena.htm"); + sendHtmlMessage(player, html); + } + else + { + revalidateDeco(player); + } + + sendHtmlMessage(player, html); + } + return; + } + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, "data/html/clanHallManager/edit_recovery.htm"); + final String hp_grade0 = "[20%][40%][220%]"; + final String hp_grade1 = "[40%][100%][160%]"; + final String hp_grade2 = "[80%][140%][200%][260%]"; + final String hp_grade3 = "[80%][120%][180%][240%][300%]"; + final String exp_grade0 = "[5%][10%][25%]"; + final String exp_grade1 = "[5%][15%][30%]"; + final String exp_grade2 = "[5%][15%][25%][40%]"; + final String exp_grade3 = "[15%][25%][35%][50%]"; + final String mp_grade0 = "[5%][10%][25%]"; + final String mp_grade1 = "[5%][15%][25%]"; + final String mp_grade2 = "[5%][15%][30%]"; + final String mp_grade3 = "[5%][15%][30%][40%]"; + if (getClanHall().getFunction(ClanHall.FUNC_RESTORE_HP) != null) + { + html.replace("%hp_recovery%", getClanHall().getFunction(ClanHall.FUNC_RESTORE_HP).getLevel() + "% (" + getClanHall().getFunction(ClanHall.FUNC_RESTORE_HP).getLease() + "Adena /" + (FeatureConfig.CH_HPREG_FEE_RATIO / 1000 / 60 / 60 / 24) + " Day)"); + html.replace("%hp_period%", "Withdraw the fee for the next time at " + Instant.ofEpochMilli(getClanHall().getFunction(ClanHall.FUNC_RESTORE_HP).getEndTime()).atZone(ZoneId.systemDefault()).format(DATE_FORMAT)); + final int grade = getClanHall().getGrade(); + switch (grade) + { + case 0: + { + html.replace("%change_hp%", "[Deactivate]" + hp_grade0); + break; + } + case 1: + { + html.replace("%change_hp%", "[Deactivate]" + hp_grade1); + break; + } + case 2: + { + html.replace("%change_hp%", "[Deactivate]" + hp_grade2); + break; + } + case 3: + { + html.replace("%change_hp%", "[Deactivate]" + hp_grade3); + break; + } + } + } + else + { + html.replace("%hp_recovery%", "none"); + html.replace("%hp_period%", "none"); + final int grade = getClanHall().getGrade(); + switch (grade) + { + case 0: + { + html.replace("%change_hp%", hp_grade0); + break; + } + case 1: + { + html.replace("%change_hp%", hp_grade1); + break; + } + case 2: + { + html.replace("%change_hp%", hp_grade2); + break; + } + case 3: + { + html.replace("%change_hp%", hp_grade3); + break; + } + } + } + + if (getClanHall().getFunction(ClanHall.FUNC_RESTORE_EXP) != null) + { + html.replace("%exp_recovery%", getClanHall().getFunction(ClanHall.FUNC_RESTORE_EXP).getLevel() + "% (" + getClanHall().getFunction(ClanHall.FUNC_RESTORE_EXP).getLease() + "Adena /" + (FeatureConfig.CH_EXPREG_FEE_RATIO / 1000 / 60 / 60 / 24) + " Day)"); + html.replace("%exp_period%", "Withdraw the fee for the next time at " + Instant.ofEpochMilli(getClanHall().getFunction(ClanHall.FUNC_RESTORE_EXP).getEndTime()).atZone(ZoneId.systemDefault()).format(DATE_FORMAT)); + final int grade = getClanHall().getGrade(); + switch (grade) + { + case 0: + { + html.replace("%change_exp%", "[Deactivate]" + exp_grade0); + break; + } + case 1: + { + html.replace("%change_exp%", "[Deactivate]" + exp_grade1); + break; + } + case 2: + { + html.replace("%change_exp%", "[Deactivate]" + exp_grade2); + break; + } + case 3: + { + html.replace("%change_exp%", "[Deactivate]" + exp_grade3); + break; + } + } + } + else + { + html.replace("%exp_recovery%", "none"); + html.replace("%exp_period%", "none"); + final int grade = getClanHall().getGrade(); + switch (grade) + { + case 0: + { + html.replace("%change_exp%", exp_grade0); + break; + } + case 1: + { + html.replace("%change_exp%", exp_grade1); + break; + } + case 2: + { + html.replace("%change_exp%", exp_grade2); + break; + } + case 3: + { + html.replace("%change_exp%", exp_grade3); + break; + } + } + } + + if (getClanHall().getFunction(ClanHall.FUNC_RESTORE_MP) != null) + { + html.replace("%mp_recovery%", getClanHall().getFunction(ClanHall.FUNC_RESTORE_MP).getLevel() + "% (" + getClanHall().getFunction(ClanHall.FUNC_RESTORE_MP).getLease() + "Adena /" + (FeatureConfig.CH_MPREG_FEE_RATIO / 1000 / 60 / 60 / 24) + " Day)"); + html.replace("%mp_period%", "Withdraw the fee for the next time at " + Instant.ofEpochMilli(getClanHall().getFunction(ClanHall.FUNC_RESTORE_MP).getEndTime()).atZone(ZoneId.systemDefault()).format(DATE_FORMAT)); + final int grade = getClanHall().getGrade(); + switch (grade) + { + case 0: + { + html.replace("%change_mp%", "[Deactivate]" + mp_grade0); + break; + } + case 1: + { + html.replace("%change_mp%", "[Deactivate]" + mp_grade1); + break; + } + case 2: + { + html.replace("%change_mp%", "[Deactivate]" + mp_grade2); + break; + } + case 3: + { + html.replace("%change_mp%", "[Deactivate]" + mp_grade3); + break; + } + } + } + else + { + html.replace("%mp_recovery%", "none"); + html.replace("%mp_period%", "none"); + final int grade = getClanHall().getGrade(); + switch (grade) + { + case 0: + { + html.replace("%change_mp%", mp_grade0); + break; + } + case 1: + { + html.replace("%change_mp%", mp_grade1); + break; + } + case 2: + { + html.replace("%change_mp%", mp_grade2); + break; + } + case 3: + { + html.replace("%change_mp%", mp_grade3); + break; + } + } + } + + sendHtmlMessage(player, html); + } + else if (val.equalsIgnoreCase("other")) + { + if (st.countTokens() >= 1) + { + if (getClanHall().getOwnerId() == 0) + { + player.sendMessage("This clan hall has no owner, you cannot change the configuration."); + return; + } + + val = st.nextToken(); + if (val.equalsIgnoreCase("item_cancel")) + { + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, "data/html/clanHallManager/functions-cancel.htm"); + html.replace("%apply%", "other item 0"); + sendHtmlMessage(player, html); + return; + } + else if (val.equalsIgnoreCase("tele_cancel")) + { + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, "data/html/clanHallManager/functions-cancel.htm"); + html.replace("%apply%", "other tele 0"); + sendHtmlMessage(player, html); + return; + } + else if (val.equalsIgnoreCase("support_cancel")) + { + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, "data/html/clanHallManager/functions-cancel.htm"); + html.replace("%apply%", "other support 0"); + sendHtmlMessage(player, html); + return; + } + else if (val.equalsIgnoreCase("edit_item")) + { + val = st.nextToken(); + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, "data/html/clanHallManager/functions-apply.htm"); + html.replace("%name%", "Magic Equipment (Item Production Facilities)"); + final int stage = Integer.parseInt(val); + int cost; + switch (stage) + { + case 1: + { + cost = FeatureConfig.CH_ITEM1_FEE; + break; + } + case 2: + { + cost = FeatureConfig.CH_ITEM2_FEE; + break; + } + default: + { + cost = FeatureConfig.CH_ITEM3_FEE; + break; + } + } + + html.replace("%cost%", cost + "Adena /" + (FeatureConfig.CH_ITEM_FEE_RATIO / 1000 / 60 / 60 / 24) + " Day)"); + html.replace("%use%", "Allow the purchase of special items at fixed intervals."); + html.replace("%apply%", "other item " + stage); + sendHtmlMessage(player, html); + return; + } + else if (val.equalsIgnoreCase("edit_support")) + { + val = st.nextToken(); + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, "data/html/clanHallManager/functions-apply.htm"); + html.replace("%name%", "Insignia (Supplementary Magic)"); + final int stage = Integer.parseInt(val); + int cost; + switch (stage) + { + case 1: + { + cost = FeatureConfig.CH_SUPPORT1_FEE; + break; + } + case 2: + { + cost = FeatureConfig.CH_SUPPORT2_FEE; + break; + } + case 3: + { + cost = FeatureConfig.CH_SUPPORT3_FEE; + break; + } + case 4: + { + cost = FeatureConfig.CH_SUPPORT4_FEE; + break; + } + case 5: + { + cost = FeatureConfig.CH_SUPPORT5_FEE; + break; + } + case 6: + { + cost = FeatureConfig.CH_SUPPORT6_FEE; + break; + } + case 7: + { + cost = FeatureConfig.CH_SUPPORT7_FEE; + break; + } + default: + { + cost = FeatureConfig.CH_SUPPORT8_FEE; + break; + } + } + + html.replace("%cost%", cost + "Adena /" + (FeatureConfig.CH_SUPPORT_FEE_RATIO / 1000 / 60 / 60 / 24) + " Day)"); + html.replace("%use%", "Enables the use of supplementary magic."); + html.replace("%apply%", "other support " + stage); + sendHtmlMessage(player, html); + return; + } + else if (val.equalsIgnoreCase("edit_tele")) + { + val = st.nextToken(); + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, "data/html/clanHallManager/functions-apply.htm"); + html.replace("%name%", "Mirror (Teleportation Device)"); + final int stage = Integer.parseInt(val); + int cost; + switch (stage) + { + case 1: + { + cost = FeatureConfig.CH_TELE1_FEE; + break; + } + default: + { + cost = FeatureConfig.CH_TELE2_FEE; + break; + } + } + + html.replace("%cost%", cost + "Adena /" + (FeatureConfig.CH_TELE_FEE_RATIO / 1000 / 60 / 60 / 24) + " Day)"); + html.replace("%use%", "Teleports clan members in a clan hall to the target Stage " + stage + " staging area"); + html.replace("%apply%", "other tele " + stage); + sendHtmlMessage(player, html); + return; + } + else if (val.equalsIgnoreCase("item")) + { + if (st.countTokens() >= 1) + { + if (getClanHall().getOwnerId() == 0) + { + player.sendMessage("This clan hall has no owner, you cannot change the configuration."); + return; + } + + val = st.nextToken(); + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, "data/html/clanHallManager/functions-apply_confirmed.htm"); + if ((getClanHall().getFunction(ClanHall.FUNC_ITEM_CREATE) != null) && (getClanHall().getFunction(ClanHall.FUNC_ITEM_CREATE).getLevel() == Integer.parseInt(val))) + { + html.setFile(player, "data/html/clanHallManager/functions-used.htm"); + html.replace("%val%", "Stage " + val); + sendHtmlMessage(player, html); + return; + } + + int fee; + final int level = Integer.parseInt(val); + switch (level) + { + case 0: + { + fee = 0; + html.setFile(player, "data/html/clanHallManager/functions-cancel_confirmed.htm"); + break; + } + case 1: + { + fee = FeatureConfig.CH_ITEM1_FEE; + break; + } + case 2: + { + fee = FeatureConfig.CH_ITEM2_FEE; + break; + } + default: + { + fee = FeatureConfig.CH_ITEM3_FEE; + break; + } + } + + if (!getClanHall().updateFunctions(player, ClanHall.FUNC_ITEM_CREATE, level, fee, FeatureConfig.CH_ITEM_FEE_RATIO, (getClanHall().getFunction(ClanHall.FUNC_ITEM_CREATE) == null))) + { + html.setFile(player, "data/html/clanHallManager/low_adena.htm"); + sendHtmlMessage(player, html); + } + else + { + revalidateDeco(player); + } + + sendHtmlMessage(player, html); + } + return; + } + else if (val.equalsIgnoreCase("tele")) + { + if (st.countTokens() >= 1) + { + int fee; + val = st.nextToken(); + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, "data/html/clanHallManager/functions-apply_confirmed.htm"); + if ((getClanHall().getFunction(ClanHall.FUNC_TELEPORT) != null) && (getClanHall().getFunction(ClanHall.FUNC_TELEPORT).getLevel() == Integer.parseInt(val))) + { + html.setFile(player, "data/html/clanHallManager/functions-used.htm"); + html.replace("%val%", "Stage " + val); + sendHtmlMessage(player, html); + return; + } + + final int level = Integer.parseInt(val); + switch (level) + { + case 0: + { + fee = 0; + html.setFile(player, "data/html/clanHallManager/functions-cancel_confirmed.htm"); + break; + } + case 1: + { + fee = FeatureConfig.CH_TELE1_FEE; + break; + } + default: + { + fee = FeatureConfig.CH_TELE2_FEE; + break; + } + } + + if (!getClanHall().updateFunctions(player, ClanHall.FUNC_TELEPORT, level, fee, FeatureConfig.CH_TELE_FEE_RATIO, (getClanHall().getFunction(ClanHall.FUNC_TELEPORT) == null))) + { + html.setFile(player, "data/html/clanHallManager/low_adena.htm"); + sendHtmlMessage(player, html); + } + else + { + revalidateDeco(player); + } + + sendHtmlMessage(player, html); + } + return; + } + else if (val.equalsIgnoreCase("support")) + { + if (st.countTokens() >= 1) + { + int fee; + val = st.nextToken(); + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, "data/html/clanHallManager/functions-apply_confirmed.htm"); + if ((getClanHall().getFunction(ClanHall.FUNC_SUPPORT) != null) && (getClanHall().getFunction(ClanHall.FUNC_SUPPORT).getLevel() == Integer.parseInt(val))) + { + html.setFile(player, "data/html/clanHallManager/functions-used.htm"); + html.replace("%val%", "Stage " + val); + sendHtmlMessage(player, html); + return; + } + + final int level = Integer.parseInt(val); + switch (level) + { + case 0: + { + fee = 0; + html.setFile(player, "data/html/clanHallManager/functions-cancel_confirmed.htm"); + break; + } + case 1: + { + fee = FeatureConfig.CH_SUPPORT1_FEE; + break; + } + case 2: + { + fee = FeatureConfig.CH_SUPPORT2_FEE; + break; + } + case 3: + { + fee = FeatureConfig.CH_SUPPORT3_FEE; + break; + } + case 4: + { + fee = FeatureConfig.CH_SUPPORT4_FEE; + break; + } + case 5: + { + fee = FeatureConfig.CH_SUPPORT5_FEE; + break; + } + case 6: + { + fee = FeatureConfig.CH_SUPPORT6_FEE; + break; + } + case 7: + { + fee = FeatureConfig.CH_SUPPORT7_FEE; + break; + } + default: + { + fee = FeatureConfig.CH_SUPPORT8_FEE; + break; + } + } + + if (!getClanHall().updateFunctions(player, ClanHall.FUNC_SUPPORT, level, fee, FeatureConfig.CH_SUPPORT_FEE_RATIO, (getClanHall().getFunction(ClanHall.FUNC_SUPPORT) == null))) + { + html.setFile(player, "data/html/clanHallManager/low_adena.htm"); + sendHtmlMessage(player, html); + } + else + { + revalidateDeco(player); + } + + sendHtmlMessage(player, html); + } + return; + } + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, "data/html/clanHallManager/edit_other.htm"); + final String tele = "[Level 1][Level 2]"; + final String support_grade0 = "[Level 1][Level 2]"; + final String support_grade1 = "[Level 1][Level 2][Level 4]"; + final String support_grade2 = "[Level 3][Level 4][Level 5]"; + final String support_grade3 = "[Level 3][Level 5][Level 7][Level 8]"; + final String item = "[Level 1][Level 2][Level 3]"; + if (getClanHall().getFunction(ClanHall.FUNC_TELEPORT) != null) + { + html.replace("%tele%", "Stage " + getClanHall().getFunction(ClanHall.FUNC_TELEPORT).getLevel() + " (" + getClanHall().getFunction(ClanHall.FUNC_TELEPORT).getLease() + "Adena /" + (FeatureConfig.CH_TELE_FEE_RATIO / 1000 / 60 / 60 / 24) + " Day)"); + html.replace("%tele_period%", "Withdraw the fee for the next time at " + Instant.ofEpochMilli(getClanHall().getFunction(ClanHall.FUNC_TELEPORT).getEndTime()).atZone(ZoneId.systemDefault()).format(DATE_FORMAT)); + html.replace("%change_tele%", "[Deactivate]" + tele); + } + else + { + html.replace("%tele%", "none"); + html.replace("%tele_period%", "none"); + html.replace("%change_tele%", tele); + } + + if (getClanHall().getFunction(ClanHall.FUNC_SUPPORT) != null) + { + html.replace("%support%", "Stage " + getClanHall().getFunction(ClanHall.FUNC_SUPPORT).getLevel() + " (" + getClanHall().getFunction(ClanHall.FUNC_SUPPORT).getLease() + "Adena /" + (FeatureConfig.CH_SUPPORT_FEE_RATIO / 1000 / 60 / 60 / 24) + " Day)"); + html.replace("%support_period%", "Withdraw the fee for the next time at " + Instant.ofEpochMilli(getClanHall().getFunction(ClanHall.FUNC_SUPPORT).getEndTime()).atZone(ZoneId.systemDefault()).format(DATE_FORMAT)); + final int grade = getClanHall().getGrade(); + switch (grade) + { + case 0: + { + html.replace("%change_support%", "[Deactivate]" + support_grade0); + break; + } + case 1: + { + html.replace("%change_support%", "[Deactivate]" + support_grade1); + break; + } + case 2: + { + html.replace("%change_support%", "[Deactivate]" + support_grade2); + break; + } + case 3: + { + html.replace("%change_support%", "[Deactivate]" + support_grade3); + break; + } + } + } + else + { + html.replace("%support%", "none"); + html.replace("%support_period%", "none"); + final int grade = getClanHall().getGrade(); + switch (grade) + { + case 0: + { + html.replace("%change_support%", support_grade0); + break; + } + case 1: + { + html.replace("%change_support%", support_grade1); + break; + } + case 2: + { + html.replace("%change_support%", support_grade2); + break; + } + case 3: + { + html.replace("%change_support%", support_grade3); + break; + } + } + } + + if (getClanHall().getFunction(ClanHall.FUNC_ITEM_CREATE) != null) + { + html.replace("%item%", "Stage " + getClanHall().getFunction(ClanHall.FUNC_ITEM_CREATE).getLevel() + " (" + getClanHall().getFunction(ClanHall.FUNC_ITEM_CREATE).getLease() + "Adena /" + (FeatureConfig.CH_ITEM_FEE_RATIO / 1000 / 60 / 60 / 24) + " Day)"); + html.replace("%item_period%", "Withdraw the fee for the next time at " + Instant.ofEpochMilli(getClanHall().getFunction(ClanHall.FUNC_ITEM_CREATE).getEndTime()).atZone(ZoneId.systemDefault()).format(DATE_FORMAT)); + html.replace("%change_item%", "[Deactivate]" + item); + } + else + { + html.replace("%item%", "none"); + html.replace("%item_period%", "none"); + html.replace("%change_item%", item); + } + + sendHtmlMessage(player, html); + } + else if (val.equalsIgnoreCase("deco") && !getClanHall().isSiegableHall()) + { + if (st.countTokens() >= 1) + { + if (getClanHall().getOwnerId() == 0) + { + player.sendMessage("This clan hall has no owner, you cannot change the configuration."); + return; + } + + val = st.nextToken(); + if (val.equalsIgnoreCase("curtains_cancel")) + { + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, "data/html/clanHallManager/functions-cancel.htm"); + html.replace("%apply%", "deco curtains 0"); + sendHtmlMessage(player, html); + return; + } + else if (val.equalsIgnoreCase("fixtures_cancel")) + { + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, "data/html/clanHallManager/functions-cancel.htm"); + html.replace("%apply%", "deco fixtures 0"); + sendHtmlMessage(player, html); + return; + } + else if (val.equalsIgnoreCase("edit_curtains")) + { + val = st.nextToken(); + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, "data/html/clanHallManager/functions-apply.htm"); + html.replace("%name%", "Curtains (Decoration)"); + final int stage = Integer.parseInt(val); + int cost; + switch (stage) + { + case 1: + { + cost = FeatureConfig.CH_CURTAIN1_FEE; + break; + } + default: + { + cost = FeatureConfig.CH_CURTAIN2_FEE; + break; + } + } + + html.replace("%cost%", cost + "Adena /" + (FeatureConfig.CH_CURTAIN_FEE_RATIO / 1000 / 60 / 60 / 24) + " Day)"); + html.replace("%use%", "These curtains can be used to decorate the clan hall."); + html.replace("%apply%", "deco curtains " + stage); + sendHtmlMessage(player, html); + return; + } + else if (val.equalsIgnoreCase("edit_fixtures")) + { + val = st.nextToken(); + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, "data/html/clanHallManager/functions-apply.htm"); + html.replace("%name%", "Front Platform (Decoration)"); + final int stage = Integer.parseInt(val); + int cost; + switch (stage) + { + case 1: + { + cost = FeatureConfig.CH_FRONT1_FEE; + break; + } + default: + { + cost = FeatureConfig.CH_FRONT2_FEE; + break; + } + } + + html.replace("%cost%", cost + "Adena /" + (FeatureConfig.CH_FRONT_FEE_RATIO / 1000 / 60 / 60 / 24) + " Day)"); + html.replace("%use%", "Used to decorate the clan hall."); + html.replace("%apply%", "deco fixtures " + stage); + sendHtmlMessage(player, html); + return; + } + else if (val.equalsIgnoreCase("curtains")) + { + if (st.countTokens() >= 1) + { + int fee; + val = st.nextToken(); + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, "data/html/clanHallManager/functions-apply_confirmed.htm"); + if ((getClanHall().getFunction(ClanHall.FUNC_DECO_CURTAINS) != null) && (getClanHall().getFunction(ClanHall.FUNC_DECO_CURTAINS).getLevel() == Integer.parseInt(val))) + { + html.setFile(player, "data/html/clanHallManager/functions-used.htm"); + html.replace("%val%", "Stage " + val); + sendHtmlMessage(player, html); + return; + } + + final int level = Integer.parseInt(val); + switch (level) + { + case 0: + { + fee = 0; + html.setFile(player, "data/html/clanHallManager/functions-cancel_confirmed.htm"); + break; + } + case 1: + { + fee = FeatureConfig.CH_CURTAIN1_FEE; + break; + } + default: + { + fee = FeatureConfig.CH_CURTAIN2_FEE; + break; + } + } + + if (!getClanHall().updateFunctions(player, ClanHall.FUNC_DECO_CURTAINS, level, fee, FeatureConfig.CH_CURTAIN_FEE_RATIO, (getClanHall().getFunction(ClanHall.FUNC_DECO_CURTAINS) == null))) + { + html.setFile(player, "data/html/clanHallManager/low_adena.htm"); + sendHtmlMessage(player, html); + } + else + { + revalidateDeco(player); + } + + sendHtmlMessage(player, html); + } + return; + } + else if (val.equalsIgnoreCase("fixtures")) + { + if (st.countTokens() >= 1) + { + int fee; + val = st.nextToken(); + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, "data/html/clanHallManager/functions-apply_confirmed.htm"); + if ((getClanHall().getFunction(ClanHall.FUNC_DECO_FRONTPLATEFORM) != null) && (getClanHall().getFunction(ClanHall.FUNC_DECO_FRONTPLATEFORM).getLevel() == Integer.parseInt(val))) + { + html.setFile(player, "data/html/clanHallManager/functions-used.htm"); + html.replace("%val%", "Stage " + val); + sendHtmlMessage(player, html); + return; + } + + final int level = Integer.parseInt(val); + switch (level) + { + case 0: + { + fee = 0; + html.setFile(player, "data/html/clanHallManager/functions-cancel_confirmed.htm"); + break; + } + case 1: + { + fee = FeatureConfig.CH_FRONT1_FEE; + break; + } + default: + { + fee = FeatureConfig.CH_FRONT2_FEE; + break; + } + } + + if (!getClanHall().updateFunctions(player, ClanHall.FUNC_DECO_FRONTPLATEFORM, level, fee, FeatureConfig.CH_FRONT_FEE_RATIO, (getClanHall().getFunction(ClanHall.FUNC_DECO_FRONTPLATEFORM) == null))) + { + html.setFile(player, "data/html/clanHallManager/low_adena.htm"); + sendHtmlMessage(player, html); + } + else + { + revalidateDeco(player); + } + + sendHtmlMessage(player, html); + } + return; + } + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, "data/html/clanHallManager/deco.htm"); + final String curtains = "[Level 1][Level 2]"; + final String fixtures = "[Level 1][Level 2]"; + if (getClanHall().getFunction(ClanHall.FUNC_DECO_CURTAINS) != null) + { + html.replace("%curtain%", "Stage " + getClanHall().getFunction(ClanHall.FUNC_DECO_CURTAINS).getLevel() + " (" + getClanHall().getFunction(ClanHall.FUNC_DECO_CURTAINS).getLease() + "Adena /" + (FeatureConfig.CH_CURTAIN_FEE_RATIO / 1000 / 60 / 60 / 24) + " Day)"); + html.replace("%curtain_period%", "Withdraw the fee for the next time at " + Instant.ofEpochMilli(getClanHall().getFunction(ClanHall.FUNC_DECO_CURTAINS).getEndTime()).atZone(ZoneId.systemDefault()).format(DATE_FORMAT)); + html.replace("%change_curtain%", "[Deactivate]" + curtains); + } + else + { + html.replace("%curtain%", "none"); + html.replace("%curtain_period%", "none"); + html.replace("%change_curtain%", curtains); + } + + if (getClanHall().getFunction(ClanHall.FUNC_DECO_FRONTPLATEFORM) != null) + { + html.replace("%fixture%", "Stage " + getClanHall().getFunction(ClanHall.FUNC_DECO_FRONTPLATEFORM).getLevel() + " (" + getClanHall().getFunction(ClanHall.FUNC_DECO_FRONTPLATEFORM).getLease() + "Adena /" + (FeatureConfig.CH_FRONT_FEE_RATIO / 1000 / 60 / 60 / 24) + " Day)"); + html.replace("%fixture_period%", "Withdraw the fee for the next time at " + Instant.ofEpochMilli(getClanHall().getFunction(ClanHall.FUNC_DECO_FRONTPLATEFORM).getEndTime()).atZone(ZoneId.systemDefault()).format(DATE_FORMAT)); + html.replace("%change_fixture%", "[Deactivate]" + fixtures); + } + else + { + html.replace("%fixture%", "none"); + html.replace("%fixture_period%", "none"); + html.replace("%change_fixture%", fixtures); + } + + sendHtmlMessage(player, html); + } + else if (val.equalsIgnoreCase("back")) + { + showChatWindow(player); + } + else + { + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getClanHall().isSiegableHall() ? "data/html/clanHallManager/manage_siegable.htm" : "data/html/clanHallManager/manage.htm"); + sendHtmlMessage(player, html); + } + } + else + { + final NpcHtmlMessage html = new NpcHtmlMessage(1); + html.setFile(player, "data/html/clanHallManager/not_authorized.htm"); + sendHtmlMessage(player, html); + } + return; + } + else if (actualCommand.equalsIgnoreCase("support")) + { + if (player.isCursedWeaponEquipped()) + { + // Custom system message + player.sendMessage("The wielder of a cursed weapon cannot receive outside heals or buffs"); + return; + } + + setTarget(player); + Skill skill; + if (val.isEmpty()) + { + return; + } + + try + { + final int skillId = Integer.parseInt(val); + try + { + int skillLevel = 0; + if (st.countTokens() >= 1) + { + skillLevel = Integer.parseInt(st.nextToken()); + } + + skill = SkillData.getInstance().getSkill(skillId, skillLevel); + if (skill.hasEffectType(EffectType.SUMMON)) + { + player.doSimultaneousCast(skill); + } + else + { + final int mpCost = skill.getMpConsume() + skill.getMpInitialConsume(); + + // If Clan Hall Buff are free or current MP is greater than MP cost, the skill should be casted. + if ((getCurrentMp() >= mpCost) || FeatureConfig.CH_BUFF_FREE) + { + doCast(skill); + } + else + { + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, "data/html/clanHallManager/support-no_mana.htm"); + html.replace("%mp%", String.valueOf((int) getCurrentMp())); + sendHtmlMessage(player, html); + return; + } + } + + if (getClanHall().getFunction(ClanHall.FUNC_SUPPORT) == null) + { + return; + } + + if (getClanHall().getFunction(ClanHall.FUNC_SUPPORT).getLevel() == 0) + { + return; + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, "data/html/clanHallManager/support-done.htm"); + html.replace("%mp%", String.valueOf((int) getCurrentMp())); + sendHtmlMessage(player, html); + } + catch (Exception e) + { + player.sendMessage("Invalid skill level, contact your admin!"); + } + } + catch (Exception e) + { + player.sendMessage("Invalid skill level, contact your admin!"); + } + return; + } + else if (actualCommand.equalsIgnoreCase("list_back")) + { + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + String fileName = "data/html/clanHallManager/chamberlain-" + getId() + ".htm"; + final File file = new File(ServerConfig.DATAPACK_ROOT, fileName); + if (!file.isFile()) + { + fileName = "data/html/clanHallManager/chamberlain.htm"; + } + + html.setFile(player, fileName); + html.replace("%objectId%", String.valueOf(getObjectId())); + html.replace("%npcname%", getName()); + sendHtmlMessage(player, html); + return; + } + else if (actualCommand.equalsIgnoreCase("support_back")) + { + if (getClanHall().getFunction(ClanHall.FUNC_SUPPORT).getLevel() == 0) + { + return; + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, "data/html/clanHallManager/support" + getClanHall().getFunction(ClanHall.FUNC_SUPPORT).getLevel() + ".htm"); + html.replace("%mp%", String.valueOf((int) getStatus().getCurrentMp())); + sendHtmlMessage(player, html); + return; + } + else if (actualCommand.equalsIgnoreCase("goto")) + { + final ClanHallFunction function = getClanHall().getFunction(ClanHall.FUNC_TELEPORT); + if (function != null) + { + final int teleportLevel = function.getLevel(); + if (teleportLevel > 0) + { + final TeleportHolder holder = TeleporterData.getInstance().getHolder(getId(), "tel" + teleportLevel); + if (holder != null) + { + holder.doTeleport(player, this, Integer.parseInt(command.split(" ")[1])); + player.sendPacket(ActionFailed.STATIC_PACKET); + } + } + } + return; + } + } + + super.onBypassFeedback(player, command); + } + + private void sendHtmlMessage(Player player, NpcHtmlMessage html) + { + html.replace("%objectId%", String.valueOf(getObjectId())); + html.replace("%npcId%", String.valueOf(getId())); + player.sendPacket(html); + } + + @Override + public void showChatWindow(Player player) + { + player.sendPacket(ActionFailed.STATIC_PACKET); + String fileName = "data/html/clanHallManager/chamberlain-no.htm"; + + final int condition = validateCondition(player); + if (condition == COND_OWNER) + { + fileName = "data/html/clanHallManager/chamberlain-" + getId() + ".htm"; + final File file = new File(ServerConfig.DATAPACK_ROOT, fileName); + if (!file.isFile()) + { + fileName = "data/html/clanHallManager/chamberlain.htm"; + } + } + else if (condition == COND_OWNER_FALSE) + { + fileName = "data/html/clanHallManager/chamberlain-of.htm"; + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, fileName); + html.replace("%objectId%", String.valueOf(getObjectId())); + html.replace("%npcId%", String.valueOf(getId())); + player.sendPacket(html); + } + + protected int validateCondition(Player player) + { + if (getClanHall() == null) + { + return COND_ALL_FALSE; + } + + if (player.isGM()) + { + return COND_OWNER; + } + + if (player.getClan() != null) + { + if (getClanHall().getOwnerId() == player.getClanId()) + { + return COND_OWNER; + } + + return COND_OWNER_FALSE; + } + + return COND_ALL_FALSE; + } + + /** + * @return the PledgeHall this Npc belongs to. + */ + public ClanHall getClanHall() + { + if (_clanHallId < 0) + { + ClanHall temp = ClanHallTable.getInstance().getNearbyClanHall(getX(), getY(), 500); + if (temp == null) + { + temp = CHSiegeManager.getInstance().getNearbyClanHall(this); + } + + if (temp != null) + { + _clanHallId = temp.getId(); + } + + if (_clanHallId < 0) + { + return null; + } + } + + return ClanHallTable.getInstance().getClanHallById(_clanHallId); + } + + private void revalidateDeco(Player player) + { + final AuctionableHall ch = ClanHallTable.getInstance().getClanHallByOwner(player.getClan()); + if (ch == null) + { + return; + } + + player.sendPacket(new AgitDecoInfo(ch)); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/ControlTower.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/ControlTower.java new file mode 100644 index 0000000..cfce65d --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/ControlTower.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.instance; + +import java.util.Collection; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; + +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.Tower; +import org.l2jmobius.gameserver.entity.actor.enums.creature.InstanceType; +import org.l2jmobius.gameserver.entity.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.entity.spawns.Spawn; + +/** + * Class for Control Tower instance. + */ +public class ControlTower extends Tower +{ + private Collection _guards; + + /** + * Creates a control tower. + * @param template the control tower NPC template + */ + public ControlTower(NpcTemplate template) + { + super(template); + setInstanceType(InstanceType.ControlTower); + } + + @Override + public boolean doDie(Creature killer) + { + if (getCastle().getSiege().isInProgress()) + { + getCastle().getSiege().killedCT(); + + if ((_guards != null) && !_guards.isEmpty()) + { + for (Spawn spawn : _guards) + { + try + { + spawn.stopRespawn(); + // spawn.getLastSpawn().doDie(spawn.getLastSpawn()); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Error at ControlTower", e); + } + } + + _guards.clear(); + } + } + + return super.doDie(killer); + } + + public void registerGuard(Spawn guard) + { + getGuards().add(guard); + } + + private Collection getGuards() + { + if (_guards == null) + { + synchronized (this) + { + if (_guards == null) + { + _guards = ConcurrentHashMap.newKeySet(); + } + } + } + + return _guards; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/Cubic.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/Cubic.java new file mode 100644 index 0000000..8311431 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/Cubic.java @@ -0,0 +1,802 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.instance; + +import java.util.List; +import java.util.concurrent.Future; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.commons.threads.ThreadPool; +import org.l2jmobius.commons.util.Rnd; +import org.l2jmobius.gameserver.config.PlayerConfig; +import org.l2jmobius.gameserver.data.xml.CubicData; +import org.l2jmobius.gameserver.entity.WorldObject; +import org.l2jmobius.gameserver.entity.actor.Attackable; +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.Summon; +import org.l2jmobius.gameserver.entity.actor.tasks.cubics.CubicAction; +import org.l2jmobius.gameserver.entity.actor.tasks.cubics.CubicDisappear; +import org.l2jmobius.gameserver.entity.actor.tasks.cubics.CubicHeal; +import org.l2jmobius.gameserver.entity.actor.templates.CubicTemplate; +import org.l2jmobius.gameserver.entity.cubic.CubicSelectionMode; +import org.l2jmobius.gameserver.entity.cubic.CubicSkill; +import org.l2jmobius.gameserver.entity.cubic.CubicTargetType; +import org.l2jmobius.gameserver.entity.groups.Party; +import org.l2jmobius.gameserver.entity.zone.ZoneId; +import org.l2jmobius.gameserver.managers.DuelManager; +import org.l2jmobius.gameserver.mechanics.effects.EffectType; +import org.l2jmobius.gameserver.mechanics.skill.BuffInfo; +import org.l2jmobius.gameserver.mechanics.skill.EffectScope; +import org.l2jmobius.gameserver.mechanics.skill.Skill; +import org.l2jmobius.gameserver.mechanics.stats.Formulas; +import org.l2jmobius.gameserver.mechanics.stats.Stat; +import org.l2jmobius.gameserver.network.SystemMessageId; + +/** + * @author UnAfraid, Mobius, BazookaRpm + */ +public class Cubic +{ + private static final Logger LOGGER = Logger.getLogger(Cubic.class.getName()); + + // Type of Cubics + public static final int STORM_CUBIC = 1; + public static final int VAMPIRIC_CUBIC = 2; + public static final int LIFE_CUBIC = 3; + public static final int VIPER_CUBIC = 4; + public static final int POLTERGEIST_CUBIC = 5; + public static final int BINDING_CUBIC = 6; + public static final int AQUA_CUBIC = 7; + public static final int SPARK_CUBIC = 8; + public static final int ATTRACT_CUBIC = 9; + + // Max range of cubic skills. + // TODO: Check/fix the max range + public static final int MAX_MAGIC_RANGE = 900; + + // Cubic skills + public static final int SKILL_CUBIC_HEAL = 4051; + + private final Player _owner; + private Creature _target; + + private final int _cubicId; + private final CubicTemplate _template; + private final int _cubicPower; + private final int _cubicDelay; + private final int _cubicSkillChance; + private final int _cubicMaxCount; + private final boolean _givenByOther; + + private final List _cubicSkills; + + private Future _disappearTask; + private Future _actionTask; + + public Cubic(Player owner, int cubicId, int level, int cubicPower, int cubicDelay, int cubicSkillChance, int cubicMaxCount, int cubicDuration, boolean givenByOther) + { + _owner = owner; + _cubicId = cubicId; + _template = CubicData.getInstance().getCubicTemplate(cubicId, level); + if (_template == null) + { + _cubicPower = cubicPower; + _cubicDelay = cubicDelay * 1000; + _cubicSkillChance = cubicSkillChance; + _cubicMaxCount = cubicMaxCount; + _cubicSkills = java.util.Collections.emptyList(); + LOGGER.warning("Missing cubic template. cubicId: " + cubicId + " level: " + level + " ownerId: " + owner.getObjectId()); + } + else + { + _cubicPower = cubicPower > 0 ? cubicPower : _template.getBaseMAtk(); + _cubicDelay = (cubicDelay > 0 ? cubicDelay : _template.getDelay()) * 1000; + _cubicSkillChance = cubicSkillChance; + _cubicMaxCount = cubicMaxCount > -1 ? cubicMaxCount : _template.getMaxCount(); + _cubicSkills = _template.getCubicSkills(); + } + _givenByOther = givenByOther; + + final int duration = cubicDuration > 0 ? cubicDuration : _template != null ? _template.getDuration() : cubicDuration; + _disappearTask = ThreadPool.schedule(new CubicDisappear(this), duration * 1000); + if ((_template != null) && (_template.getTargetType() == CubicTargetType.HEAL)) + { + doAction(); + } + } + + public void doAction() + { + if ((_actionTask == null) && (_template != null) && !_cubicSkills.isEmpty()) + { + synchronized (this) + { + if (_actionTask == null) + { + if (_template.getTargetType() == CubicTargetType.HEAL) + { + _actionTask = ThreadPool.scheduleAtFixedRate(new CubicHeal(this), 0, _cubicDelay); + } + else + { + _actionTask = ThreadPool.scheduleAtFixedRate(new CubicAction(this, _cubicSkillChance), 0, _cubicDelay); + } + } + } + } + } + + public int getId() + { + return _cubicId; + } + + public Player getOwner() + { + return _owner; + } + + public int getCubicPower() + { + return _cubicPower; + } + + public Creature getTarget() + { + return _target; + } + + public void setTarget(Creature target) + { + _target = target; + } + + public CubicTemplate getTemplate() + { + return _template; + } + + public List getCubicSkills() + { + return _cubicSkills; + } + + public CubicSkill getCubicSkill() + { + if (getNormalCubicSkillCount() == 0) + { + return null; + } + + final CubicSelectionMode selectionMode = _template != null ? _template.getSelectionMode() : CubicSelectionMode.RANDOM; + switch (selectionMode) + { + case ORDERED: + { + return getOrderedCubicSkill(); + } + case WEIGHTED: + { + return getWeightedCubicSkill(); + } + case RANDOM: + default: + { + return getRandomCubicSkill(); + } + } + } + + private int getNormalCubicSkillCount() + { + int count = 0; + for (CubicSkill cubicSkill : _cubicSkills) + { + if (!cubicSkill.isPriority()) + { + count++; + } + } + + return count; + } + + private CubicSkill getRandomCubicSkill() + { + int index = Rnd.get(getNormalCubicSkillCount()); + for (CubicSkill cubicSkill : _cubicSkills) + { + if (cubicSkill.isPriority()) + { + continue; + } + + if (index-- == 0) + { + return cubicSkill; + } + } + + return null; + } + + private CubicSkill getOrderedCubicSkill() + { + for (CubicSkill cubicSkill : _cubicSkills) + { + if (cubicSkill.isPriority()) + { + continue; + } + + if ((cubicSkill.getTriggerRate() >= 100) || (Rnd.get(100) < cubicSkill.getTriggerRate())) + { + return cubicSkill; + } + } + + return null; + } + + private CubicSkill getWeightedCubicSkill() + { + int totalRate = 0; + for (CubicSkill cubicSkill : _cubicSkills) + { + if (!cubicSkill.isPriority() && (cubicSkill.getTriggerRate() > 0)) + { + totalRate += cubicSkill.getTriggerRate(); + } + } + + if (totalRate <= 0) + { + return getRandomCubicSkill(); + } + + int chance = Rnd.get(totalRate); + for (CubicSkill cubicSkill : _cubicSkills) + { + if (cubicSkill.isPriority() || (cubicSkill.getTriggerRate() <= 0)) + { + continue; + } + + chance -= cubicSkill.getTriggerRate(); + if (chance < 0) + { + return cubicSkill; + } + } + + return getRandomCubicSkill(); + } + + public int getCubicMaxCount() + { + return _cubicMaxCount; + } + + public void stopAction() + { + _target = null; + if (_actionTask != null) + { + _actionTask.cancel(true); + _actionTask = null; + } + } + + public void cancelDisappear() + { + if (_disappearTask != null) + { + _disappearTask.cancel(true); + _disappearTask = null; + } + } + + /** this sets the enemy target for a cubic */ + public void getCubicTarget() + { + try + { + _target = null; + final WorldObject ownerTarget = _owner.getTarget(); + if (ownerTarget == null) + { + return; + } + + // Custom event targeting + if (_owner.isOnEvent()) + { + final Player target = ownerTarget.asPlayer(); + if ((target != null) && ((_owner.getTeam() != target.getTeam()) || _owner.isOnSoloEvent()) && !(target.isDead())) + { + _target = ownerTarget.asCreature(); + } + return; + } + + // Duel targeting + if (_owner.isInDuel()) + { + final Player playerA = DuelManager.getInstance().getDuel(_owner.getDuelId()).getPlayerA(); + final Player playerB = DuelManager.getInstance().getDuel(_owner.getDuelId()).getPlayerB(); + if (DuelManager.getInstance().getDuel(_owner.getDuelId()).isPartyDuel()) + { + final Party partyA = playerA.getParty(); + final Party partyB = playerB.getParty(); + Party partyEnemy = null; + if (partyA != null) + { + if (partyA.getMembers().contains(_owner)) + { + if (partyB != null) + { + partyEnemy = partyB; + } + else + { + _target = playerB; + } + } + else + { + partyEnemy = partyA; + } + } + else + { + if (playerA == _owner) + { + if (partyB != null) + { + partyEnemy = partyB; + } + else + { + _target = playerB; + } + } + else + { + _target = playerA; + } + } + + if (((_target == playerA) || (_target == playerB)) && (_target == ownerTarget)) + { + return; + } + + if (partyEnemy != null) + { + if (partyEnemy.getMembers().contains(ownerTarget)) + { + _target = ownerTarget.asCreature(); + } + return; + } + } + + if ((playerA != _owner) && (ownerTarget == playerA)) + { + _target = playerA; + return; + } + + if ((playerB != _owner) && (ownerTarget == playerB)) + { + _target = playerB; + return; + } + + _target = null; + return; + } + + // Olympiad targeting + if (_owner.isInOlympiadMode()) + { + if (_owner.isOlympiadStart() && ownerTarget.isPlayable()) + { + final Player targetPlayer = ownerTarget.asPlayer(); + if ((targetPlayer != null) && (targetPlayer.getOlympiadGameId() == _owner.getOlympiadGameId()) && (targetPlayer.getOlympiadSide() != _owner.getOlympiadSide())) + { + _target = ownerTarget.asCreature(); + } + } + return; + } + + // test owners target if it is valid then use it + if (ownerTarget.isCreature() && (ownerTarget != _owner.getSummon()) && (ownerTarget != _owner)) + { + // target mob which has aggro on you or your summon + if (ownerTarget.isAttackable()) + { + final Attackable attackable = ownerTarget.asAttackable(); + if (attackable.isInAggroList(_owner) && !attackable.isDead()) + { + _target = ownerTarget.asCreature(); + return; + } + + if (_owner.hasSummon() && attackable.isInAggroList(_owner.getSummon()) && !attackable.isDead()) + { + _target = ownerTarget.asCreature(); + return; + } + } + + // get target in pvp or in siege + Player enemy = null; + if (((_owner.getPvpFlag() > 0) && !_owner.isInsideZone(ZoneId.PEACE)) || _owner.isInsideZone(ZoneId.PVP)) + { + if (!ownerTarget.asCreature().isDead()) + { + enemy = ownerTarget.asPlayer(); + } + + if (enemy != null) + { + boolean targetIt = true; + if (_owner.getParty() != null) + { + if (_owner.getParty().getMembers().contains(enemy)) + { + targetIt = false; + } + else if ((_owner.getParty().getCommandChannel() != null) && _owner.getParty().getCommandChannel().getMembers().contains(enemy)) + { + targetIt = false; + } + } + + if ((_owner.getClan() != null) && !_owner.isInsideZone(ZoneId.PVP)) + { + if (_owner.getClan().isMember(enemy.getObjectId())) + { + targetIt = false; + } + + if ((_owner.getAllyId() > 0) && (enemy.getAllyId() > 0) && (_owner.getAllyId() == enemy.getAllyId())) + { + targetIt = false; + } + } + + if ((enemy.getPvpFlag() == 0) && !enemy.isInsideZone(ZoneId.PVP)) + { + targetIt = false; + } + + if (enemy.isInsideZone(ZoneId.PEACE)) + { + targetIt = false; + } + + if ((_owner.getSiegeState() > 0) && (_owner.getSiegeState() == enemy.getSiegeState())) + { + targetIt = false; + } + + if (!enemy.isSpawned()) + { + targetIt = false; + } + + if (targetIt) + { + _target = enemy; + } + } + } + } + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Cubic target selection failed. cubicId: " + _cubicId + " ownerId: " + _owner.getObjectId(), e); + } + } + + private void applyCubicEffects(Skill skill, Creature target) + { + if ((target == null) || ((target != _owner) && skill.hasNegativeEffect() && (target.isInvul() || (_owner.isGM() && !_owner.getAccessLevel().canGiveDamage())))) + { + return; + } + + if (target.isInvulAgainst(skill.getId(), skill.getLevel())) + { + return; + } + + final boolean addContinuousEffects = skill.isToggle() || skill.isContinuous(); + final BuffInfo info = new BuffInfo(_owner, target, skill); + skill.applyEffectScope(EffectScope.GENERAL, info, true, addContinuousEffects); + final EffectScope pvpOrPveEffectScope = _owner.isPlayable() && target.isAttackable() ? EffectScope.PVE : _owner.isPlayable() && target.isPlayable() ? EffectScope.PVP : null; + skill.applyEffectScope(pvpOrPveEffectScope, info, true, addContinuousEffects); + skill.applyEffectScope(EffectScope.CHANNELING, info, true, addContinuousEffects); + if (addContinuousEffects) + { + target.getEffectList().add(info); + } + } + + public void useCubicContinuous(Skill skill, List targets) + { + for (Creature target : targets) + { + if ((target == null) || target.isDead()) + { + continue; + } + + if (skill.hasNegativeEffect()) + { + final boolean acted = Formulas.calcCubicSkillSuccess(this, target, skill, Formulas.SHIELD_DEFENSE_FAILED); + if (!acted) + { + _owner.sendPacket(SystemMessageId.YOUR_ATTACK_HAS_FAILED); + continue; + } + } + + // Apply effects + applyCubicEffects(skill, target); + + // If this is a negative effect skill notify the duel manager, so it can be removed after the duel (player & target must be in the same duel). + if (target.isPlayer()) + { + final Player player = target.asPlayer(); + if (player.isInDuel() && skill.hasNegativeEffect() && (_owner.getDuelId() == player.getDuelId())) + { + DuelManager.getInstance().onBuff(player, skill); + } + } + } + } + + /** + * @param skill + * @param targets + */ + public void useCubicMdam(Skill skill, List targets) + { + for (Creature target : targets) + { + if (target == null) + { + continue; + } + + if (target.isAlikeDead()) + { + if (target.isPlayer() && PlayerConfig.FAKE_DEATH_DAMAGE_STAND) + { + target.stopFakeDeath(true); + } + else + { + continue; + } + } + + final boolean mcrit = Formulas.calcMCrit(_owner.getMCriticalHit(target, skill)); + final byte shld = Formulas.calcShldUse(_owner, target, skill); + int damage = (int) Formulas.calcMagicDam(this, target, skill, mcrit, shld); + if (damage > 0) + { + // Manage attack or cast break of the target (calculating rate, sending message...). + if (!target.isRaid() && Formulas.calcAtkBreak(target, damage)) + { + target.breakAttack(); + target.breakCast(); + } + + // Shield Deflect Magic: If target is reflecting the skill then no damage is done. + if (target.getStat().calcStat(Stat.VENGEANCE_SKILL_MAGIC_DAMAGE, 0, target, skill) > Rnd.get(100)) + { + damage = 0; + } + else + { + _owner.sendDamageMessage(target, damage, mcrit, false, false); + target.reduceCurrentHp(damage, _owner, skill); + } + } + } + } + + public void useCubicDrain(Skill skill, List targets) + { + for (Creature target : targets) + { + if (target.isAlikeDead()) + { + continue; + } + + final boolean mcrit = Formulas.calcMCrit(_owner.getMCriticalHit(target, skill)); + final byte shld = Formulas.calcShldUse(_owner, target, skill); + final int damage = (int) Formulas.calcMagicDam(this, target, skill, mcrit, shld); + + // TODO: Unhardcode fixed value + final double hpAdd = (0.4 * damage); + final Player owner = _owner; + final double hp = ((owner.getCurrentHp() + hpAdd) > owner.getMaxHp() ? owner.getMaxHp() : (owner.getCurrentHp() + hpAdd)); + owner.setCurrentHp(hp); + + // Check to see if we should damage the target. + if ((damage > 0) && !target.isDead()) + { + target.reduceCurrentHp(damage, _owner, skill); + + // Manage attack or cast break of the target (calculating rate, sending message...). + if (!target.isRaid() && Formulas.calcAtkBreak(target, damage)) + { + target.breakAttack(); + target.breakCast(); + } + + owner.sendDamageMessage(target, damage, mcrit, false, false); + } + } + } + + public void useCubicDisabler(Skill skill, List targets) + { + for (Creature target : targets) + { + if ((target == null) || target.isDead()) + { + continue; + } + + if (skill.hasEffectType(EffectType.STUN, EffectType.PARALYZE, EffectType.ROOT) && Formulas.calcCubicSkillSuccess(this, target, skill, Formulas.SHIELD_DEFENSE_FAILED)) + { + // Apply effects + applyCubicEffects(skill, target); + + // If this is a negative effect skill notify the duel manager, so it can be removed after the duel (player & target must be in the same duel). + if (target.isPlayer()) + { + final Player player = target.asPlayer(); + if (player.isInDuel() && skill.hasNegativeEffect() && (_owner.getDuelId() == player.getDuelId())) + { + DuelManager.getInstance().onBuff(player, skill); + } + } + } + + if (skill.hasEffectType(EffectType.AGGRESSION) && Formulas.calcCubicSkillSuccess(this, target, skill, Formulas.SHIELD_DEFENSE_FAILED)) + { + if (target.isAttackable()) + { + target.getAI().notifyActionAggression(_owner, (int) ((150 * skill.getPower()) / (target.getLevel() + 7))); + } + + // Apply effects + applyCubicEffects(skill, target); + } + } + } + + /** + * @param owner + * @param target + * @return true if the target is inside of the owner's max Cubic range + */ + public static boolean isInCubicRange(Creature owner, Creature target) + { + if ((owner == null) || (target == null)) + { + return false; + } + + // Temporary range check until real behavior of cubics is known/coded. + final long range = MAX_MAGIC_RANGE; + final long x = owner.getX() - target.getX(); + final long y = owner.getY() - target.getY(); + final long z = owner.getZ() - target.getZ(); + return (((x * x) + (y * y) + (z * z)) <= (range * range)); + } + + /** this sets the friendly target for a cubic */ + public void cubicTargetForHeal() + { + Creature target = null; + double percentleft = 100.0; + Party party = _owner.getParty(); + + // if owner is in a duel but not in a party duel, then it is the same as he does not have a party + if (_owner.isInDuel() && !DuelManager.getInstance().getDuel(_owner.getDuelId()).isPartyDuel()) + { + party = null; + } + + if ((party != null) && !_owner.isInOlympiadMode()) + { + // Get all visible objects in a spheric area near the Creature. + // Get a list of Party Members. + for (Creature partyMember : party.getMembers()) + { + // if party member not dead, check if he is in cast range of heal cubic and member is in cubic casting range, check if he need heal and if he have the lowest HP + if (!partyMember.isDead() && isInCubicRange(_owner, partyMember) && (partyMember.getCurrentHp() < partyMember.getMaxHp()) && (percentleft > (partyMember.getCurrentHp() / partyMember.getMaxHp()))) + { + percentleft = (partyMember.getCurrentHp() / partyMember.getMaxHp()); + target = partyMember; + } + + final Player player = partyMember.asPlayer(); + if (player != null) + { + final Summon summon = player.getSummon(); + if (summon != null) + { + if (summon.isDead()) + { + continue; + } + + // If party member's pet not dead, check if it is in cast range of heal cubic. + if (!isInCubicRange(_owner, summon)) + { + continue; + } + + // member's pet is in cubic casting range, check if he need heal and if he have the lowest HP + if ((summon.getCurrentHp() < summon.getMaxHp()) && (percentleft > (summon.getCurrentHp() / summon.getMaxHp()))) + { + percentleft = (summon.getCurrentHp() / summon.getMaxHp()); + target = summon; + } + } + } + } + } + else + { + if (_owner.getCurrentHp() < _owner.getMaxHp()) + { + percentleft = (_owner.getCurrentHp() / _owner.getMaxHp()); + target = _owner; + } + + if (_owner.hasSummon() && !_owner.getSummon().isDead() && (_owner.getSummon().getCurrentHp() < _owner.getSummon().getMaxHp()) && (percentleft > (_owner.getSummon().getCurrentHp() / _owner.getSummon().getMaxHp())) && isInCubicRange(_owner, _owner.getSummon())) + { + target = _owner.getSummon(); + } + } + + _target = target; + } + + public boolean givenByOther() + { + return _givenByOther; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/DawnPriest.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/DawnPriest.java new file mode 100644 index 0000000..f4f9ab3 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/DawnPriest.java @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.instance; + +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.enums.creature.InstanceType; +import org.l2jmobius.gameserver.entity.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.mechanics.sevensigns.SevenSigns; +import org.l2jmobius.gameserver.network.serverpackets.ActionFailed; +import org.l2jmobius.gameserver.network.serverpackets.NpcHtmlMessage; + +public class DawnPriest extends SignsPriest +{ + /** + * Creates a dawn priest. + * @param template the dawn priest NPC template + */ + public DawnPriest(NpcTemplate template) + { + super(template); + setInstanceType(InstanceType.DawnPriest); + } + + @Override + public void onBypassFeedback(Player player, String command) + { + if (command.startsWith("Chat")) + { + showChatWindow(player); + } + else + { + super.onBypassFeedback(player, command); + } + } + + @Override + public void showChatWindow(Player player) + { + player.sendPacket(ActionFailed.STATIC_PACKET); + + String filename = SevenSigns.SEVEN_SIGNS_HTML_PATH; + final int sealGnosisOwner = SevenSigns.getInstance().getSealOwner(SevenSigns.SEAL_GNOSIS); + final int playerCabal = SevenSigns.getInstance().getPlayerCabal(player.getObjectId()); + final boolean isSealValidationPeriod = SevenSigns.getInstance().isSealValidationPeriod(); + final boolean isCompResultsPeriod = SevenSigns.getInstance().isCompResultsPeriod(); + final int recruitPeriod = SevenSigns.getInstance().getCurrentPeriod(); + final int compWinner = SevenSigns.getInstance().getCabalHighestScore(); + + switch (playerCabal) + { + case SevenSigns.CABAL_DAWN: + { + if (isCompResultsPeriod) + { + filename += "dawn_priest_5.htm"; + } + else if (recruitPeriod == 0) + { + filename += "dawn_priest_6.htm"; + } + else if (isSealValidationPeriod) + { + if (compWinner == SevenSigns.CABAL_DAWN) + { + if (compWinner != sealGnosisOwner) + { + filename += "dawn_priest_2c.htm"; + } + else + { + filename += "dawn_priest_2a.htm"; + } + } + else if (compWinner == SevenSigns.CABAL_NULL) + { + filename += "dawn_priest_2d.htm"; + } + else + { + filename += "dawn_priest_2b.htm"; + } + } + else + { + filename += "dawn_priest_1b.htm"; + } + break; + } + case SevenSigns.CABAL_DUSK: + { + if (isSealValidationPeriod) + { + filename += "dawn_priest_3a.htm"; + } + else + { + filename += "dawn_priest_3b.htm"; + } + break; + } + default: + { + if (isCompResultsPeriod) + { + filename += "dawn_priest_5.htm"; + } + else if (recruitPeriod == 0) + { + filename += "dawn_priest_6.htm"; + } + else if (isSealValidationPeriod) + { + if (compWinner == SevenSigns.CABAL_DAWN) + { + filename += "dawn_priest_4.htm"; + } + else if (compWinner == SevenSigns.CABAL_NULL) + { + filename += "dawn_priest_2d.htm"; + } + else + { + filename += "dawn_priest_2b.htm"; + } + } + else + { + filename += "dawn_priest_1a.htm"; + } + break; + } + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, filename); + html.replace("%objectId%", String.valueOf(getObjectId())); + player.sendPacket(html); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/Decoy.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/Decoy.java new file mode 100644 index 0000000..849cdc2 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/Decoy.java @@ -0,0 +1,254 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.instance; + +import java.util.concurrent.Future; +import java.util.logging.Level; + +import org.l2jmobius.commons.threads.ThreadPool; +import org.l2jmobius.gameserver.data.xml.SkillData; +import org.l2jmobius.gameserver.entity.World; +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.enums.creature.InstanceType; +import org.l2jmobius.gameserver.entity.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.entity.item.Weapon; +import org.l2jmobius.gameserver.entity.item.instance.Item; +import org.l2jmobius.gameserver.managers.ZoneManager; +import org.l2jmobius.gameserver.mechanics.skill.Skill; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.serverpackets.CharInfo; +import org.l2jmobius.gameserver.network.serverpackets.ServerPacket; +import org.l2jmobius.gameserver.taskmanagers.DecayTaskManager; + +public class Decoy extends Creature +{ + private final Player _owner; + private Future _decoyLifeTask; + private Future _hateSpam; + + public Decoy(NpcTemplate template, Player owner, int totalLifeTime) + { + this(template, owner, totalLifeTime, true); + } + + public Decoy(NpcTemplate template, Player owner, int totalLifeTime, boolean aggressive) + { + super(template); + setInstanceType(InstanceType.Decoy); + _owner = owner; + setXYZInvisible(owner.getX(), owner.getY(), owner.getZ()); + setInvul(false); + _decoyLifeTask = ThreadPool.schedule(() -> unSummon(owner), totalLifeTime); + if (aggressive) + { + final int hateSpamSkillId = 5272; + final int skilllevel = Math.min(getTemplate().getDisplayId() - 13070, SkillData.getInstance().getMaxLevel(hateSpamSkillId)); + _hateSpam = ThreadPool.scheduleAtFixedRate(new HateSpam(this, SkillData.getInstance().getSkill(hateSpamSkillId, skilllevel)), 2000, 5000); + } + } + + @Override + public boolean doDie(Creature killer) + { + if (!super.doDie(killer)) + { + return false; + } + + if (_hateSpam != null) + { + _hateSpam.cancel(true); + _hateSpam = null; + } + + unSummon(_owner); + DecayTaskManager.getInstance().add(this); + return true; + } + + private static class HateSpam implements Runnable + { + private final Decoy _player; + private final Skill _skill; + + HateSpam(Decoy player, Skill hate) + { + _player = player; + _skill = hate; + } + + @Override + public void run() + { + try + { + _player.setTarget(_player); + _player.doCast(_skill); + } + catch (Throwable e) + { + LOGGER.log(Level.SEVERE, "Decoy Error: ", e); + } + } + } + + public synchronized void unSummon(Player owner) + { + if (_hateSpam != null) + { + _hateSpam.cancel(true); + _hateSpam = null; + } + + if (isSpawned() && !isDead()) + { + ZoneManager.getInstance().getRegion(this).removeFromZones(this); + owner.setDecoy(null); + decayMe(); + } + + if (_decoyLifeTask != null) + { + _decoyLifeTask.cancel(false); + _decoyLifeTask = null; + } + } + + @Override + public void onSpawn() + { + super.onSpawn(); + sendPacket(new CharInfo(this, false)); + } + + @Override + public void updateAbnormalEffect() + { + World.forEachVisibleObject(this, Player.class, player -> + { + if (isVisibleFor(player)) + { + player.sendPacket(new CharInfo(this, false)); + } + }); + } + + public void stopDecay() + { + DecayTaskManager.getInstance().cancel(this); + } + + @Override + public void onDecay() + { + deleteMe(_owner); + } + + @Override + public boolean isAutoAttackable(Creature attacker) + { + return _owner.isAutoAttackable(attacker); + } + + @Override + public Item getActiveWeaponInstance() + { + return null; + } + + @Override + public Weapon getActiveWeaponItem() + { + return null; + } + + @Override + public Item getSecondaryWeaponInstance() + { + return null; + } + + @Override + public Weapon getSecondaryWeaponItem() + { + return null; + } + + @Override + public int getId() + { + return getTemplate().getId(); + } + + @Override + public int getLevel() + { + return getTemplate().getLevel(); + } + + public void deleteMe(Player owner) + { + decayMe(); + owner.setDecoy(null); + } + + public Player getOwner() + { + return _owner; + } + + @Override + public Player asPlayer() + { + return _owner; + } + + @Override + public NpcTemplate getTemplate() + { + return (NpcTemplate) super.getTemplate(); + } + + @Override + public void sendInfo(Player player) + { + player.sendPacket(new CharInfo(this, false)); + } + + @Override + public void sendPacket(ServerPacket packet) + { + if (_owner != null) + { + _owner.sendPacket(packet); + } + } + + @Override + public void sendPacket(SystemMessageId id) + { + if (_owner != null) + { + _owner.sendPacket(id); + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/Defender.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/Defender.java new file mode 100644 index 0000000..3d842c0 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/Defender.java @@ -0,0 +1,205 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.instance; + +import org.l2jmobius.gameserver.ai.CreatureAI; +import org.l2jmobius.gameserver.ai.SiegeGuardAI; +import org.l2jmobius.gameserver.ai.SpecialSiegeGuardAI; +import org.l2jmobius.gameserver.entity.actor.Attackable; +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.enums.creature.InstanceType; +import org.l2jmobius.gameserver.entity.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.managers.CastleManager; +import org.l2jmobius.gameserver.mechanics.siege.Castle; +import org.l2jmobius.gameserver.mechanics.siege.clanhalls.SiegableHall; +import org.l2jmobius.gameserver.network.serverpackets.ActionFailed; + +public class Defender extends Attackable +{ + private Castle _castle = null; // the castle which the instance should defend + private SiegableHall _hall = null; // the siegable hall which the instance should defend + + /** + * Creates a defender. + * @param template the defender NPC template + */ + public Defender(NpcTemplate template) + { + super(template); + setInstanceType(InstanceType.Defender); + } + + @Override + protected CreatureAI initAI() + { + if (getCastle(10000) != null) + { + return new SiegeGuardAI(this); + } + + return new SpecialSiegeGuardAI(this); + } + + /** + * Return True if a siege is in progress and the Creature attacker isn't a Defender. + * @param attacker The Creature that the SiegeGuard try to attack + */ + @Override + public boolean isAutoAttackable(Creature attacker) + { + // Attackable during siege by all except defenders. + if (!attacker.isPlayable()) + { + return false; + } + + final Player player = attacker.asPlayer(); + + // Check if siege is in progress. + if (((_castle != null) && _castle.getZone().isActive()) || ((_hall != null) && _hall.getSiegeZone().isActive())) + { + final int activeSiegeId = _castle != null ? _castle.getResidenceId() : _hall != null ? _hall.getId() : 0; + + // Check if player is an enemy of this defender npc. + if ((player != null) && (((player.getSiegeState() == 2) && !player.isRegisteredOnThisSiegeField(activeSiegeId)) || ((player.getSiegeState() == 1)) || (player.getSiegeState() == 0))) + { + return true; + } + } + + return false; + } + + @Override + public boolean hasRandomAnimation() + { + return false; + } + + /** + * This method forces guard to return to home location previously set + */ + @Override + public void returnHome() + { + if (getWalkSpeed() <= 0) + { + return; + } + + if (getSpawn() == null) + { + return; + } + + if (!isInsideRadius2D(getSpawn(), 40)) + { + clearAggroList(); + + if (hasAI()) + { + getAI().setIntentionMoveTo(getSpawn().getLocation()); + } + } + } + + @Override + public void onSpawn() + { + super.onSpawn(); + + _castle = CastleManager.getInstance().getCastle(getX(), getY(), getZ()); + _hall = getConquerableHall(); + if ((_castle == null) && (_hall == null)) + { + LOGGER.warning("Defender spawned outside of Fortress, Castle or Siegable hall Zone! NpcId: " + getId() + " x=" + getX() + " y=" + getY() + " z=" + getZ()); + } + } + + /** + * Custom onAction behaviour. Note that super() is not called because guards need extra check to see if a player should interact or ATTACK them when clicked. + */ + @Override + public void onAction(Player player, boolean interact) + { + if (!canTarget(player)) + { + player.sendPacket(ActionFailed.STATIC_PACKET); + return; + } + + // Check if the Player already target the Npc. + if (this != player.getTarget()) + { + // Set the target of the Player player. + player.setTarget(this); + } + else if (interact) + { + // This max heigth difference might need some tweaking. + if (isAutoAttackable(player) && !isAlikeDead() && (Math.abs(player.getZ() - getZ()) < 600)) + { + player.getAI().setIntentionAttack(this); + } + + // Notify the Player AI with INTERACT. + if (!isAutoAttackable(player) && !canInteract(player)) + { + player.getAI().setIntentionInteract(this); + } + } + + // Send a Server->Client ActionFailed to the Player in order to avoid that the client wait another packet. + player.sendPacket(ActionFailed.STATIC_PACKET); + } + + @Override + public void addDamageHate(Creature attacker, long damage, long aggro) + { + if (attacker == null) + { + return; + } + + if (!(attacker instanceof Defender)) + { + if ((damage == 0) && (aggro <= 1) && attacker.isPlayable()) + { + final Player player = attacker.asPlayer(); + + // Check if siege is in progress. + if (((_castle != null) && _castle.getZone().isActive()) || ((_hall != null) && _hall.getSiegeZone().isActive())) + { + final int activeSiegeId = _castle != null ? _castle.getResidenceId() : _hall != null ? _hall.getId() : 0; + + // Do not add hate on defenders. + if ((player.getSiegeState() == 2) && player.isRegisteredOnThisSiegeField(activeSiegeId)) + { + return; + } + } + } + + super.addDamageHate(attacker, damage, aggro); + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/Door.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/Door.java new file mode 100644 index 0000000..cba2a38 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/Door.java @@ -0,0 +1,750 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.instance; + +import java.util.Set; +import java.util.concurrent.Future; +import java.util.logging.Level; + +import org.l2jmobius.commons.threads.ThreadPool; +import org.l2jmobius.commons.util.Rnd; +import org.l2jmobius.gameserver.ai.CreatureAI; +import org.l2jmobius.gameserver.ai.DoorAI; +import org.l2jmobius.gameserver.data.sql.ClanHallTable; +import org.l2jmobius.gameserver.data.xml.DoorData; +import org.l2jmobius.gameserver.entity.Location; +import org.l2jmobius.gameserver.entity.World; +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.enums.creature.InstanceType; +import org.l2jmobius.gameserver.entity.actor.enums.creature.Race; +import org.l2jmobius.gameserver.entity.actor.stat.DoorStat; +import org.l2jmobius.gameserver.entity.actor.status.DoorStatus; +import org.l2jmobius.gameserver.entity.actor.templates.DoorTemplate; +import org.l2jmobius.gameserver.entity.clan.Clan; +import org.l2jmobius.gameserver.entity.instancezone.Instance; +import org.l2jmobius.gameserver.entity.item.Weapon; +import org.l2jmobius.gameserver.entity.item.instance.Item; +import org.l2jmobius.gameserver.entity.residences.ClanHall; +import org.l2jmobius.gameserver.managers.CastleManager; +import org.l2jmobius.gameserver.managers.InstanceManager; +import org.l2jmobius.gameserver.mechanics.siege.Castle; +import org.l2jmobius.gameserver.mechanics.siege.clanhalls.SiegableHall; +import org.l2jmobius.gameserver.mechanics.skill.Skill; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.serverpackets.DoorStatusUpdate; +import org.l2jmobius.gameserver.network.serverpackets.OnEventTrigger; +import org.l2jmobius.gameserver.network.serverpackets.StaticObjectInfo; +import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; + +public class Door extends Creature +{ + public static final byte OPEN_BY_CLICK = 1; + public static final byte OPEN_BY_TIME = 2; + public static final byte OPEN_BY_ITEM = 4; + public static final byte OPEN_BY_SKILL = 8; + public static final byte OPEN_BY_CYCLE = 16; + + /** The castle index in the array of Castle this Npc belongs to */ + private int _castleIndex = -2; + private ClanHall _clanHall; + boolean _open = false; + private boolean _isAttackableDoor = false; + private boolean _isTargetable; + private int _meshindex = 1; + + // used for autoclose on open + private Future _autoCloseTask; + + /** + * Creates a door. + * @param template the door template + */ + public Door(DoorTemplate template) + { + super(template); + setInstanceType(InstanceType.Door); + setInvul(false); + setLethalable(false); + _open = template.isOpenByDefault(); + _isAttackableDoor = template.isAttackable(); + _isTargetable = template.isTargetable(); + if (getGroupName() != null) + { + DoorData.addDoorGroup(getGroupName(), getId()); + } + + if (isOpenableByTime()) + { + startTimerOpen(); + } + + final int clanhallId = template.getClanHallId(); + if (clanhallId > 0) + { + final ClanHall hall = ClanHallTable.getAllClanHalls().get(clanhallId); + if (hall != null) + { + setClanHall(hall); + hall.getDoors().add(this); + } + } + } + + @Override + protected CreatureAI initAI() + { + return new DoorAI(this); + } + + private void startTimerOpen() + { + int delay = _open ? getTemplate().getOpenTime() : getTemplate().getCloseTime(); + if (getTemplate().getRandomTime() > 0) + { + delay += Rnd.get(getTemplate().getRandomTime()); + } + + ThreadPool.schedule(new TimerOpen(), delay * 1000); + } + + @Override + public DoorTemplate getTemplate() + { + return (DoorTemplate) super.getTemplate(); + } + + @Override + public DoorStatus getStatus() + { + return (DoorStatus) super.getStatus(); + } + + @Override + public void initCharStatus() + { + setStatus(new DoorStatus(this)); + } + + @Override + public void initCharStat() + { + setStat(new DoorStat(this)); + } + + @Override + public DoorStat getStat() + { + return (DoorStat) super.getStat(); + } + + /** + * @return {@code true} if door is open-able by skill. + */ + public boolean isOpenableBySkill() + { + return (getTemplate().getOpenType() & OPEN_BY_SKILL) == OPEN_BY_SKILL; + } + + /** + * @return {@code true} if door is open-able by item. + */ + public boolean isOpenableByItem() + { + return (getTemplate().getOpenType() & OPEN_BY_ITEM) == OPEN_BY_ITEM; + } + + /** + * @return {@code true} if door is open-able by double-click. + */ + public boolean isOpenableByClick() + { + return (getTemplate().getOpenType() & OPEN_BY_CLICK) == OPEN_BY_CLICK; + } + + /** + * @return {@code true} if door is open-able by time. + */ + public boolean isOpenableByTime() + { + return (getTemplate().getOpenType() & OPEN_BY_TIME) == OPEN_BY_TIME; + } + + /** + * @return {@code true} if door is open-able by Field Cycle system. + */ + public boolean isOpenableByCycle() + { + return (getTemplate().getOpenType() & OPEN_BY_CYCLE) == OPEN_BY_CYCLE; + } + + @Override + public int getLevel() + { + return getTemplate().getLevel(); + } + + /** + * Gets the door ID. + * @return the door ID + */ + @Override + public int getId() + { + return getTemplate().getId(); + } + + /** + * @return Returns if the door is open. + */ + public boolean isOpen() + { + return _open; + } + + /** + * @param open The door open status. + */ + public void setOpen(boolean open) + { + _open = open; + if (getChildId() > 0) + { + final Door sibling = getSiblingDoor(getChildId()); + if (sibling != null) + { + sibling.notifyChildEvent(open); + } + else + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": cannot find child id: " + getChildId()); + } + } + } + + public boolean isAttackableDoor() + { + return _isAttackableDoor; + } + + public boolean isShowHp() + { + return getTemplate().isShowHp(); + } + + public void setIsAttackableDoor(boolean value) + { + _isAttackableDoor = value; + } + + public int getDamage() + { + final int dmg = 6 - (int) Math.ceil((getCurrentHp() / getMaxHp()) * 6); + if (dmg > 6) + { + return 6; + } + + if (dmg < 0) + { + return 0; + } + + return dmg; + } + + // TODO: Replace index with the castle id itself. + public Castle getCastle() + { + if (_castleIndex < 0) + { + _castleIndex = CastleManager.getInstance().getCastleIndex(this); + } + + if (_castleIndex < 0) + { + return null; + } + + return CastleManager.getInstance().getCastles().get(_castleIndex); + } + + public void setClanHall(ClanHall clanhall) + { + _clanHall = clanhall; + } + + public ClanHall getClanHall() + { + return _clanHall; + } + + public boolean isEnemy() + { + if ((getCastle() != null) && (getCastle().getResidenceId() > 0) && getCastle().getZone().isActive() && isShowHp()) + { + return true; + } + + if ((_clanHall != null) && _clanHall.isSiegableHall() && ((SiegableHall) _clanHall).getSiegeZone().isActive() && isShowHp()) + { + return true; + } + + return false; + } + + @Override + public boolean isAutoAttackable(Creature attacker) + { + // Doors can`t be attacked by NPCs. + if (!attacker.isPlayable()) + { + return false; + } + + if (_isAttackableDoor) + { + return true; + } + + if (!isShowHp()) + { + return false; + } + + final Player actingPlayer = attacker.asPlayer(); + if ((_clanHall != null) && _clanHall.isSiegableHall()) + { + final SiegableHall hall = (SiegableHall) _clanHall; + if (!hall.isSiegableHall()) + { + return false; + } + + return hall.isInSiege() && hall.getSiege().doorIsAutoAttackable() && hall.getSiege().checkIsAttacker(actingPlayer.getClan()); + } + + // Attackable only during siege by everyone (not owner). + final boolean isCastle = ((getCastle() != null) && (getCastle().getResidenceId() > 0) && getCastle().getZone().isActive()); + if (isCastle) + { + final Clan clan = actingPlayer.getClan(); + if ((clan != null) && (clan.getId() == getCastle().getOwnerId())) + { + return false; + } + } + + return isCastle; + } + + @Override + public void updateAbnormalEffect() + { + } + + /** + * Return null. + */ + @Override + public Item getActiveWeaponInstance() + { + return null; + } + + @Override + public Weapon getActiveWeaponItem() + { + return null; + } + + @Override + public Item getSecondaryWeaponInstance() + { + return null; + } + + @Override + public Weapon getSecondaryWeaponItem() + { + return null; + } + + @Override + public void broadcastStatusUpdate() + { + final StaticObjectInfo su = new StaticObjectInfo(this); + final DoorStatusUpdate dsu = new DoorStatusUpdate(this); + final OnEventTrigger oe = (getEmitter() > 0) ? new OnEventTrigger(this, !_open) : null; + + World.forEachVisibleObject(this, Player.class, player -> + { + if (!isVisibleFor(player)) + { + return; + } + + player.sendPacket(su); + player.sendPacket(dsu); + if (oe != null) + { + player.sendPacket(oe); + } + }); + } + + public void openMe() + { + if (getGroupName() != null) + { + manageGroupOpen(true, getGroupName()); + return; + } + + if (!isOpen()) + { + setOpen(true); + broadcastStatusUpdate(); + startAutoCloseTask(); + } + } + + public void closeMe() + { + // Remove close task. + final Future oldTask = _autoCloseTask; + if (oldTask != null) + { + _autoCloseTask = null; + oldTask.cancel(false); + } + + if (getGroupName() != null) + { + manageGroupOpen(false, getGroupName()); + return; + } + + if (isOpen()) + { + setOpen(false); + broadcastStatusUpdate(); + } + } + + private void manageGroupOpen(boolean open, String groupName) + { + final Set set = DoorData.getDoorsByGroup(groupName); + Door first = null; + for (Integer id : set) + { + final Door door = getSiblingDoor(id); + if (first == null) + { + first = door; + } + + if (door.isOpen() != open) + { + door.setOpen(open); + door.broadcastStatusUpdate(); + } + } + + if ((first != null) && open) + { + first.startAutoCloseTask(); // only one from group + } + } + + /** + * Door notify child about open state change + * @param open true if opened + */ + private void notifyChildEvent(boolean open) + { + final byte openThis = open ? getTemplate().getMasterDoorOpen() : getTemplate().getMasterDoorClose(); + if (openThis == 1) + { + openMe(); + } + else if (openThis == -1) + { + closeMe(); + } + } + + public String getDoorName() + { + return getTemplate().getName(); + } + + public int getX(int i) + { + return getTemplate().getNodeX()[i]; + } + + public int getY(int i) + { + return getTemplate().getNodeY()[i]; + } + + public int getZMin() + { + return getTemplate().getNodeZ(); + } + + public int getZMax() + { + return getTemplate().getNodeZ() + getTemplate().getHeight(); + } + + public void setMeshIndex(int mesh) + { + _meshindex = mesh; + } + + public int getMeshIndex() + { + return _meshindex; + } + + public int getEmitter() + { + return getTemplate().getEmmiter(); + } + + public boolean isWall() + { + return getTemplate().isWall(); + } + + public String getGroupName() + { + return getTemplate().getGroupName(); + } + + public int getChildId() + { + return getTemplate().getChildDoorId(); + } + + @Override + public void reduceCurrentHp(double damage, Creature attacker, boolean awake, boolean isDOT, Skill skill) + { + if (isWall() && !isInInstance()) + { + if (!attacker.isServitor()) + { + return; + } + + if (attacker.getTemplate().getRace() != Race.SIEGE_WEAPON) + { + return; + } + } + + super.reduceCurrentHp(damage, attacker, awake, isDOT, skill); + } + + @Override + public void reduceCurrentHpByDOT(double i, Creature attacker, Skill skill) + { + // doors can't be damaged by DOTs + } + + @Override + public boolean doDie(Creature killer) + { + if (!super.doDie(killer)) + { + return false; + } + + final boolean isCastle = ((getCastle() != null) && (getCastle().getResidenceId() > 0) && getCastle().getSiege().isInProgress()); + final boolean isHall = ((_clanHall != null) && _clanHall.isSiegableHall() && ((SiegableHall) _clanHall).isInSiege()); + if (isCastle || isHall) + { + broadcastPacket(new SystemMessage(SystemMessageId.THE_CASTLE_GATE_HAS_BEEN_BROKEN_DOWN)); + } + + return true; + } + + @Override + public void moveToLocation(int x, int y, int z, int offset) + { + } + + @Override + public void stopMove(Location loc) + { + } + + @Override + public void doAttack(Creature target) + { + } + + @Override + public void doCast(Skill skill) + { + } + + @Override + public void sendInfo(Player player) + { + if (isVisibleFor(player)) + { + player.sendPacket(new StaticObjectInfo(this)); + player.sendPacket(new DoorStatusUpdate(this)); + if (getEmitter() > 0) + { + player.sendPacket(new OnEventTrigger(this, !_open)); + } + } + } + + public void setTargetable(boolean value) + { + _isTargetable = value; + broadcastStatusUpdate(); + } + + @Override + public boolean isTargetable() + { + return _isTargetable; + } + + public boolean checkCollision() + { + return getTemplate().isCheckCollision(); + } + + /** + * All doors are stored at DoorTable except instance doors + * @param doorId + * @return + */ + private Door getSiblingDoor(int doorId) + { + if (!isInInstance()) + { + return DoorData.getInstance().getDoor(doorId); + } + + final Instance inst = InstanceManager.getInstance().getInstance(getInstanceId()); + if (inst != null) + { + return inst.getDoor(doorId); + } + + return null; // 2 late + } + + private void startAutoCloseTask() + { + final DoorTemplate template = getTemplate(); + if ((template.getCloseTime() < 0) || isOpenableByTime()) + { + return; + } + + // Clanhall doors should not auto close. + if (template.getClanHallId() > 0) + { + return; + } + + final Future oldTask = _autoCloseTask; + if (oldTask != null) + { + _autoCloseTask = null; + oldTask.cancel(false); + } + + _autoCloseTask = ThreadPool.schedule(new AutoClose(), getTemplate().getCloseTime() * 1000); + } + + class AutoClose implements Runnable + { + @Override + public void run() + { + if (_open) + { + closeMe(); + } + } + } + + class TimerOpen implements Runnable + { + @Override + public void run() + { + if (_open) + { + closeMe(); + } + else + { + openMe(); + } + + int delay = _open ? getTemplate().getCloseTime() : getTemplate().getOpenTime(); + if (getTemplate().getRandomTime() > 0) + { + delay += Rnd.get(getTemplate().getRandomTime()); + } + + ThreadPool.schedule(this, delay * 1000); + } + } + + @Override + public boolean isDoor() + { + return true; + } + + @Override + public Door asDoor() + { + return this; + } + + @Override + public String toString() + { + final StringBuilder sb = new StringBuilder(); + sb.append(getClass().getSimpleName()); + sb.append('['); + sb.append(getTemplate().getId()); + sb.append("]("); + sb.append(getObjectId()); + sb.append(')'); + return sb.toString(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/Doorman.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/Doorman.java new file mode 100644 index 0000000..aaf945c --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/Doorman.java @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.instance; + +import java.util.StringTokenizer; + +import org.l2jmobius.gameserver.data.xml.DoorData; +import org.l2jmobius.gameserver.data.xml.TeleporterData; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.enums.creature.InstanceType; +import org.l2jmobius.gameserver.entity.actor.enums.player.TeleportType; +import org.l2jmobius.gameserver.entity.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.entity.teleporter.TeleportHolder; +import org.l2jmobius.gameserver.network.serverpackets.ActionFailed; +import org.l2jmobius.gameserver.network.serverpackets.NpcHtmlMessage; + +public class Doorman extends Folk +{ + /** + * Creates a doorman. + * @param template the doorman NPC template + */ + public Doorman(NpcTemplate template) + { + super(template); + setInstanceType(InstanceType.Doorman); + } + + @Override + public void onBypassFeedback(Player player, String command) + { + if (command.startsWith("Chat")) + { + showChatWindow(player); + return; + } + else if (command.startsWith("open_doors")) + { + if (isOwnerClan(player)) + { + if (isUnderSiege()) + { + cannotManageDoors(player); + } + else + { + openDoors(player, command); + } + } + return; + } + else if (command.startsWith("close_doors")) + { + if (isOwnerClan(player)) + { + if (isUnderSiege()) + { + cannotManageDoors(player); + } + else + { + closeDoors(player, command); + } + } + return; + } + else if (command.startsWith("tele")) + { + if (isOwnerClan(player)) + { + final TeleportHolder holder = TeleporterData.getInstance().getHolder(getId(), TeleportType.OTHER.name()); + if (holder != null) + { + final int locId = Integer.parseInt(command.substring(5).trim()); + holder.doTeleport(player, this, locId); + } + } + return; + } + + super.onBypassFeedback(player, command); + } + + @Override + public void showChatWindow(Player player) + { + player.sendPacket(ActionFailed.STATIC_PACKET); + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + if (!isOwnerClan(player)) + { + html.setFile(player, "data/html/doorman/" + getTemplate().getId() + "-no.htm"); + } + else + { + html.setFile(player, "data/html/doorman/" + getTemplate().getId() + ".htm"); + } + + html.replace("%objectId%", String.valueOf(getObjectId())); + player.sendPacket(html); + } + + protected void openDoors(Player player, String command) + { + final StringTokenizer st = new StringTokenizer(command.substring(10), ", "); + st.nextToken(); + + while (st.hasMoreTokens()) + { + DoorData.getInstance().getDoor(Integer.parseInt(st.nextToken())).openMe(); + } + } + + protected void closeDoors(Player player, String command) + { + final StringTokenizer st = new StringTokenizer(command.substring(11), ", "); + st.nextToken(); + + while (st.hasMoreTokens()) + { + DoorData.getInstance().getDoor(Integer.parseInt(st.nextToken())).closeMe(); + } + } + + protected void cannotManageDoors(Player player) + { + player.sendPacket(ActionFailed.STATIC_PACKET); + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, "data/html/doorman/" + getTemplate().getId() + "-busy.htm"); + player.sendPacket(html); + } + + protected boolean isOwnerClan(Player player) + { + return true; + } + + protected boolean isUnderSiege() + { + return false; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/DungeonGatekeeper.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/DungeonGatekeeper.java new file mode 100644 index 0000000..753dd0f --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/DungeonGatekeeper.java @@ -0,0 +1,204 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.instance; + +import java.util.StringTokenizer; + +import org.l2jmobius.gameserver.config.FeatureConfig; +import org.l2jmobius.gameserver.data.xml.TeleporterData; +import org.l2jmobius.gameserver.entity.actor.Npc; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.enums.creature.InstanceType; +import org.l2jmobius.gameserver.entity.actor.enums.player.TeleportType; +import org.l2jmobius.gameserver.entity.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.entity.teleporter.TeleportHolder; +import org.l2jmobius.gameserver.mechanics.sevensigns.SevenSigns; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.serverpackets.ActionFailed; +import org.l2jmobius.gameserver.network.serverpackets.NpcHtmlMessage; + +public class DungeonGatekeeper extends Npc +{ + /** + * Creates a dungeon gatekeeper. + * @param template the dungeon gatekeeper NPC template + */ + public DungeonGatekeeper(NpcTemplate template) + { + super(template); + setInstanceType(InstanceType.DungeonGatekeeper); + } + + @Override + public void onBypassFeedback(Player player, String command) + { + player.sendPacket(ActionFailed.STATIC_PACKET); + + final StringTokenizer st = new StringTokenizer(command, " "); + final String actualCommand = st.nextToken(); // Get actual command + String filename = SevenSigns.SEVEN_SIGNS_HTML_PATH; + final int sealAvariceOwner = SevenSigns.getInstance().getSealOwner(SevenSigns.SEAL_AVARICE); + final int sealGnosisOwner = SevenSigns.getInstance().getSealOwner(SevenSigns.SEAL_GNOSIS); + final int playerCabal = SevenSigns.getInstance().getPlayerCabal(player.getObjectId()); + final boolean isSealValidationPeriod = SevenSigns.getInstance().isSealValidationPeriod(); + final int compWinner = SevenSigns.getInstance().getCabalHighestScore(); + if (actualCommand.startsWith("necro")) + { + boolean canPort = true; + if (!FeatureConfig.ALT_SEVENSIGNS_OPEN_NECROPOLIS) + { + if (isSealValidationPeriod) + { + if ((compWinner == SevenSigns.CABAL_DAWN) && ((playerCabal != SevenSigns.CABAL_DAWN) || (sealAvariceOwner != SevenSigns.CABAL_DAWN))) + { + player.sendPacket(SystemMessageId.ONLY_A_LORD_OF_DAWN_MAY_USE_THIS); + canPort = false; + } + else if ((compWinner == SevenSigns.CABAL_DUSK) && ((playerCabal != SevenSigns.CABAL_DUSK) || (sealAvariceOwner != SevenSigns.CABAL_DUSK))) + { + player.sendPacket(SystemMessageId.ONLY_A_REVOLUTIONARY_OF_DUSK_MAY_USE_THIS); + canPort = false; + } + else if ((compWinner == SevenSigns.CABAL_NULL) && (playerCabal != SevenSigns.CABAL_NULL)) + { + canPort = true; + } + else if (playerCabal == SevenSigns.CABAL_NULL) + { + canPort = false; + } + } + else + { + if (playerCabal == SevenSigns.CABAL_NULL) + { + canPort = false; + } + } + } + + if (!canPort) + { + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + filename += "necro_no.htm"; + html.setFile(player, filename); + player.sendPacket(html); + } + else + { + doTeleport(player, Integer.parseInt(st.nextToken())); + player.setIn7sDungeon(true); + } + } + else if (actualCommand.startsWith("cata")) + { + boolean canPort = true; + if (!FeatureConfig.ALT_SEVENSIGNS_OPEN_CATACUMBS) + { + if (isSealValidationPeriod) + { + if ((compWinner == SevenSigns.CABAL_DAWN) && ((playerCabal != SevenSigns.CABAL_DAWN) || (sealGnosisOwner != SevenSigns.CABAL_DAWN))) + { + player.sendPacket(SystemMessageId.ONLY_A_LORD_OF_DAWN_MAY_USE_THIS); + canPort = false; + } + else if ((compWinner == SevenSigns.CABAL_DUSK) && ((playerCabal != SevenSigns.CABAL_DUSK) || (sealGnosisOwner != SevenSigns.CABAL_DUSK))) + { + player.sendPacket(SystemMessageId.ONLY_A_REVOLUTIONARY_OF_DUSK_MAY_USE_THIS); + canPort = false; + } + else if ((compWinner == SevenSigns.CABAL_NULL) && (playerCabal != SevenSigns.CABAL_NULL)) + { + canPort = true; + } + else if (playerCabal == SevenSigns.CABAL_NULL) + { + canPort = false; + } + } + else + { + if (playerCabal == SevenSigns.CABAL_NULL) + { + canPort = false; + } + } + } + + if (!canPort) + { + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + filename += "cata_no.htm"; + html.setFile(player, filename); + player.sendPacket(html); + } + else + { + doTeleport(player, Integer.parseInt(st.nextToken())); + player.setIn7sDungeon(true); + } + } + else if (actualCommand.startsWith("exit")) + { + doTeleport(player, Integer.parseInt(st.nextToken())); + player.setIn7sDungeon(false); + } + else if (actualCommand.startsWith("goto")) + { + doTeleport(player, Integer.parseInt(st.nextToken())); + } + else + { + super.onBypassFeedback(player, command); + } + } + + private void doTeleport(Player player, int value) + { + final TeleportHolder holder = TeleporterData.getInstance().getHolder(getId(), TeleportType.OTHER.name()); + if (holder != null) + { + holder.doTeleport(player, this, value); + } + else + { + LOGGER.warning("No teleport destination with id:" + value); + } + + player.sendPacket(ActionFailed.STATIC_PACKET); + } + + @Override + public String getHtmlPath(int npcId, int value) + { + String pom = ""; + if (value == 0) + { + pom = Integer.toString(npcId); + } + else + { + pom = npcId + "-" + value; + } + + return "data/html/teleporter/" + pom + ".htm"; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/DuskPriest.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/DuskPriest.java new file mode 100644 index 0000000..b4ffa67 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/DuskPriest.java @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.instance; + +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.enums.creature.InstanceType; +import org.l2jmobius.gameserver.entity.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.mechanics.sevensigns.SevenSigns; +import org.l2jmobius.gameserver.network.serverpackets.ActionFailed; +import org.l2jmobius.gameserver.network.serverpackets.NpcHtmlMessage; + +public class DuskPriest extends SignsPriest +{ + /** + * Creates a dusk priest. + * @param template the dusk priest NPC template + */ + public DuskPriest(NpcTemplate template) + { + super(template); + setInstanceType(InstanceType.DuskPriest); + } + + @Override + public void onBypassFeedback(Player player, String command) + { + if (command.startsWith("Chat")) + { + showChatWindow(player); + } + else + { + super.onBypassFeedback(player, command); + } + } + + @Override + public void showChatWindow(Player player) + { + player.sendPacket(ActionFailed.STATIC_PACKET); + + String filename = SevenSigns.SEVEN_SIGNS_HTML_PATH; + final int sealGnosisOwner = SevenSigns.getInstance().getSealOwner(SevenSigns.SEAL_GNOSIS); + final int playerCabal = SevenSigns.getInstance().getPlayerCabal(player.getObjectId()); + final boolean isSealValidationPeriod = SevenSigns.getInstance().isSealValidationPeriod(); + final boolean isCompResultsPeriod = SevenSigns.getInstance().isCompResultsPeriod(); + final int recruitPeriod = SevenSigns.getInstance().getCurrentPeriod(); + final int compWinner = SevenSigns.getInstance().getCabalHighestScore(); + + switch (playerCabal) + { + case SevenSigns.CABAL_DUSK: + { + if (isCompResultsPeriod) + { + filename += "dusk_priest_5.htm"; + } + else if (recruitPeriod == 0) + { + filename += "dusk_priest_6.htm"; + } + else if (isSealValidationPeriod) + { + if (compWinner == SevenSigns.CABAL_DUSK) + { + if (compWinner != sealGnosisOwner) + { + filename += "dusk_priest_2c.htm"; + } + else + { + filename += "dusk_priest_2a.htm"; + } + } + else if (compWinner == SevenSigns.CABAL_NULL) + { + filename += "dusk_priest_2d.htm"; + } + else + { + filename += "dusk_priest_2b.htm"; + } + } + else + { + filename += "dusk_priest_1b.htm"; + } + break; + } + case SevenSigns.CABAL_DAWN: + { + if (isSealValidationPeriod) + { + filename += "dusk_priest_3a.htm"; + } + else + { + filename += "dusk_priest_3b.htm"; + } + break; + } + default: + { + if (isCompResultsPeriod) + { + filename += "dusk_priest_5.htm"; + } + else if (recruitPeriod == 0) + { + filename += "dusk_priest_6.htm"; + } + else if (isSealValidationPeriod) + { + if (compWinner == SevenSigns.CABAL_DUSK) + { + filename += "dusk_priest_4.htm"; + } + else if (compWinner == SevenSigns.CABAL_NULL) + { + filename += "dusk_priest_2d.htm"; + } + else + { + filename += "dusk_priest_2b.htm"; + } + } + else + { + filename += "dusk_priest_1a.htm"; + } + break; + } + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, filename); + html.replace("%objectId%", String.valueOf(getObjectId())); + player.sendPacket(html); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/EffectPoint.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/EffectPoint.java new file mode 100644 index 0000000..bda06ef --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/EffectPoint.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.instance; + +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.Npc; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.enums.creature.InstanceType; +import org.l2jmobius.gameserver.entity.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.network.serverpackets.ActionFailed; + +public class EffectPoint extends Npc +{ + private final Player _owner; + + /** + * Creates an effect point. + * @param template the effect point NPC template + * @param owner the owner + */ + public EffectPoint(NpcTemplate template, Creature owner) + { + super(template); + setInstanceType(InstanceType.EffectPoint); + setInvul(false); + _owner = owner == null ? null : owner.asPlayer(); + if (owner != null) + { + setInstanceId(owner.getInstanceId()); + } + } + + @Override + public Player asPlayer() + { + return _owner; + } + + /** + * this is called when a player interacts with this NPC + * @param player + */ + @Override + public void onAction(Player player, boolean interact) + { + player.sendPacket(ActionFailed.STATIC_PACKET); + } + + @Override + public void onActionShift(Player player) + { + if (player == null) + { + return; + } + + player.sendPacket(ActionFailed.STATIC_PACKET); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/EventMonster.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/EventMonster.java new file mode 100644 index 0000000..fba475f --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/EventMonster.java @@ -0,0 +1,66 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.actor.instance; + +import org.l2jmobius.gameserver.entity.actor.enums.creature.InstanceType; +import org.l2jmobius.gameserver.entity.actor.templates.NpcTemplate; + +/** + * @author Gnacik + */ +public class EventMonster extends Monster +{ + // Block offensive skills usage on event mobs. + // mainly for AoE skills, disallow kill many event mobs + // with one skill + public boolean block_skill_attack = false; + + // Event mobs should drop items to ground. + // But item pickup must be protected to killer. + // TODO: Some mobs need protect drop for spawner + public boolean drop_on_ground = false; + + /** + * Creates an event monster. + * @param template the event monster NPC template + */ + public EventMonster(NpcTemplate template) + { + super(template); + setInstanceType(InstanceType.EventMob); + } + + public void eventSetBlockOffensiveSkills(boolean value) + { + block_skill_attack = value; + } + + public void eventSetDropOnGround(boolean value) + { + drop_on_ground = value; + } + + public boolean eventDropOnGround() + { + return drop_on_ground; + } + + public boolean eventSkillAttackBlocked() + { + return block_skill_attack; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/FeedableBeast.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/FeedableBeast.java new file mode 100644 index 0000000..30922ad --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/FeedableBeast.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.instance; + +import org.l2jmobius.gameserver.entity.actor.enums.creature.InstanceType; +import org.l2jmobius.gameserver.entity.actor.templates.NpcTemplate; + +// This class is here mostly for convenience and for avoidance of hardcoded IDs. +// It refers to Beast (mobs) that can be attacked but can also be fed. +// For example, the Beast Farm's Alpen Buffalo. +// This class is only truly used by the handlers in order to check the correctness +// of the target. However, no additional tasks are needed, since they are all +// handled by scripted AI. +public class FeedableBeast extends Monster +{ + public FeedableBeast(NpcTemplate template) + { + super(template); + setInstanceType(InstanceType.FeedableBeast); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/Fence.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/Fence.java new file mode 100644 index 0000000..7b412e7 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/Fence.java @@ -0,0 +1,176 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.actor.instance; + +import org.l2jmobius.gameserver.data.xml.FenceData; +import org.l2jmobius.gameserver.entity.World; +import org.l2jmobius.gameserver.entity.WorldObject; +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.enums.creature.FenceState; +import org.l2jmobius.gameserver.managers.IdManager; +import org.l2jmobius.gameserver.network.serverpackets.DeleteObject; +import org.l2jmobius.gameserver.network.serverpackets.ExColosseumFenceInfo; + +/** + * @author HorridoJoho + */ +public class Fence extends WorldObject +{ + private final int _xMin; + private final int _xMax; + private final int _yMin; + private final int _yMax; + + private final String _name; + private final int _width; + private final int _length; + + private FenceState _state; + private int[] _heightFences; + + public Fence(int x, int y, String name, int width, int length, int height, FenceState state) + { + super(IdManager.getInstance().getNextId()); + + _xMin = x - (width / 2); + _xMax = x + (width / 2); + _yMin = y - (length / 2); + _yMax = y + (length / 2); + _name = name; + _width = width; + _length = length; + _state = state; + if (height > 1) + { + _heightFences = new int[height - 1]; + for (int i = 0; i < _heightFences.length; i++) + { + _heightFences[i] = IdManager.getInstance().getNextId(); + } + } + } + + @Override + public int getId() + { + return getObjectId(); + } + + @Override + public String getName() + { + return _name; + } + + @Override + public boolean isAutoAttackable(Creature attacker) + { + return false; + } + + @Override + public void sendInfo(Player player) + { + player.sendPacket(new ExColosseumFenceInfo(this)); + if (_heightFences != null) + { + for (int objId : _heightFences) + { + player.sendPacket(new ExColosseumFenceInfo(objId, getX(), getY(), getZ(), _width, _length, _state)); + } + } + } + + @Override + public boolean decayMe() + { + if (_heightFences != null) + { + final DeleteObject[] deleteObjects = new DeleteObject[_heightFences.length]; + for (int i = 0; i < _heightFences.length; i++) + { + deleteObjects[i] = new DeleteObject(_heightFences[i]); + } + + World.forEachVisibleObject(this, Player.class, player -> + { + for (int i = 0; i < _heightFences.length; i++) + { + player.sendPacket(deleteObjects[i]); + } + }); + } + + return super.decayMe(); + } + + public boolean deleteMe() + { + decayMe(); + + FenceData.getInstance().removeFence(this); + return false; + } + + public FenceState getState() + { + return _state; + } + + public void setState(FenceState type) + { + _state = type; + broadcastInfo(); + } + + public int getWidth() + { + return _width; + } + + public int getLength() + { + return _length; + } + + public int getXMin() + { + return _xMin; + } + + public int getYMin() + { + return _yMin; + } + + public int getXMax() + { + return _xMax; + } + + public int getYMax() + { + return _yMax; + } + + @Override + public boolean isFence() + { + return true; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/FestivalGuide.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/FestivalGuide.java new file mode 100644 index 0000000..905c89d --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/FestivalGuide.java @@ -0,0 +1,278 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.actor.instance; + +import org.l2jmobius.gameserver.config.FeatureConfig; +import org.l2jmobius.gameserver.entity.actor.Npc; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.enums.creature.InstanceType; +import org.l2jmobius.gameserver.entity.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.mechanics.sevensigns.SevenSigns; +import org.l2jmobius.gameserver.mechanics.sevensigns.SevenSignsFestival; +import org.l2jmobius.gameserver.network.serverpackets.ActionFailed; +import org.l2jmobius.gameserver.network.serverpackets.NpcHtmlMessage; + +/** + * Festival of Darkness Guide (Seven Signs) + * @author Tempy + */ +public class FestivalGuide extends Npc +{ + private final int _festivalType; + private final int _festivalOracle; + private final int _blueStonesNeeded; + private final int _greenStonesNeeded; + private final int _redStonesNeeded; + + /** + * Creates a festival guide. + * @param template the festival guide NPC template + */ + public FestivalGuide(NpcTemplate template) + { + super(template); + setInstanceType(InstanceType.FestivalGuide); + + switch (getId()) + { + case 31127: + case 31132: + { + _festivalType = SevenSignsFestival.FESTIVAL_LEVEL_MAX_31; + _festivalOracle = SevenSigns.CABAL_DAWN; + _blueStonesNeeded = 900; + _greenStonesNeeded = 540; + _redStonesNeeded = 270; + break; + } + case 31128: + case 31133: + { + _festivalType = SevenSignsFestival.FESTIVAL_LEVEL_MAX_42; + _festivalOracle = SevenSigns.CABAL_DAWN; + _blueStonesNeeded = 1500; + _greenStonesNeeded = 900; + _redStonesNeeded = 450; + break; + } + case 31129: + case 31134: + { + _festivalType = SevenSignsFestival.FESTIVAL_LEVEL_MAX_53; + _festivalOracle = SevenSigns.CABAL_DAWN; + _blueStonesNeeded = 3000; + _greenStonesNeeded = 1800; + _redStonesNeeded = 900; + break; + } + case 31130: + case 31135: + { + _festivalType = SevenSignsFestival.FESTIVAL_LEVEL_MAX_64; + _festivalOracle = SevenSigns.CABAL_DAWN; + _blueStonesNeeded = 4500; + _greenStonesNeeded = 2700; + _redStonesNeeded = 1350; + break; + } + case 31131: + case 31136: + { + _festivalType = SevenSignsFestival.FESTIVAL_LEVEL_MAX_NONE; + _festivalOracle = SevenSigns.CABAL_DAWN; + _blueStonesNeeded = 6000; + _greenStonesNeeded = 3600; + _redStonesNeeded = 1800; + break; + } + case 31137: + case 31142: + { + _festivalType = SevenSignsFestival.FESTIVAL_LEVEL_MAX_31; + _festivalOracle = SevenSigns.CABAL_DUSK; + _blueStonesNeeded = 900; + _greenStonesNeeded = 540; + _redStonesNeeded = 270; + break; + } + case 31138: + case 31143: + { + _festivalType = SevenSignsFestival.FESTIVAL_LEVEL_MAX_42; + _festivalOracle = SevenSigns.CABAL_DUSK; + _blueStonesNeeded = 1500; + _greenStonesNeeded = 900; + _redStonesNeeded = 450; + break; + } + case 31139: + case 31144: + { + _festivalType = SevenSignsFestival.FESTIVAL_LEVEL_MAX_53; + _festivalOracle = SevenSigns.CABAL_DUSK; + _blueStonesNeeded = 3000; + _greenStonesNeeded = 1800; + _redStonesNeeded = 900; + break; + } + case 31140: + case 31145: + { + _festivalType = SevenSignsFestival.FESTIVAL_LEVEL_MAX_64; + _festivalOracle = SevenSigns.CABAL_DUSK; + _blueStonesNeeded = 4500; + _greenStonesNeeded = 2700; + _redStonesNeeded = 1350; + break; + } + case 31141: + case 31146: + { + _festivalType = SevenSignsFestival.FESTIVAL_LEVEL_MAX_NONE; + _festivalOracle = SevenSigns.CABAL_DUSK; + _blueStonesNeeded = 6000; + _greenStonesNeeded = 3600; + _redStonesNeeded = 1800; + break; + } + default: + { + _festivalType = SevenSignsFestival.FESTIVAL_LEVEL_MAX_NONE; + _festivalOracle = SevenSigns.CABAL_NULL; + _blueStonesNeeded = 0; + _greenStonesNeeded = 0; + _redStonesNeeded = 0; + } + } + } + + public int getFestivalType() + { + return _festivalType; + } + + public int getFestivalOracle() + { + return _festivalOracle; + } + + public int getStoneCount(int stoneType) + { + switch (stoneType) + { + case SevenSigns.SEAL_STONE_BLUE_ID: + { + return _blueStonesNeeded; + } + case SevenSigns.SEAL_STONE_GREEN_ID: + { + return _greenStonesNeeded; + } + case SevenSigns.SEAL_STONE_RED_ID: + { + return _redStonesNeeded; + } + default: + { + return -1; + } + } + } + + public void showChatWindow(Player player, int value, String suffix, boolean isDescription) + { + String filename = SevenSigns.SEVEN_SIGNS_HTML_PATH + "festival/"; + filename += (isDescription) ? "desc_" : "festival_"; + filename += (suffix != null) ? value + suffix + ".htm" : value + ".htm"; + + // Send a Server->Client NpcHtmlMessage containing the text of the Npc to the Player + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, filename); + html.replace("%objectId%", String.valueOf(getObjectId())); + html.replace("%festivalType%", SevenSignsFestival.getFestivalName(_festivalType)); + html.replace("%cycleMins%", String.valueOf(SevenSignsFestival.getInstance().getMinsToNextCycle())); + if (!isDescription && "2b".equals(value + suffix)) + { + html.replace("%minFestivalPartyMembers%", String.valueOf(FeatureConfig.ALT_FESTIVAL_MIN_PLAYER)); + } + + // If the stats or bonus table is required, construct them. + if (value == 5) + { + html.replace("%statsTable%", getStatsTable()); + } + + if (value == 6) + { + html.replace("%bonusTable%", getBonusTable()); + } + + // festival's fee + if (value == 1) + { + html.replace("%blueStoneNeeded%", String.valueOf(_blueStonesNeeded)); + html.replace("%greenStoneNeeded%", String.valueOf(_greenStonesNeeded)); + html.replace("%redStoneNeeded%", String.valueOf(_redStonesNeeded)); + } + + player.sendPacket(html); + + // Send a Server->Client ActionFailed to the Player in order to avoid that the client wait another packet + player.sendPacket(ActionFailed.STATIC_PACKET); + } + + private static String getStatsTable() + { + final StringBuilder tableHtml = new StringBuilder(1000); + + // Get the scores for each of the festival level ranges (types). + for (int i = 0; i < 5; i++) + { + final int dawnScore = SevenSignsFestival.getInstance().getHighestScore(SevenSigns.CABAL_DAWN, i); + final int duskScore = SevenSignsFestival.getInstance().getHighestScore(SevenSigns.CABAL_DUSK, i); + final String festivalName = SevenSignsFestival.getFestivalName(i); + String winningCabal = "Children of Dusk"; + if (dawnScore > duskScore) + { + winningCabal = "Children of Dawn"; + } + else if (dawnScore == duskScore) + { + winningCabal = "None"; + } + + tableHtml.append("" + festivalName + "" + duskScore + "" + dawnScore + "" + winningCabal + ""); + } + + return tableHtml.toString(); + } + + private static String getBonusTable() + { + final StringBuilder tableHtml = new StringBuilder(500); + + // Get the accumulated scores for each of the festival level ranges (types). + for (int i = 0; i < 5; i++) + { + final int accumScore = SevenSignsFestival.getInstance().getAccumulatedBonus(i); + final String festivalName = SevenSignsFestival.getFestivalName(i); + tableHtml.append("" + festivalName + "" + accumScore + ""); + } + + return tableHtml.toString(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/FestivalMonster.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/FestivalMonster.java new file mode 100644 index 0000000..014c74f --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/FestivalMonster.java @@ -0,0 +1,119 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.actor.instance; + +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.enums.creature.InstanceType; +import org.l2jmobius.gameserver.entity.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.entity.groups.Party; +import org.l2jmobius.gameserver.entity.item.enums.ItemProcessType; +import org.l2jmobius.gameserver.entity.item.instance.Item; +import org.l2jmobius.gameserver.mechanics.sevensigns.SevenSignsFestival; +import org.l2jmobius.gameserver.network.serverpackets.InventoryUpdate; + +/** + * This class manages all attackable festival NPCs, spawned during the Festival of Darkness. + * @author Tempy + */ +public class FestivalMonster extends Monster +{ + protected int _bonusMultiplier = 1; + + /** + * Creates a festival monster. + * @param template the festival monster NPC template + */ + public FestivalMonster(NpcTemplate template) + { + super(template); + setInstanceType(InstanceType.FestivalMonster); + } + + public void setOfferingBonus(int bonusMultiplier) + { + _bonusMultiplier = bonusMultiplier; + } + + /** + * Return True if the attacker is not another FestivalMonster. + */ + @Override + public boolean isAutoAttackable(Creature attacker) + { + return !(attacker instanceof FestivalMonster); + } + + /** + * All mobs in the festival are aggressive, and have high aggro range. + */ + @Override + public boolean isAggressive() + { + return true; + } + + /** + * All mobs in the festival really don't need random animation. + */ + @Override + public boolean hasRandomAnimation() + { + return false; + } + + /** + * Actions: + *
      + *
    • Check if the killing object is a player, and then find the party they belong to.
    • + *
    • Add a blood offering item to the leader of the party.
    • + *
    • Update the party leader's inventory to show the new item addition.
    • + *
    + */ + @Override + public void doItemDrop(Creature lastAttacker) + { + Player killingChar = null; + if (!lastAttacker.isPlayer()) + { + return; + } + + killingChar = lastAttacker.asPlayer(); + final Party associatedParty = killingChar.getParty(); + if (associatedParty == null) + { + return; + } + + final Player partyLeader = associatedParty.getLeader(); + final Item addedOfferings = partyLeader.getInventory().addItem(ItemProcessType.REWARD, SevenSignsFestival.FESTIVAL_OFFERING_ID, _bonusMultiplier, partyLeader, this); + final InventoryUpdate iu = new InventoryUpdate(); + if (addedOfferings.getCount() != _bonusMultiplier) + { + iu.addModifiedItem(addedOfferings); + } + else + { + iu.addNewItem(addedOfferings); + } + + partyLeader.sendInventoryUpdate(iu); + + super.doItemDrop(lastAttacker); // Normal drop + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/Fisherman.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/Fisherman.java new file mode 100644 index 0000000..080a139 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/Fisherman.java @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.instance; + +import java.util.List; + +import org.l2jmobius.gameserver.config.GeneralConfig; +import org.l2jmobius.gameserver.data.xml.SkillData; +import org.l2jmobius.gameserver.data.xml.SkillTreeData; +import org.l2jmobius.gameserver.entity.actor.Npc; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.enums.creature.InstanceType; +import org.l2jmobius.gameserver.entity.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.managers.FishingChampionshipManager; +import org.l2jmobius.gameserver.mechanics.skill.Skill; +import org.l2jmobius.gameserver.mechanics.skill.enums.AcquireSkillType; +import org.l2jmobius.gameserver.mechanics.skill.holders.SkillLearn; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.serverpackets.AcquireSkillList; +import org.l2jmobius.gameserver.network.serverpackets.NpcHtmlMessage; +import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; + +public class Fisherman extends Merchant +{ + /** + * Creates a fisherman. + * @param template the fisherman NPC template + */ + public Fisherman(NpcTemplate template) + { + super(template); + setInstanceType(InstanceType.Fisherman); + } + + @Override + public String getHtmlPath(int npcId, int value) + { + String pom = ""; + if (value == 0) + { + pom = Integer.toString(npcId); + } + else + { + pom = npcId + "-" + value; + } + + return "data/html/fisherman/" + pom + ".htm"; + } + + @Override + public void onBypassFeedback(Player player, String command) + { + if (command.equalsIgnoreCase("FishSkillList")) + { + showFishSkillList(player); + } + else if (command.startsWith("FishingChampionship")) + { + if (GeneralConfig.ALT_FISH_CHAMPIONSHIP_ENABLED) + { + FishingChampionshipManager.getInstance().showChampScreen(player, this); + } + else + { + sendHtml(player, this, "no_fish_event001.htm"); + } + } + else if (command.startsWith("FishingReward")) + { + if (GeneralConfig.ALT_FISH_CHAMPIONSHIP_ENABLED) + { + if (FishingChampionshipManager.getInstance().isWinner(player.getName())) + { + FishingChampionshipManager.getInstance().getReward(player); + } + else + { + sendHtml(player, this, "no_fish_event_reward001.htm"); + } + } + else + { + sendHtml(player, this, "no_fish_event001.htm"); + } + } + else + { + super.onBypassFeedback(player, command); + } + } + + public static void showFishSkillList(Player player) + { + final List skills = SkillTreeData.getInstance().getAvailableFishingSkills(player); + final AcquireSkillList asl = new AcquireSkillList(AcquireSkillType.FISHING); + int count = 0; + for (SkillLearn s : skills) + { + final Skill sk = SkillData.getInstance().getSkill(s.getSkillId(), s.getSkillLevel()); + if (sk == null) + { + continue; + } + + count++; + asl.addSkill(s.getSkillId(), s.getSkillLevel(), s.getSkillLevel(), s.getLevelUpSp(), 1); + } + + if (count == 0) + { + final int minlLevel = SkillTreeData.getInstance().getMinLevelForNewSkill(player, SkillTreeData.getInstance().getFishingSkillTree()); + if (minlLevel > 0) + { + final SystemMessage sm = new SystemMessage(SystemMessageId.YOU_DO_NOT_HAVE_ANY_FURTHER_SKILLS_TO_LEARN_COME_BACK_WHEN_YOU_HAVE_REACHED_LEVEL_S1); + sm.addInt(minlLevel); + player.sendPacket(sm); + } + else + { + player.sendPacket(SystemMessageId.THERE_ARE_NO_OTHER_SKILLS_TO_LEARN); + } + } + else + { + player.sendPacket(asl); + } + } + + private void sendHtml(Player player, Npc npc, String htmlName) + { + final NpcHtmlMessage html = new NpcHtmlMessage(npc.getObjectId()); + html.setFile(player, "data/html/fisherman/championship/" + htmlName); + player.sendPacket(html); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/FlameTower.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/FlameTower.java new file mode 100644 index 0000000..ff7021a --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/FlameTower.java @@ -0,0 +1,87 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.actor.instance; + +import java.util.List; + +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.Tower; +import org.l2jmobius.gameserver.entity.actor.enums.creature.InstanceType; +import org.l2jmobius.gameserver.entity.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.entity.zone.ZoneType; +import org.l2jmobius.gameserver.managers.ZoneManager; + +/** + * Class for Flame Control Tower instance. + * @author JIV + */ +public class FlameTower extends Tower +{ + private int _upgradeLevel = 0; + private List _zoneList; + + /** + * Creates a flame tower. + * @param template the flame tower NPC template + */ + public FlameTower(NpcTemplate template) + { + super(template); + setInstanceType(InstanceType.FlameTower); + } + + @Override + public boolean doDie(Creature killer) + { + enableZones(false); + return super.doDie(killer); + } + + @Override + public boolean deleteMe() + { + enableZones(false); + return super.deleteMe(); + } + + public void enableZones(boolean value) + { + if ((_zoneList != null) && (_upgradeLevel != 0)) + { + final int maxIndex = _upgradeLevel * 2; + for (int i = 0; i < maxIndex; i++) + { + final ZoneType zone = ZoneManager.getInstance().getZoneById(_zoneList.get(i)); + if (zone != null) + { + zone.setEnabled(value); + } + } + } + } + + public void setUpgradeLevel(int level) + { + _upgradeLevel = level; + } + + public void setZoneList(List list) + { + _zoneList = list; + enableZones(true); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/FlyTerrainObject.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/FlyTerrainObject.java new file mode 100644 index 0000000..6f19a9d --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/FlyTerrainObject.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.instance; + +import org.l2jmobius.gameserver.entity.actor.Npc; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.enums.creature.InstanceType; +import org.l2jmobius.gameserver.entity.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.network.serverpackets.ActionFailed; + +public class FlyTerrainObject extends Npc +{ + /** + * Creates a fly terrain object. + * @param template the fly terrain object + */ + public FlyTerrainObject(NpcTemplate template) + { + super(template); + setInstanceType(InstanceType.FlyTerrainObject); + setFlying(true); + } + + @Override + public void onAction(Player player, boolean interact) + { + player.sendPacket(ActionFailed.STATIC_PACKET); + } + + @Override + public void onActionShift(Player player) + { + if (player.isGM()) + { + super.onActionShift(player); + } + else + { + player.sendPacket(ActionFailed.STATIC_PACKET); + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/Folk.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/Folk.java new file mode 100644 index 0000000..5615d8c --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/Folk.java @@ -0,0 +1,202 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.instance; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import org.l2jmobius.gameserver.data.xml.EnchantSkillTreeData; +import org.l2jmobius.gameserver.data.xml.SkillData; +import org.l2jmobius.gameserver.data.xml.SkillTreeData; +import org.l2jmobius.gameserver.entity.actor.Npc; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.enums.creature.InstanceType; +import org.l2jmobius.gameserver.entity.actor.enums.player.PlayerClass; +import org.l2jmobius.gameserver.entity.actor.status.FolkStatus; +import org.l2jmobius.gameserver.entity.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.mechanics.skill.Skill; +import org.l2jmobius.gameserver.mechanics.skill.enums.AcquireSkillType; +import org.l2jmobius.gameserver.mechanics.skill.holders.EnchantSkillLearn; +import org.l2jmobius.gameserver.mechanics.skill.holders.SkillLearn; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.serverpackets.AcquireSkillDone; +import org.l2jmobius.gameserver.network.serverpackets.AcquireSkillList; +import org.l2jmobius.gameserver.network.serverpackets.ActionFailed; +import org.l2jmobius.gameserver.network.serverpackets.ExEnchantSkillList; +import org.l2jmobius.gameserver.network.serverpackets.NpcHtmlMessage; +import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; + +public class Folk extends Npc +{ + /** + * Creates a NPC. + * @param template the NPC template + */ + public Folk(NpcTemplate template) + { + super(template); + setInstanceType(InstanceType.Folk); + setInvul(false); + } + + @Override + public FolkStatus getStatus() + { + return (FolkStatus) super.getStatus(); + } + + @Override + public void initCharStatus() + { + setStatus(new FolkStatus(this)); + } + + public List getClassesToTeach() + { + return getTemplate().getTeachInfo(); + } + + /** + * Displays Skill Tree for a given player, npc and class Id. + * @param player the active character. + * @param npc the last folk. + * @param playerClass the player's active class identifier as a {@link PlayerClass} enum value + */ + public static void showSkillList(Player player, Npc npc, PlayerClass playerClass) + { + if (!npc.getTemplate().canTeach(playerClass)) + { + npc.showNoTeachHtml(player); + return; + } + + if (((Folk) npc).getClassesToTeach().isEmpty()) + { + final NpcHtmlMessage html = new NpcHtmlMessage(npc.getObjectId()); + final String sb = "I cannot teach you. My class list is empty.
    Ask admin to fix it. Need add my npcid and classes to skill_learn.sql.
    NpcId:" + npc.getTemplate().getId() + ", Your playerClass:" + player.getPlayerClass().getId() + ""; + html.setHtml(sb); + player.sendPacket(html); + return; + } + + // Normal skills, No LearnedByFS, no AutoGet skills. + final Collection skills = SkillTreeData.getInstance().getAvailableSkills(player, playerClass, false, false); + final AcquireSkillList asl = new AcquireSkillList(AcquireSkillType.CLASS); + int count = 0; + player.setLearningClass(playerClass); + for (SkillLearn s : skills) + { + if (SkillData.getInstance().getSkill(s.getSkillId(), s.getSkillLevel()) != null) + { + asl.addSkill(s.getSkillId(), s.getSkillLevel(), s.getSkillLevel(), s.getCalculatedLevelUpSp(player.getPlayerClass(), playerClass), 0); + count++; + } + } + + if (count == 0) + { + final Map skillTree = SkillTreeData.getInstance().getCompleteClassSkillTree(playerClass); + final int minLevel = SkillTreeData.getInstance().getMinLevelForNewSkill(player, skillTree); + if (minLevel > 0) + { + final SystemMessage sm = new SystemMessage(SystemMessageId.YOU_DO_NOT_HAVE_ANY_FURTHER_SKILLS_TO_LEARN_COME_BACK_WHEN_YOU_HAVE_REACHED_LEVEL_S1); + sm.addInt(minLevel); + player.sendPacket(sm); + } + else + { + if (player.getPlayerClass().level() == 1) + { + player.sendMessage("There are no other skills to learn. Please come back after 2nd class change."); + } + else + { + player.sendPacket(SystemMessageId.THERE_ARE_NO_OTHER_SKILLS_TO_LEARN); + player.sendPacket(AcquireSkillDone.STATIC_PACKET); + } + } + } + else + { + player.sendPacket(asl); + } + } + + /** + * This method displays EnchantSkillList to the player. + * @param player The player who requested the method. + */ + public void showEnchantSkillList(Player player) + { + if (!getTemplate().canTeach(player.getPlayerClass())) + { + showNoTeachHtml(player); + return; + } + + if (player.getPlayerClass().level() < 3) + { + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setHtml("You must have 3rd class change quest completed."); + player.sendPacket(html); + return; + } + + final ExEnchantSkillList esl = new ExEnchantSkillList(); + int count = 0; + for (EnchantSkillLearn s : EnchantSkillTreeData.getInstance().getAvailableEnchantSkills(player)) + { + final Skill sk = SkillData.getInstance().getSkill(s.getId(), s.getLevel()); + if (sk == null) + { + continue; + } + + count++; + esl.addSkill(s.getId(), s.getLevel(), s.getSpCost(), s.getExp()); + } + + if (count == 0) + { + player.sendPacket(SystemMessageId.THERE_IS_NO_SKILL_THAT_ENABLES_ENCHANT); + final int level = player.getLevel(); + if (level < 74) + { + final SystemMessage sm = new SystemMessage(SystemMessageId.YOU_DO_NOT_HAVE_ANY_FURTHER_SKILLS_TO_LEARN_COME_BACK_WHEN_YOU_HAVE_REACHED_LEVEL_S1); + sm.addInt(74); + player.sendPacket(sm); + } + else + { + player.sendPacket(SystemMessageId.THERE_ARE_NO_OTHER_SKILLS_TO_LEARN); + } + + player.sendPacket(AcquireSkillDone.STATIC_PACKET); + } + else + { + player.sendPacket(esl); + } + + player.sendPacket(ActionFailed.STATIC_PACKET); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/FriendlyMob.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/FriendlyMob.java new file mode 100644 index 0000000..26a0bd8 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/FriendlyMob.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.instance; + +import org.l2jmobius.gameserver.entity.actor.Attackable; +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.enums.creature.InstanceType; +import org.l2jmobius.gameserver.entity.actor.templates.NpcTemplate; + +/** + * This class represents Friendly Mobs lying over the world.
    + * These friendly mobs should only attack players with karma > 0 and it is always aggro, since it just attacks players with karma. + */ +public class FriendlyMob extends Attackable +{ + /** + * Creates a friendly monster. + * @param template the friendly monster NPC template + */ + public FriendlyMob(NpcTemplate template) + { + super(template); + setInstanceType(InstanceType.FriendlyMob); + } + + @Override + public boolean isAutoAttackable(Creature attacker) + { + if (attacker.isPlayer()) + { + return attacker.asPlayer().getKarma() > 0; + } + + return false; + } + + @Override + public boolean isAggressive() + { + return true; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/GrandBoss.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/GrandBoss.java new file mode 100644 index 0000000..76b674d --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/GrandBoss.java @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.instance; + +import org.l2jmobius.commons.util.Rnd; +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.enums.creature.InstanceType; +import org.l2jmobius.gameserver.entity.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.entity.groups.Party; +import org.l2jmobius.gameserver.managers.RaidBossPointsManager; +import org.l2jmobius.gameserver.mechanics.olympiad.Hero; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; + +/** + * This class manages all Grand Bosses. + * @version $Revision: 1.0.0.0 $ $Date: 2006/06/16 $ + */ +public class GrandBoss extends Monster +{ + private boolean _useRaidCurse = true; + + /** + * Creates a grand boss. + * @param template the grand boss NPC template + */ + public GrandBoss(NpcTemplate template) + { + super(template); + setInstanceType(InstanceType.GrandBoss); + setIsRaid(true); + setLethalable(false); + } + + @Override + public void onSpawn() + { + setRandomWalking(false); + super.onSpawn(); + } + + @Override + public boolean doDie(Creature killer) + { + if (!super.doDie(killer)) + { + return false; + } + + if ((killer != null) && killer.isPlayable()) + { + final SystemMessage msg = new SystemMessage(SystemMessageId.CONGRATULATIONS_YOUR_RAID_WAS_SUCCESSFUL); + final Player player = killer.asPlayer(); + final Party party = player.getParty(); + if (party != null) + { + party.broadcastPacket(msg); + for (Player member : party.getMembers()) + { + RaidBossPointsManager.getInstance().addPoints(member, getId(), (getLevel() / 2) + Rnd.get(-5, 5)); + if (member.isNoble()) + { + Hero.getInstance().setRBkilled(member.getObjectId(), getId()); + } + } + } + else + { + player.sendPacket(msg); + RaidBossPointsManager.getInstance().addPoints(player, getId(), (getLevel() / 2) + Rnd.get(-5, 5)); + if (player.isNoble()) + { + Hero.getInstance().setRBkilled(player.getObjectId(), getId()); + } + } + } + + return true; + } + + @Override + public float getVitalityPoints(int level, long damage) + { + return -super.getVitalityPoints(level, damage) / 100; + } + + @Override + public boolean useVitalityRate() + { + return false; + } + + public void setUseRaidCurse(boolean value) + { + _useRaidCurse = value; + } + + @Override + public boolean giveRaidCurse() + { + return _useRaidCurse; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/Guard.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/Guard.java new file mode 100644 index 0000000..bfcf089 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/Guard.java @@ -0,0 +1,228 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.instance; + +import org.l2jmobius.gameserver.config.custom.FactionSystemConfig; +import org.l2jmobius.gameserver.entity.World; +import org.l2jmobius.gameserver.entity.actor.Attackable; +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.enums.creature.InstanceType; +import org.l2jmobius.gameserver.entity.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.mechanics.events.EventDispatcher; +import org.l2jmobius.gameserver.mechanics.events.EventType; +import org.l2jmobius.gameserver.mechanics.events.holders.actor.npc.OnNpcFirstTalk; +import org.l2jmobius.gameserver.mechanics.skill.Skill; +import org.l2jmobius.gameserver.network.serverpackets.ActionFailed; + +/** + * This class manages all Guards in the world. It inherits all methods from Attackable and adds some more such as tracking PK and aggressive Monster. + */ +public class Guard extends Attackable +{ + /** + * Creates a guard. + * @param template the guard NPC template + */ + public Guard(NpcTemplate template) + { + super(template); + setInstanceType(InstanceType.Guard); + } + + @Override + public boolean isAutoAttackable(Creature attacker) + { + if (attacker.isMonster() && !attacker.isFakePlayer()) + { + return true; + } + + if (FactionSystemConfig.FACTION_SYSTEM_ENABLED && FactionSystemConfig.FACTION_GUARDS_ENABLED && attacker.isPlayable()) + { + final Player player = attacker.asPlayer(); + if ((player.isGood() && getTemplate().isClan(FactionSystemConfig.FACTION_EVIL_TEAM_NAME)) || (player.isEvil() && getTemplate().isClan(FactionSystemConfig.FACTION_GOOD_TEAM_NAME))) + { + return true; + } + } + + return super.isAutoAttackable(attacker); + } + + @Override + public void addDamage(Creature attacker, int damage, Skill skill) + { + super.addDamage(attacker, damage, skill); + getAI().startFollow(attacker); + addDamageHate(attacker, 0, 10); + World.forEachVisibleObjectInRange(this, Guard.class, 500, guard -> + { + guard.getAI().startFollow(attacker); + guard.addDamageHate(attacker, 0, 10); + }); + } + + /** + * Set the home location of its GuardInstance. + */ + @Override + public void onSpawn() + { + setRandomWalking(false); + super.onSpawn(); + + getAI().setIntentionActive(); + + // Check the region where this mob is, do not activate the AI if region is inactive. + // final WorldRegion region = World.getRegion(this); + // if ((region != null) && (!region.isActive())) + // { + // getAI().stopAITask(); + // } + } + + /** + * Return the pathfile of the selected HTML file in function of the GuardInstance Identifier and of the page number.
    + *
    + * Format of the pathfile: + *
      + *
    • if page number = 0 : data/html/guard/12006.htm (npcId-page number)
    • + *
    • if page number > 0 : data/html/guard/12006-1.htm (npcId-page number)
    • + *
    + * @param npcId The Identifier of the Npc whose text must be display + * @param value The number of the page to display + */ + @Override + public String getHtmlPath(int npcId, int value) + { + String pom = ""; + if (value == 0) + { + pom = Integer.toString(npcId); + } + else + { + pom = npcId + "-" + value; + } + + return "data/html/guard/" + pom + ".htm"; + } + + /** + * Manage actions when a player click on the GuardInstance.
    + *
    + * Actions on first click on the GuardInstance (Select it): + *
      + *
    • Set the GuardInstance as target of the Player player (if necessary)
    • + *
    • Send a Server->Client packet MyTargetSelected to the Player player (display the select window)
    • + *
    • Set the Player Intention to IDLE
    • + *
    • Send a Server->Client packet ValidateLocation to correct the GuardInstance position and heading on the client
    • + *
    + *
    + * Actions on second click on the GuardInstance (Attack it/Interact with it): + *
      + *
    • If Player is in the _aggroList of the GuardInstance, set the Player Intention to ATTACK
    • + *
    • If Player is NOT in the _aggroList of the GuardInstance, set the Player Intention to INTERACT (after a distance verification) and show message
    • + *
    + *
    + * Example of use: + *
      + *
    • Client packet : Action, AttackRequest
    • + *
    + * @param player The Player that start an action on the GuardInstance + */ + @Override + public void onAction(Player player, boolean interactValue) + { + if (!canTarget(player)) + { + return; + } + + boolean interact = interactValue; + if (FactionSystemConfig.FACTION_SYSTEM_ENABLED && FactionSystemConfig.FACTION_GUARDS_ENABLED && ((player.isGood() && getTemplate().isClan(FactionSystemConfig.FACTION_EVIL_TEAM_NAME)) || (player.isEvil() && getTemplate().isClan(FactionSystemConfig.FACTION_GOOD_TEAM_NAME)))) + { + interact = false; + + // TODO: Fix normal targeting + player.getAI().setIntentionAttack(this); + } + + if (isFakePlayer() && isInCombat()) + { + interact = false; + + // TODO: Fix normal targeting + player.getAI().setIntentionAttack(this); + } + + // Check if the Player already target the GuardInstance. + if (getObjectId() != player.getTargetId()) + { + // Set the target of the Player player. + player.setTarget(this); + } + else if (interact) + { + // Check if the Player is in the _aggroList of the GuardInstance. + if (isInAggroList(player)) + { + // Set the Player Intention to ATTACK. + player.getAI().setIntentionAttack(this); + } + else + { + // Calculate the distance between the Player and the Npc. + if (!canInteract(player)) + { + // Set the Player Intention to INTERACT. + player.getAI().setIntentionInteract(this); + } + else + { + player.setLastFolkNPC(this); + + // Open a chat window on client with the text of the GuardInstance. + if (hasListener(EventType.ON_NPC_QUEST_START)) + { + player.setLastQuestNpcObject(getObjectId()); + } + + if (hasListener(EventType.ON_NPC_FIRST_TALK)) + { + if (EventDispatcher.getInstance().hasListener(EventType.ON_NPC_FIRST_TALK, this)) + { + EventDispatcher.getInstance().notifyEventAsync(new OnNpcFirstTalk(this, player), this); + } + } + else + { + showChatWindow(player, 0); + } + } + } + } + + // Send a Server->Client ActionFailed to the Player in order to avoid that the client wait another packet. + player.sendPacket(ActionFailed.STATIC_PACKET); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/Merchant.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/Merchant.java new file mode 100644 index 0000000..7af410e --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/Merchant.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.instance; + +import org.l2jmobius.gameserver.data.MerchantPriceConfigTable; +import org.l2jmobius.gameserver.data.MerchantPriceConfigTable.MerchantPriceConfig; +import org.l2jmobius.gameserver.data.xml.BuyListData; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.enums.creature.InstanceType; +import org.l2jmobius.gameserver.entity.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.mechanics.buylist.BuyListHolder; +import org.l2jmobius.gameserver.network.serverpackets.ActionFailed; +import org.l2jmobius.gameserver.network.serverpackets.BuyList; + +/** + * @version $Revision: 1.10.4.9 $ $Date: 2005/04/11 10:06:08 $ + */ +public class Merchant extends Folk +{ + private MerchantPriceConfig _mpc; + + /** + * Creates a merchant, + * @param template the merchant NPC template + */ + public Merchant(NpcTemplate template) + { + super(template); + setInstanceType(InstanceType.Merchant); + } + + @Override + public void onSpawn() + { + super.onSpawn(); + _mpc = MerchantPriceConfigTable.getInstance().getMerchantPriceConfig(this); + } + + @Override + public String getHtmlPath(int npcId, int value) + { + String pom = ""; + if (value == 0) + { + pom = Integer.toString(npcId); + } + else + { + pom = npcId + "-" + value; + } + + return "data/html/merchant/" + pom + ".htm"; + } + + /** + * @return Returns the mpc. + */ + public MerchantPriceConfig getMpc() + { + return _mpc; + } + + public void showBuyWindow(Player player, int value) + { + showBuyWindow(player, value, true); + } + + public void showBuyWindow(Player player, int value, boolean applyTax) + { + final BuyListHolder buyList = BuyListData.getInstance().getBuyList(value); + if (buyList == null) + { + LOGGER.warning("BuyList not found! BuyListId:" + value); + player.sendPacket(ActionFailed.STATIC_PACKET); + return; + } + + if (!buyList.isNpcAllowed(getId())) + { + LOGGER.warning("Npc not allowed in BuyList! BuyListId:" + value + " NpcId:" + getId()); + player.sendPacket(ActionFailed.STATIC_PACKET); + return; + } + + final double taxRate = (applyTax) ? _mpc.getTotalTaxRate() : 0; + player.setInventoryBlockingStatus(true); + + player.sendPacket(new BuyList(buyList, player.getAdena(), taxRate)); + // player.sendPacket(new ExBuySellList(player, buyList, taxRate, false)); + player.sendPacket(ActionFailed.STATIC_PACKET); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/Monster.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/Monster.java new file mode 100644 index 0000000..51d7591 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/Monster.java @@ -0,0 +1,223 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.instance; + +import java.util.List; + +import org.l2jmobius.gameserver.config.NpcConfig; +import org.l2jmobius.gameserver.config.custom.FakePlayersConfig; +import org.l2jmobius.gameserver.entity.WorldObject; +import org.l2jmobius.gameserver.entity.actor.Attackable; +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.enums.creature.InstanceType; +import org.l2jmobius.gameserver.entity.actor.holders.npc.MinionList; +import org.l2jmobius.gameserver.entity.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.mechanics.effects.EffectFlag; +import org.l2jmobius.gameserver.mechanics.skill.Skill; + +/** + * This class manages all Monsters. + *
      + *
    • Minion
    • + *
    • RaidBoss
    • + *
    • GrandBoss
    • + *
    + */ +public class Monster extends Attackable +{ + protected boolean _enableMinions = true; + + private Monster _master = null; + private MinionList _minionList = null; + + /** + * Creates a monster. + * @param template the monster NPC template + */ + public Monster(NpcTemplate template) + { + super(template); + setInstanceType(InstanceType.Monster); + setAutoAttackable(true); + } + + /** + * Return True if the attacker is not another Monster. + */ + @Override + public boolean isAutoAttackable(Creature attacker) + { + if (isFakePlayer()) + { + return FakePlayersConfig.FAKE_PLAYER_AUTO_ATTACKABLE || isInCombat() || attacker.isMonster() || (getScriptValue() > 0); + } + + // Check if the Monster target is aggressive. + if (NpcConfig.GUARD_ATTACK_AGGRO_MOB && isAggressive() && (attacker instanceof Guard)) + { + return true; + } + + if (attacker.isMonster()) + { + return attacker.isFakePlayer(); + } + + // Anything considers monsters friendly except Players, Attackables (Guards, Friendly NPC), Traps and EffectPoints. + if (!attacker.isPlayable() && !attacker.isAttackable() && !(attacker instanceof Trap) && !(attacker instanceof EffectPoint)) + { + return false; + } + + return super.isAutoAttackable(attacker); + } + + /** + * Return True if the Monster is Aggressive (aggroRange > 0). + */ + @Override + public boolean isAggressive() + { + return getTemplate().isAggressive() && !isAffected(EffectFlag.PASSIVE); + } + + @Override + public void onSpawn() + { + if (!isTeleporting() && (_master != null)) + { + setRandomWalking(false); + setIsRaidMinion(_master.isRaid()); + _master.getMinionList().onMinionSpawn(this); + } + + // Dynamic script-based minions spawned here, after all preparations. + super.onSpawn(); + } + + @Override + public synchronized void onTeleported() + { + super.onTeleported(); + + if (hasMinions()) + { + getMinionList().onMasterTeleported(); + } + } + + @Override + public boolean deleteMe() + { + if (hasMinions()) + { + getMinionList().onMasterDie(true); + } + + if (_master != null) + { + _master.getMinionList().onMinionDie(this, 0); + } + + return super.deleteMe(); + } + + @Override + public Monster getLeader() + { + return _master; + } + + public void setLeader(Monster leader) + { + _master = leader; + } + + public void enableMinions(boolean value) + { + _enableMinions = value; + } + + public boolean hasMinions() + { + return _minionList != null; + } + + public MinionList getMinionList() + { + if (_minionList == null) + { + synchronized (this) + { + if (_minionList == null) + { + _minionList = new MinionList(this); + } + } + } + + return _minionList; + } + + @Override + public boolean isMonster() + { + return true; + } + + @Override + public Monster asMonster() + { + return this; + } + + /** + * @return true if this Monster (or its master) is registered in WalkingManager + */ + @Override + public boolean isWalker() + { + return ((_master == null) ? super.isWalker() : _master.isWalker()); + } + + /** + * @return {@code true} if this Monster is not raid minion, master state otherwise. + */ + @Override + public boolean giveRaidCurse() + { + return (isRaidMinion() && (_master != null)) ? _master.giveRaidCurse() : super.giveRaidCurse(); + } + + @Override + public void doCast(Skill skill, Creature target, List targets) + { + // Might need some exceptions here, but it will prevent the monster buffing player bug. + if (!skill.hasNegativeEffect() && (getTarget() != null) && getTarget().isPlayer()) + { + setCastingNow(false); + setCastingSimultaneouslyNow(false); + return; + } + + super.doCast(skill, target, targets); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/OlympiadManager.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/OlympiadManager.java new file mode 100644 index 0000000..e64d518 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/OlympiadManager.java @@ -0,0 +1,285 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.actor.instance; + +import java.util.List; +import java.util.Map; +import java.util.logging.Logger; + +import org.l2jmobius.gameserver.config.OlympiadConfig; +import org.l2jmobius.gameserver.data.xml.MultisellData; +import org.l2jmobius.gameserver.entity.actor.Npc; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.enums.creature.InstanceType; +import org.l2jmobius.gameserver.entity.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.entity.item.enums.ItemProcessType; +import org.l2jmobius.gameserver.mechanics.olympiad.Olympiad; +import org.l2jmobius.gameserver.network.serverpackets.ExHeroList; +import org.l2jmobius.gameserver.network.serverpackets.NpcHtmlMessage; + +/** + * Olympiad NPCs Instance + * @author godson + */ +public class OlympiadManager extends Npc +{ + private static Logger _logOlymp = Logger.getLogger(OlympiadManager.class.getName()); + + private static final int GATE_PASS = OlympiadConfig.OLYMPIAD_COMP_RITEM; + private static final String FEWER_THAN = "Fewer than " + String.valueOf(OlympiadConfig.OLYMPIAD_REG_DISPLAY); + private static final String MORE_THAN = "More than " + String.valueOf(OlympiadConfig.OLYMPIAD_REG_DISPLAY); + + /** + * Creates an olympiad manager. + * @param template the olympiad manager NPC template + */ + public OlympiadManager(NpcTemplate template) + { + super(template); + setInstanceType(InstanceType.OlympiadManager); + } + + @Override + public void onBypassFeedback(Player player, String command) + { + if (command.startsWith("OlympiadDesc")) + { + final int val = Integer.parseInt(command.substring(13, 14)); + final String suffix = command.substring(14); + showChatWindow(player, val, suffix); + } + else if (command.startsWith("OlympiadNoble")) + { + if (player.getClassIndex() != 0) + { + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, Olympiad.OLYMPIAD_HTML_PATH + "noble_desc5.htm"); + html.replace("%objectId%", String.valueOf(getObjectId())); + player.sendPacket(html); + return; + } + + if (!player.isNoble() || (player.getPlayerClass().level() < 3)) + { + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, Olympiad.OLYMPIAD_HTML_PATH + "noble_desc6.htm"); + html.replace("%objectId%", String.valueOf(getObjectId())); + player.sendPacket(html); + return; + } + + final int val = Integer.parseInt(command.substring(14)); + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + + switch (val) + { + case 1: + { + Olympiad.getInstance().unRegisterNoble(player); + break; + } + case 2: + { + int classed = 0; + int nonClassed = 0; + final int[] array = Olympiad.getInstance().getWaitingList(); + if (array != null) + { + classed = array[0]; + nonClassed = array[1]; + } + + html.setFile(player, Olympiad.OLYMPIAD_HTML_PATH + "noble_registered.htm"); + if (OlympiadConfig.OLYMPIAD_REG_DISPLAY > 0) + { + html.replace("%listClassed%", classed < OlympiadConfig.OLYMPIAD_REG_DISPLAY ? FEWER_THAN : MORE_THAN); + html.replace("%listNonClassedTeam%", FEWER_THAN); + html.replace("%listNonClassed%", nonClassed < OlympiadConfig.OLYMPIAD_REG_DISPLAY ? FEWER_THAN : MORE_THAN); + } + else + { + html.replace("%listClassed%", String.valueOf(classed)); + html.replace("%listNonClassedTeam%", "0"); + html.replace("%listNonClassed%", String.valueOf(nonClassed)); + } + + html.replace("%objectId%", String.valueOf(getObjectId())); + player.sendPacket(html); + break; + } + case 3: + { + final int points = Olympiad.getInstance().getNoblePoints(player.getObjectId()); + html.setFile(player, Olympiad.OLYMPIAD_HTML_PATH + "noble_points1.htm"); + html.replace("%points%", String.valueOf(points)); + html.replace("%objectId%", String.valueOf(getObjectId())); + player.sendPacket(html); + break; + } + case 4: + { + Olympiad.getInstance().registerNoble(player, false); + break; + } + case 5: + { + Olympiad.getInstance().registerNoble(player, true); + break; + } + case 6: + { + if (player.getVariables().getInt(Olympiad.UNCLAIMED_OLYMPIAD_PASSES_VAR, 0) > 0) + { + html.setFile(player, Olympiad.OLYMPIAD_HTML_PATH + "noble_settle.htm"); + html.replace("%objectId%", String.valueOf(getObjectId())); + player.sendPacket(html); + } + else + { + html.setFile(player, Olympiad.OLYMPIAD_HTML_PATH + "noble_nopoints.htm"); + html.replace("%objectId%", String.valueOf(getObjectId())); + player.sendPacket(html); + } + break; + } + case 7: + { + MultisellData.getInstance().separateAndSend(102, player, this, false); + break; + } + case 8: + { + MultisellData.getInstance().separateAndSend(103, player, this, false); + break; + } + case 9: + { + final int point = Olympiad.getInstance().getLastNobleOlympiadPoints(player.getObjectId()); + html.setFile(player, Olympiad.OLYMPIAD_HTML_PATH + "noble_points2.htm"); + html.replace("%points%", String.valueOf(point)); + html.replace("%objectId%", String.valueOf(getObjectId())); + player.sendPacket(html); + break; + } + case 10: + { + final int passes = player.getVariables().getInt(Olympiad.UNCLAIMED_OLYMPIAD_PASSES_VAR, 0); + if (passes > 0) + { + player.getVariables().remove(Olympiad.UNCLAIMED_OLYMPIAD_PASSES_VAR); + player.addItem(ItemProcessType.REWARD, GATE_PASS, passes * OlympiadConfig.OLYMPIAD_GP_PER_POINT, player, true); + } + break; + } + default: + { + _logOlymp.warning("Olympiad System: Could not send packet for request " + val); + break; + } + } + } + else if (command.startsWith("Olympiad")) + { + final int val = Integer.parseInt(command.substring(9, 10)); + final NpcHtmlMessage reply = new NpcHtmlMessage(getObjectId()); + + switch (val) + { + case 1: + { + final Map matches = Olympiad.getInstance().getMatchList(); + reply.setFile(player, Olympiad.OLYMPIAD_HTML_PATH + "olympiad_observe1.htm"); + for (int i = 0; i < Olympiad.getStadiumCount(); i++) + { + final int arenaID = i + 1; + + // &$906; -> \\&\\$906; + reply.replace("%title" + arenaID + "%", matches.getOrDefault(i, "\\&$906;")); + } + + reply.replace("%objectId%", String.valueOf(getObjectId())); + player.sendPacket(reply); + break; + } + case 2: + { + // for example >> Olympiad 1_88 + final int classId = Integer.parseInt(command.substring(11)); + if (((classId >= 88) && (classId <= 118)) || ((classId >= 131) && (classId <= 134)) || (classId == 136)) + { + final List names = Olympiad.getInstance().getClassLeaderBoard(classId); + reply.setFile(player, Olympiad.OLYMPIAD_HTML_PATH + "olympiad_ranking.htm"); + int index = 1; + for (String name : names) + { + reply.replace("%place" + index + "%", String.valueOf(index)); + reply.replace("%rank" + index + "%", name); + index++; + if (index > 10) + { + break; + } + } + + for (; index <= 10; index++) + { + reply.replace("%place" + index + "%", ""); + reply.replace("%rank" + index + "%", ""); + } + + reply.replace("%objectId%", String.valueOf(getObjectId())); + player.sendPacket(reply); + } + break; + } + case 3: + { + final int id = Integer.parseInt(command.substring(11)); + Olympiad.addSpectator(id, player, true); + break; + } + case 4: + { + player.sendPacket(new ExHeroList()); + break; + } + default: + { + _logOlymp.warning("Olympiad System: Could not send packet for request " + val); + break; + } + } + } + else + { + super.onBypassFeedback(player, command); + } + } + + public void showChatWindow(Player player, int value, String suffix) + { + String filename = Olympiad.OLYMPIAD_HTML_PATH; + filename += "noble_desc" + value; + filename += (suffix != null) ? suffix + ".htm" : ".htm"; + if (filename.equals(Olympiad.OLYMPIAD_HTML_PATH + "noble_desc0.htm")) + { + filename = Olympiad.OLYMPIAD_HTML_PATH + "noble_main.htm"; + } + + showChatWindow(player, filename); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/Pet.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/Pet.java new file mode 100644 index 0000000..8ee6e95 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/Pet.java @@ -0,0 +1,1459 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.instance; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.concurrent.Future; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.commons.database.DatabaseFactory; +import org.l2jmobius.commons.threads.ThreadPool; +import org.l2jmobius.commons.util.Rnd; +import org.l2jmobius.gameserver.config.GeneralConfig; +import org.l2jmobius.gameserver.config.NpcConfig; +import org.l2jmobius.gameserver.config.PlayerConfig; +import org.l2jmobius.gameserver.config.RatesConfig; +import org.l2jmobius.gameserver.data.holders.PetData; +import org.l2jmobius.gameserver.data.holders.PetLevelData; +import org.l2jmobius.gameserver.data.sql.CharSummonTable; +import org.l2jmobius.gameserver.data.sql.SummonEffectTable; +import org.l2jmobius.gameserver.data.xml.PetDataTable; +import org.l2jmobius.gameserver.data.xml.SkillData; +import org.l2jmobius.gameserver.entity.World; +import org.l2jmobius.gameserver.entity.WorldObject; +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.Summon; +import org.l2jmobius.gameserver.entity.actor.enums.creature.InstanceType; +import org.l2jmobius.gameserver.entity.actor.stat.PetStat; +import org.l2jmobius.gameserver.entity.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.entity.groups.PartyDistributionType; +import org.l2jmobius.gameserver.entity.item.Weapon; +import org.l2jmobius.gameserver.entity.item.enums.BodyPart; +import org.l2jmobius.gameserver.entity.item.enums.ItemLocation; +import org.l2jmobius.gameserver.entity.item.enums.ItemProcessType; +import org.l2jmobius.gameserver.entity.item.instance.Item; +import org.l2jmobius.gameserver.entity.item.type.ArmorType; +import org.l2jmobius.gameserver.entity.item.type.WeaponType; +import org.l2jmobius.gameserver.entity.itemcontainer.Inventory; +import org.l2jmobius.gameserver.entity.itemcontainer.PetInventory; +import org.l2jmobius.gameserver.entity.zone.ZoneId; +import org.l2jmobius.gameserver.handler.IItemHandler; +import org.l2jmobius.gameserver.handler.ItemHandler; +import org.l2jmobius.gameserver.managers.CursedWeaponsManager; +import org.l2jmobius.gameserver.managers.ItemManager; +import org.l2jmobius.gameserver.managers.ItemsOnGroundManager; +import org.l2jmobius.gameserver.mechanics.skill.AbnormalType; +import org.l2jmobius.gameserver.mechanics.skill.BuffInfo; +import org.l2jmobius.gameserver.mechanics.skill.EffectScope; +import org.l2jmobius.gameserver.mechanics.skill.Skill; +import org.l2jmobius.gameserver.mechanics.skill.enums.SkillFinishType; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.serverpackets.ActionFailed; +import org.l2jmobius.gameserver.network.serverpackets.InventoryUpdate; +import org.l2jmobius.gameserver.network.serverpackets.PetInventoryUpdate; +import org.l2jmobius.gameserver.network.serverpackets.StatusUpdate; +import org.l2jmobius.gameserver.network.serverpackets.StopMove; +import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; +import org.l2jmobius.gameserver.taskmanagers.DecayTaskManager; + +public class Pet extends Summon +{ + protected static final Logger LOGGER_PET = Logger.getLogger(Pet.class.getName()); + + private static final String ADD_SKILL_SAVE = "INSERT INTO character_pet_skills_save (petObjItemId,skill_id,skill_level,remaining_time,buff_index) VALUES (?,?,?,?,?)"; + private static final String RESTORE_SKILL_SAVE = "SELECT petObjItemId,skill_id,skill_level,remaining_time,buff_index FROM character_pet_skills_save WHERE petObjItemId=? ORDER BY buff_index ASC"; + private static final String DELETE_SKILL_SAVE = "DELETE FROM character_pet_skills_save WHERE petObjItemId=?"; + + int _curFed; + final PetInventory _inventory; + private final int _controlObjectId; + private boolean _respawned; + private final boolean _mountable; + private Future _feedTask; + private PetData _data; + private PetLevelData _leveldata; + + /** The Experience before the last Death Penalty */ + private long _expBeforeDeath = 0; + private int _curWeightPenalty = 0; + + /** + * Creates a pet. + * @param template the pet NPC template + * @param owner the owner + * @param control the summoning item + */ + public Pet(NpcTemplate template, Player owner, Item control) + { + this(template, owner, control, (byte) (template.getDisplayId() == 12564 ? owner.getLevel() : template.getLevel())); + } + + /** + * Creates a pet. + * @param template the pet NPC template + * @param owner the pet NPC template + * @param control the summoning item + * @param level the level + */ + public Pet(NpcTemplate template, Player owner, Item control, byte level) + { + super(template, owner); + setInstanceType(InstanceType.Pet); + + _controlObjectId = control.getObjectId(); + getStat().setLevel((byte) Math.max(level, PetDataTable.getInstance().getPetMinLevel(template.getId()))); + _inventory = new PetInventory(this); + _inventory.restore(); + + final int npcId = template.getId(); + _mountable = PetDataTable.isMountable(npcId); + getPetData(); + getPetLevelData(); + } + + public PetLevelData getPetLevelData() + { + if (_leveldata == null) + { + _leveldata = PetDataTable.getInstance().getPetLevelData(getTemplate().getId(), getStat().getLevel()); + } + + return _leveldata; + } + + public PetData getPetData() + { + if (_data == null) + { + _data = PetDataTable.getInstance().getPetData(getTemplate().getId()); + } + + return _data; + } + + public void setPetData(PetLevelData value) + { + _leveldata = value; + } + + /** + * Manage Feeding Task.
    + * Feed or kill the pet depending on hunger level.
    + * If pet has food in inventory and feed level drops below 55% then consume food from inventory.
    + * Send a broadcastStatusUpdate packet for this Pet + */ + class FeedTask implements Runnable + { + @Override + public void run() + { + try + { + if ((getOwner() == null) || !getOwner().hasSummon() || (getOwner().getSummon().getObjectId() != getObjectId())) + { + stopFeed(); + return; + } + + setCurrentFed(_curFed > getFeedConsume() ? _curFed - getFeedConsume() : 0); + broadcastStatusUpdate(); + + if (isUncontrollable()) + { + if (Rnd.get(100) < 30) + { + sendPacket(SystemMessageId.STARVING_GRUMPY_AND_FED_UP_YOUR_PET_HAS_LEFT); + unSummon(getOwner()); + stopFeed(); + return; + } + + sendPacket(SystemMessageId.YOUR_PET_SERVITOR_IS_UNRESPONSIVE_AND_WILL_NOT_OBEY_ANY_ORDERS); + return; + } + + final Set foodIds = getPetData().getFood(); + if (foodIds.isEmpty()) + { + if (isHungry()) + { + sendPacket(SystemMessageId.YOUR_PET_IS_VERY_HUNGRY_PLEASE_BE_CAREFUL); + } + return; + } + + // Try to find food in pet inventory. + Item food = null; + for (int id : foodIds) + { + food = _inventory.getItemByItemId(id); + if (food != null) + { + break; + } + } + + // Pet is hungry and food is available - auto feed. + if ((food != null) && isHungry()) + { + final IItemHandler handler = ItemHandler.getInstance().getHandler(food.getEtcItem()); + if (handler != null) + { + final SystemMessage sm = new SystemMessage(SystemMessageId.YOUR_PET_WAS_HUNGRY_SO_IT_ATE_S1); + sm.addItemName(food.getId()); + sendPacket(sm); + handler.onItemUse(Pet.this, food, false); + } + } + + // Notify player if pet uncontrollable due to starvation. + if (isUncontrollable()) + { + sendPacket(SystemMessageId.YOUR_PET_SERVITOR_IS_UNRESPONSIVE_AND_WILL_NOT_OBEY_ANY_ORDERS); + } + } + catch (Exception e) + { + LOGGER_PET.log(Level.SEVERE, "Pet [ObjectId: " + getObjectId() + "] a feed task error has occurred", e); + } + } + + private int getFeedConsume() + { + return isAttackingNow() ? getPetLevelData().getPetFeedBattle() : getPetLevelData().getPetFeedNormal(); + } + } + + public static synchronized Pet spawnPet(NpcTemplate template, Player owner, Item control) + { + final Pet existingPet = World.getPet(owner.getObjectId()); + if (existingPet != null) // Owner has a pet listed in world. + { + existingPet.unSummon(owner); + } + + final PetData data = PetDataTable.getInstance().getPetData(template.getId()); + final Pet pet = restore(control, template, owner); + + // Add the pet instance to world. + if (pet != null) + { + pet.setTitle(owner.getName()); + if (data.isSynchLevel() && (pet.getLevel() != owner.getLevel())) + { + final byte availableLevel = (byte) Math.min(data.getMaxLevel(), owner.getLevel()); + pet.getStat().setLevel(availableLevel); + pet.getStat().setExp(pet.getStat().getExpForLevel(availableLevel)); + } + + World.addPet(owner.getObjectId(), pet); + } + + return pet; + } + + @Override + public PetStat getStat() + { + return (PetStat) super.getStat(); + } + + @Override + public void initCharStat() + { + setStat(new PetStat(this)); + } + + public boolean isRespawned() + { + return _respawned; + } + + @Override + public int getSummonType() + { + return 2; + } + + @Override + public int getControlObjectId() + { + return _controlObjectId; + } + + public Item getControlItem() + { + return getOwner().getInventory().getItemByObjectId(_controlObjectId); + } + + public int getCurrentFed() + { + return _curFed; + } + + public void setCurrentFed(int num) + { + // if (num <= 0) + // { + // sendPacket(new ExChangeNpcState(getObjectId(), 0x64)); + // } + // else if ((_curFed <= 0) && (num > 0)) + // { + // sendPacket(new ExChangeNpcState(getObjectId(), 0x65)); + // } + + _curFed = num > getMaxFed() ? getMaxFed() : num; + } + + /** + * Returns the pet's currently equipped weapon instance (if any). + */ + @Override + public Item getActiveWeaponInstance() + { + for (Item item : _inventory.getItems()) + { + if ((item.getItemLocation() == ItemLocation.PET_EQUIP) && (item.getTemplate().getBodyPart() == BodyPart.R_HAND)) + { + return item; + } + } + + return null; + } + + /** + * Returns the pet's currently equipped weapon (if any). + */ + @Override + public Weapon getActiveWeaponItem() + { + final Item weapon = getActiveWeaponInstance(); + return weapon == null ? null : (Weapon) weapon.getTemplate(); + } + + @Override + public Item getSecondaryWeaponInstance() + { + // temporary? unavailable + return null; + } + + @Override + public Weapon getSecondaryWeaponItem() + { + // temporary? unavailable + return null; + } + + @Override + public PetInventory getInventory() + { + return _inventory; + } + + /** + * Destroys item from inventory and send a Server->Client InventoryUpdate packet to the Player. + * @param process : ItemProcessType identifier of process triggering this action + * @param objectId : int Item Instance identifier of the item to be destroyed + * @param count : int Quantity of items to be destroyed + * @param reference : WorldObject Object referencing current action like NPC selling item or previous item in transformation + * @param sendMessage : boolean Specifies whether to send message to Client about this action + * @return boolean informing if the action was successfull + */ + @Override + public boolean destroyItem(ItemProcessType process, int objectId, int count, WorldObject reference, boolean sendMessage) + { + final Item item = _inventory.destroyItem(process, objectId, count, getOwner(), reference); + if (item == null) + { + if (sendMessage) + { + sendPacket(SystemMessageId.INCORRECT_ITEM_COUNT_2); + } + + return false; + } + + // Send Pet inventory update packet. + final PetInventoryUpdate petIU = new PetInventoryUpdate(); + petIU.addItem(item); + sendPacket(petIU); + + if (sendMessage) + { + final SystemMessage sm; + if (count > 1) + { + sm = new SystemMessage(SystemMessageId.S2_S1_HAS_DISAPPEARED); + sm.addItemName(item.getId()); + sm.addInt(count); + } + else + { + sm = new SystemMessage(SystemMessageId.S1_HAS_DISAPPEARED); + sm.addItemName(item.getId()); + } + } + + return true; + } + + /** + * Destroy item from inventory by using its itemId and send a Server->Client InventoryUpdate packet to the Player. + * @param process : ItemProcessType identifier of process triggering this action + * @param itemId : int Item identifier of the item to be destroyed + * @param count : int Quantity of items to be destroyed + * @param reference : WorldObject Object referencing current action like NPC selling item or previous item in transformation + * @param sendMessage : boolean Specifies whether to send message to Client about this action + * @return boolean informing if the action was successfull + */ + @Override + public boolean destroyItemByItemId(ItemProcessType process, int itemId, int count, WorldObject reference, boolean sendMessage) + { + final Item item = _inventory.destroyItemByItemId(process, itemId, count, getOwner(), reference); + if (item == null) + { + if (sendMessage) + { + sendPacket(SystemMessageId.INCORRECT_ITEM_COUNT_2); + } + + return false; + } + + // Send Pet inventory update packet. + final PetInventoryUpdate petIU = new PetInventoryUpdate(); + petIU.addItem(item); + sendPacket(petIU); + + if (sendMessage) + { + final SystemMessage sm; + if (count > 1) + { + sm = new SystemMessage(SystemMessageId.S2_S1_HAS_DISAPPEARED); + sm.addItemName(item.getId()); + sm.addInt(count); + } + else + { + sm = new SystemMessage(SystemMessageId.S1_HAS_DISAPPEARED); + sm.addItemName(item.getId()); + } + + sendPacket(sm); + } + + return true; + } + + @Override + public void doPickupItem(WorldObject object) + { + if (isDead()) + { + return; + } + + getAI().setIntentionIdle(); + broadcastPacket(new StopMove(this)); + if (!object.isItem()) + { + // Do not try to pickup anything that is not an item :) + LOGGER_PET.warning(this + " trying to pickup wrong target." + object); + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + + final Item target = (Item) object; + + // Cursed weapons + if (CursedWeaponsManager.getInstance().isCursed(target.getId())) + { + final SystemMessage smsg = new SystemMessage(SystemMessageId.YOU_HAVE_FAILED_TO_PICK_UP_S1); + smsg.addItemName(target.getId()); + sendPacket(smsg); + return; + } + + final boolean follow = getFollowStatus(); + SystemMessage smsg = null; + synchronized (target) + { + // Check if the target to pick up is visible. + if (!target.isSpawned()) + { + // Send a Server->Client packet ActionFailed to this Player. + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + + if (!target.getDropProtection().tryPickUp(this)) + { + sendPacket(ActionFailed.STATIC_PACKET); + smsg = new SystemMessage(SystemMessageId.YOU_HAVE_FAILED_TO_PICK_UP_S1); + smsg.addItemName(target); + sendPacket(smsg); + return; + } + + if (((isInParty() && (getParty().getDistributionType() == PartyDistributionType.FINDERS_KEEPERS)) || !isInParty()) && !_inventory.validateCapacity(target)) + { + sendPacket(ActionFailed.STATIC_PACKET); + sendPacket(SystemMessageId.UNABLE_TO_PLACE_ITEM_YOUR_PET_IS_TOO_ENCUMBERED); + return; + } + + if ((target.getOwnerId() != 0) && (target.getOwnerId() != getOwner().getObjectId()) && !getOwner().isInLooterParty(target.getOwnerId())) + { + if (target.getId() == Inventory.ADENA_ID) + { + smsg = new SystemMessage(SystemMessageId.YOU_HAVE_FAILED_TO_PICK_UP_S1_ADENA); + smsg.addInt(target.getCount()); + } + else if (target.getCount() > 1) + { + smsg = new SystemMessage(SystemMessageId.YOU_HAVE_FAILED_TO_PICK_UP_S2_S1_S); + smsg.addItemName(target); + smsg.addInt(target.getCount()); + } + else + { + smsg = new SystemMessage(SystemMessageId.YOU_HAVE_FAILED_TO_PICK_UP_S1); + smsg.addItemName(target); + } + + sendPacket(ActionFailed.STATIC_PACKET); + sendPacket(smsg); + return; + } + + if ((target.getItemLootShedule() != null) && ((target.getOwnerId() == getOwner().getObjectId()) || getOwner().isInLooterParty(target.getOwnerId()))) + { + target.resetOwnerTimer(); + } + + // Remove from the ground! + target.pickupMe(this); + + if (GeneralConfig.SAVE_DROPPED_ITEM) + { + ItemsOnGroundManager.getInstance().removeObject(target); + } + } + + // Herbs + if (target.getTemplate().hasExImmediateEffect()) + { + final IItemHandler handler = ItemHandler.getInstance().getHandler(target.getEtcItem()); + if (handler == null) + { + LOGGER.warning("No item handler registered for item ID: " + target.getId() + "."); + } + else + { + handler.onItemUse(this, target, false); + } + + ItemManager.destroyItem(ItemProcessType.NONE, target, getOwner(), null); + broadcastStatusUpdate(); + } + else + { + if (target.getId() == Inventory.ADENA_ID) + { + smsg = new SystemMessage(SystemMessageId.YOUR_PET_PICKED_UP_S1_ADENA); + smsg.addInt(target.getCount()); + sendPacket(smsg); + } + else if (target.isEnchanted()) + { + smsg = new SystemMessage(SystemMessageId.YOUR_PET_PICKED_UP_S1_S2); + smsg.addInt(target.getEnchantLevel()); + smsg.addItemName(target); + sendPacket(smsg); + } + else if (target.getCount() > 1) + { + smsg = new SystemMessage(SystemMessageId.YOUR_PET_PICKED_UP_S2_S1_S); + smsg.addInt(target.getCount()); + smsg.addItemName(target); + sendPacket(smsg); + } + else + { + smsg = new SystemMessage(SystemMessageId.YOUR_PET_PICKED_UP_S1); + smsg.addItemName(target); + sendPacket(smsg); + } + + // Attention broadcast for armor and weapon. + if ((target.getItemType() instanceof ArmorType) || (target.getItemType() instanceof WeaponType)) + { + SystemMessage attMsg; + if (target.isEnchanted()) + { + attMsg = new SystemMessage(SystemMessageId.ATTENTION_S1_S_PET_PICKED_UP_S2_S3); + attMsg.addPcName(getOwner()); + attMsg.addInt(target.getEnchantLevel()); + attMsg.addItemName(target); + } + else + { + attMsg = new SystemMessage(SystemMessageId.ATTENTION_S1_S_PET_PICKED_UP_S2); + attMsg.addPcName(getOwner()); + attMsg.addItemName(target); + } + + broadcastPacket(attMsg); + } + + // If owner is in party and it is not finders keepers, distribute the item instead of stealing it -.- + if (getOwner().isInParty() && (getOwner().getParty().getDistributionType() != PartyDistributionType.FINDERS_KEEPERS)) + { + getOwner().getParty().distributeItem(getOwner(), target); + } + else + { + final Item item = _inventory.addItem(ItemProcessType.PICKUP, target, getOwner(), this); + + // sendPacket(new PetItemList(_inventory.getItems())); + sendPacket(new PetInventoryUpdate(item)); + } + } + + getAI().setIntentionIdle(); + + if (follow) + { + followOwner(); + } + } + + @Override + public void deleteMe(Player owner) + { + _inventory.transferItemsToOwner(); + super.deleteMe(owner); + destroyControlItem(owner, false); // this should also delete the pet from the db + CharSummonTable.getInstance().getPets().remove(getOwner().getObjectId()); + } + + @Override + public boolean doDie(Creature killer) + { + final Player owner = getOwner(); + if ((owner != null) && !owner.isInDuel() && (!isInsideZone(ZoneId.PVP) || isInsideZone(ZoneId.SIEGE))) + { + deathPenalty(); + } + + if (!super.doDie(killer, true)) + { + return false; + } + + stopFeed(); + sendPacket(SystemMessageId.YOUR_PET_HAS_BEEN_KILLED_MAKE_SURE_YOU_RESURRECT_YOUR_PET_WITHIN_20_MINUTES_OR_YOUR_PET_AND_ALL_OF_IT_S_ITEMS_WILL_DISAPPEAR_FOREVER); + DecayTaskManager.getInstance().add(this); + + // Do not decrease exp if is in duel, arena. + return true; + } + + @Override + public void doRevive() + { + getOwner().removeReviving(); + + super.doRevive(); + + // stopDecay + DecayTaskManager.getInstance().cancel(this); + startFeed(); + if (!isHungry()) + { + setRunning(); + } + + getAI().setIntentionActive(); + } + + @Override + public void doRevive(double revivePower) + { + // Restore the pet's lost experience,. + // depending on the % return of the skill used (based on its power). + restoreExp(revivePower); + doRevive(); + } + + /** + * Transfers item to another inventory + * @param process ItemProcessType identifier of process triggering this action + * @param objectId Item Identifier of the item to be transfered + * @param count Quantity of items to be transfered + * @param target + * @param actor the player requesting the item transfer + * @param reference Object referencing current action like NPC selling item or previous item in transformation + * @return Item corresponding to the new item or the updated item in inventory + */ + public Item transferItem(ItemProcessType process, int objectId, int count, Inventory target, Player actor, WorldObject reference) + { + final Item oldItem = _inventory.getItemByObjectId(objectId); + final Item playerOldItem = target.getItemByItemId(oldItem.getId()); + final Item newItem = _inventory.transferItem(process, objectId, count, target, actor, reference); + if (newItem == null) + { + return null; + } + + // Send inventory update packet. + final PetInventoryUpdate petIU = new PetInventoryUpdate(); + if ((oldItem.getCount() > 0) && (oldItem != newItem)) + { + petIU.addModifiedItem(oldItem); + } + else + { + petIU.addRemovedItem(oldItem); + } + + sendPacket(petIU); + + // Send target update packet. + if (!newItem.isStackable()) + { + final Player owner = getOwner(); + if (owner != null) + { + final InventoryUpdate iu = new InventoryUpdate(); + iu.addNewItem(newItem); + owner.sendInventoryUpdate(iu); + } + } + else if ((playerOldItem != null) && newItem.isStackable()) + { + final Player owner = getOwner(); + if (owner != null) + { + final InventoryUpdate iu = new InventoryUpdate(); + iu.addModifiedItem(newItem); + owner.sendInventoryUpdate(iu); + } + } + + return newItem; + } + + /** + * Remove the Pet from DB and its associated item from the player inventory + * @param owner The owner from whose inventory we should delete the item + * @param evolve + */ + public void destroyControlItem(Player owner, boolean evolve) + { + // Remove the pet instance from world. + World.removePet(owner.getObjectId()); + + // Delete from inventory. + try + { + Item removedItem; + if (evolve) + { + removedItem = owner.getInventory().destroyItem(ItemProcessType.FEE, _controlObjectId, 1, getOwner(), this); + } + else + { + removedItem = owner.getInventory().destroyItem(ItemProcessType.DESTROY, _controlObjectId, 1, getOwner(), this); + if (removedItem != null) + { + final SystemMessage sm = new SystemMessage(SystemMessageId.S1_HAS_DISAPPEARED); + sm.addItemName(removedItem); + owner.sendPacket(sm); + } + } + + if (removedItem == null) + { + LOGGER.warning("Couldn't destroy pet control item for " + owner + " pet: " + this + " evolve: " + evolve); + } + else + { + final InventoryUpdate iu = new InventoryUpdate(); + iu.addRemovedItem(removedItem); + owner.sendInventoryUpdate(iu); + + final StatusUpdate su = new StatusUpdate(owner); + su.addAttribute(StatusUpdate.CUR_LOAD, owner.getCurrentLoad()); + owner.sendPacket(su); + + owner.broadcastUserInfo(); + + World.removeObject(removedItem); + } + } + catch (Exception e) + { + LOGGER_PET.log(Level.WARNING, "Error while destroying control item: " + e.getMessage(), e); + } + + // Pet control item no longer exists, delete the pet from the db. + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement("DELETE FROM pets WHERE item_obj_id = ?")) + { + ps.setInt(1, _controlObjectId); + ps.execute(); + } + catch (Exception e) + { + LOGGER_PET.log(Level.SEVERE, "Failed to delete Pet [ObjectId: " + getObjectId() + "]", e); + } + } + + public void dropAllItems() + { + try + { + for (Item item : _inventory.getItems()) + { + dropItemHere(item); + } + } + catch (Exception e) + { + LOGGER_PET.log(Level.WARNING, "Pet Drop Error: " + e.getMessage(), e); + } + } + + public void dropItemHere(Item item, boolean protect) + { + final Item dropit = _inventory.dropItem(ItemProcessType.DROP, item.getObjectId(), item.getCount(), getOwner(), this); + if (dropit != null) + { + if (protect) + { + dropit.getDropProtection().protect(getOwner()); + } + + LOGGER_PET.finer("Item id to drop: " + dropit.getId() + " amount: " + dropit.getCount()); + dropit.dropMe(this, getX(), getY(), getZ() + 100); + } + } + + public void dropItemHere(Item dropit) + { + dropItemHere(dropit, false); + } + + /** + * @return Returns the mount able. + */ + @Override + public boolean isMountable() + { + return _mountable; + } + + private static Pet restore(Item control, NpcTemplate template, Player owner) + { + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement("SELECT item_obj_id, name, level, curHp, curMp, exp, sp, fed FROM pets WHERE item_obj_id=?")) + { + Pet pet; + ps.setInt(1, control.getObjectId()); + try (ResultSet rs = ps.executeQuery()) + { + if (!rs.next()) + { + if (template.isType("BabyPet")) + { + pet = new BabyPet(template, owner, control); + } + else + { + pet = new Pet(template, owner, control); + } + + return pet; + } + + if (template.isType("BabyPet")) + { + pet = new BabyPet(template, owner, control, rs.getByte("level")); + } + else + { + pet = new Pet(template, owner, control, rs.getByte("level")); + } + + pet._respawned = true; + pet.setName(rs.getString("name")); + + long exp = rs.getLong("exp"); + final PetLevelData info = PetDataTable.getInstance().getPetLevelData(pet.getId(), pet.getLevel()); + + // DS: update experience based by level. + // Avoiding pet delevels due to exp per level values changed. + if ((info != null) && (exp < info.getPetMaxExp())) + { + exp = info.getPetMaxExp(); + } + + pet.getStat().setExp(exp); + pet.getStat().setSp(rs.getInt("sp")); + + pet.getStatus().setCurrentHp(rs.getInt("curHp")); + pet.getStatus().setCurrentMp(rs.getInt("curMp")); + pet.getStatus().setCurrentCp(pet.getMaxCp()); + if (rs.getDouble("curHp") < 1) + { + pet.setDead(true); + pet.stopHpMpRegeneration(); + } + + pet.setCurrentFed(rs.getInt("fed")); + } + + return pet; + } + catch (Exception e) + { + LOGGER_PET.log(Level.WARNING, "Could not restore pet data for owner: " + owner + " - " + e.getMessage(), e); + } + + return null; + } + + @Override + public void setRestoreSummon(boolean value) + { + _restoreSummon = value; + } + + @Override + public void stopSkillEffects(SkillFinishType type, int skillId) + { + super.stopSkillEffects(type, skillId); + SummonEffectTable.getInstance().removePetEffects(getControlObjectId(), skillId); + } + + @Override + public void storeMe() + { + if (_controlObjectId == 0) + { + // This is a summon, not a pet, don't store anything. + return; + } + + if (!PlayerConfig.RESTORE_PET_ON_RECONNECT) + { + _restoreSummon = false; + } + + final String req = !_respawned ? "INSERT INTO pets (name,level,curHp,curMp,exp,sp,fed,ownerId,restore,item_obj_id) VALUES (?,?,?,?,?,?,?,?,?,?)" : "UPDATE pets SET name=?,level=?,curHp=?,curMp=?,exp=?,sp=?,fed=?,ownerId=?,restore=? WHERE item_obj_id = ?"; + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement(req)) + { + ps.setString(1, getName()); + ps.setInt(2, getStat().getLevel()); + ps.setDouble(3, getStatus().getCurrentHp()); + ps.setDouble(4, getStatus().getCurrentMp()); + ps.setLong(5, getStat().getExp()); + ps.setLong(6, getStat().getSp()); + ps.setInt(7, _curFed); + ps.setInt(8, getOwner().getObjectId()); + ps.setString(9, String.valueOf(_restoreSummon)); // True restores pet on login. + ps.setInt(10, _controlObjectId); + ps.executeUpdate(); + + _respawned = true; + if (_restoreSummon) + { + CharSummonTable.getInstance().getPets().put(getOwner().getObjectId(), getControlObjectId()); + } + else + { + CharSummonTable.getInstance().getPets().remove(getOwner().getObjectId()); + } + } + catch (Exception e) + { + LOGGER_PET.log(Level.SEVERE, "Failed to store Pet [ObjectId: " + getObjectId() + "] data", e); + } + + final Item itemInst = getControlItem(); + if ((itemInst != null) && (itemInst.getEnchantLevel() != getStat().getLevel())) + { + itemInst.setEnchantLevel(getStat().getLevel()); + itemInst.updateDatabase(); + } + } + + @Override + public void storeEffect(boolean storeEffects) + { + if (!PlayerConfig.SUMMON_STORE_SKILL_COOLTIME) + { + return; + } + + // Clear list for overwrite. + SummonEffectTable.getInstance().clearPetEffects(getControlObjectId()); + + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps1 = con.prepareStatement(DELETE_SKILL_SAVE); + PreparedStatement ps2 = con.prepareStatement(ADD_SKILL_SAVE)) + { + // Delete all current stored effects for summon to avoid dupe. + ps1.setInt(1, _controlObjectId); + ps1.execute(); + + int buffIndex = 0; + + final List storedSkills = new ArrayList<>(); + + // Store all effect data along with calculated remaining. + if (storeEffects) + { + for (BuffInfo info : getEffectList().getEffects()) + { + if (info == null) + { + continue; + } + + final Skill skill = info.getSkill(); + + // Do not save heals. + if (skill.getAbnormalType() == AbnormalType.LIFE_FORCE_OTHERS) + { + continue; + } + + if (skill.isToggle()) + { + continue; + } + + // Dances and songs are not kept in retail. + if (skill.isDance() && !PlayerConfig.ALT_STORE_DANCES) + { + continue; + } + + if (storedSkills.contains(skill.getReuseHashCode())) + { + continue; + } + + storedSkills.add(skill.getReuseHashCode()); + + ps2.setInt(1, _controlObjectId); + ps2.setInt(2, skill.getId()); + ps2.setInt(3, skill.getLevel()); + ps2.setInt(4, info.getTime()); + ps2.setInt(5, ++buffIndex); + ps2.addBatch(); + + SummonEffectTable.getInstance().addPetEffect(getControlObjectId(), skill, info.getTime()); + } + + ps2.executeBatch(); + } + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Could not store pet effect data: ", e); + } + } + + @Override + public void restoreEffects() + { + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps1 = con.prepareStatement(RESTORE_SKILL_SAVE); + PreparedStatement ps2 = con.prepareStatement(DELETE_SKILL_SAVE)) + { + if (!SummonEffectTable.getInstance().containsPetId(getControlObjectId())) + { + ps1.setInt(1, _controlObjectId); + try (ResultSet rs = ps1.executeQuery()) + { + while (rs.next()) + { + final Skill skill = SkillData.getInstance().getSkill(rs.getInt("skill_id"), rs.getInt("skill_level")); + if (skill == null) + { + continue; + } + + if (skill.hasEffects(EffectScope.GENERAL)) + { + SummonEffectTable.getInstance().addPetEffect(getControlObjectId(), skill, rs.getInt("remaining_time")); + } + } + } + } + + ps2.setInt(1, _controlObjectId); + ps2.executeUpdate(); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Could not restore " + this + " active effect data: " + e.getMessage(), e); + } + finally + { + SummonEffectTable.getInstance().applyPetEffects(this, getControlObjectId()); + } + } + + public synchronized void stopFeed() + { + if (_feedTask == null) + { + return; + } + + _feedTask.cancel(false); + _feedTask = null; + } + + public synchronized void startFeed() + { + // Stop feeding task if it is active. + stopFeed(); + if (!isDead() && (getOwner().getSummon() == this)) + { + _feedTask = ThreadPool.scheduleAtFixedRate(new FeedTask(), 10000, 10000); + } + } + + @Override + public synchronized void unSummon(Player owner) + { + stopFeed(); + super.unSummon(owner); + + if (!isDead()) + { + if (_inventory != null) + { + _inventory.deleteMe(); + } + + World.removePet(owner.getObjectId()); + } + } + + /** + * Restore the specified % of experience this Pet has lost. + * @param restorePercent + */ + public void restoreExp(double restorePercent) + { + if (_expBeforeDeath > 0) + { + // Restore the specified % of lost experience. + getStat().addExp(Math.round(((_expBeforeDeath - getStat().getExp()) * restorePercent) / 100)); + _expBeforeDeath = 0; + } + } + + private void deathPenalty() + { + // TODO: Need Correct Penalty + + final int level = getStat().getLevel(); + final double percentLost = (-0.07 * level) + 6.5; + + // Calculate the Experience loss. + final long lostExp = Math.round(((getStat().getExpForLevel(level + 1) - getStat().getExpForLevel(level)) * percentLost) / 100); + + // Get the Experience before applying penalty. + _expBeforeDeath = getStat().getExp(); + + // Set the new Experience value of the Pet. + getStat().addExp(-lostExp); + } + + @Override + public synchronized void addExpAndSp(double addToExp, double addToSp) + { + if (getId() == 12564) + { + getStat().addExpAndSp(Math.round(addToExp * RatesConfig.SINEATER_XP_RATE), addToSp); + } + else + { + getStat().addExpAndSp(Math.round(addToExp * RatesConfig.PET_XP_RATE), addToSp); + } + } + + @Override + public long getExpForThisLevel() + { + return getStat().getExpForLevel(getLevel()); + } + + @Override + public long getExpForNextLevel() + { + return getStat().getExpForLevel(getLevel() + 1); + } + + @Override + public int getLevel() + { + return getStat().getLevel(); + } + + public int getMaxFed() + { + return getStat().getMaxFeed(); + } + + @Override + public int getCriticalHit(Creature target, Skill skill) + { + return getStat().getCriticalHit(target, skill); + } + + @Override + public int getSkillLevel(int skillId) + { + if (getKnownSkill(skillId) == null) + { + return 0; + } + + final int level = getLevel(); + return level > 70 ? 7 + ((level - 70) / 5) : level / 10; + } + + public void updateRefOwner(Player owner) + { + final int oldOwnerId = getOwner().getObjectId(); + setOwner(owner); + World.removePet(oldOwnerId); + World.addPet(oldOwnerId, this); + } + + public int getInventoryLimit() + { + return NpcConfig.INVENTORY_MAXIMUM_PET; + } + + public void refreshOverloaded() + { + if ((getInventory() == null) || (getOwner() == null)) + { + return; + } + + final int maxLoad = getMaxLoad(); + if (maxLoad > 0) + { + final long weightproc = ((getCurrentLoad() - getBonusWeightPenalty()) * 1000) / maxLoad; + int newWeightPenalty; + if ((weightproc < 500) || getOwner().getDietMode()) + { + newWeightPenalty = 0; + } + else if (weightproc < 666) + { + newWeightPenalty = 1; + } + else if (weightproc < 800) + { + newWeightPenalty = 2; + } + else if (weightproc < 1000) + { + newWeightPenalty = 3; + } + else + { + newWeightPenalty = 4; + } + + if (_curWeightPenalty != newWeightPenalty) + { + _curWeightPenalty = newWeightPenalty; + if (newWeightPenalty > 0) + { + addSkill(SkillData.getInstance().getSkill(4270, newWeightPenalty)); + setOverloaded(getCurrentLoad() >= maxLoad); + } + else + { + removeSkill(getKnownSkill(4270), true); + setOverloaded(false); + } + } + } + } + + @Override + public void updateAndBroadcastStatus(int value) + { + if ((getInventory() != null) && (getOwner() != null)) + { + refreshOverloaded(); + } + + super.updateAndBroadcastStatus(value); + } + + @Override + public boolean isHungry() + { + return _curFed < ((getPetData().getHungryLimit() / 100f) * getPetLevelData().getPetMaxFeed()); + } + + /** + * Verifies if a pet can be controlled by it's owner.
    + * Starving pets cannot be controlled. + * @return {@code true} if the per cannot be controlled + */ + public boolean isUncontrollable() + { + return _curFed <= 0; + } + + @Override + public int getWeapon() + { + final Item weapon = _inventory.getPaperdollItem(Inventory.PAPERDOLL_RHAND); + return weapon != null ? weapon.getId() : 0; + } + + @Override + public int getArmor() + { + final Item weapon = _inventory.getPaperdollItem(Inventory.PAPERDOLL_CHEST); + return weapon != null ? weapon.getId() : 0; + } + + public int getJewel() + { + final Item weapon = _inventory.getPaperdollItem(Inventory.PAPERDOLL_NECK); + return weapon != null ? weapon.getId() : 0; + } + + @Override + public short getSoulShotsPerHit() + { + return getPetLevelData().getPetSoulShot(); + } + + @Override + public short getSpiritShotsPerHit() + { + return getPetLevelData().getPetSpiritShot(); + } + + @Override + public void setName(String name) + { + final Item controlItem = getControlItem(); + if (controlItem != null) + { + if (controlItem.getCustomType2() == (name == null ? 1 : 0)) + { + // Name not set yet. + controlItem.setCustomType2(name != null ? 1 : 0); + controlItem.updateDatabase(); + + final Player owner = getOwner(); + if (owner != null) + { + final InventoryUpdate iu = new InventoryUpdate(); + iu.addModifiedItem(controlItem); + owner.sendInventoryUpdate(iu); + } + } + } + else + { + LOGGER.log(Level.WARNING, "Pet control item null, for pet: " + toString()); + } + + super.setName(name); + } + + public boolean canEatFoodId(int itemId) + { + return _data.getFood().contains(itemId); + } + + @Override + public boolean isPet() + { + return true; + } + + @Override + public Pet asPet() + { + return this; + } + + @Override + public double getRunSpeed() + { + return super.getRunSpeed() * (isUncontrollable() ? 0.5d : 1.0d); + } + + @Override + public double getWalkSpeed() + { + return super.getWalkSpeed() * (isUncontrollable() ? 0.5d : 1.0d); + } + + @Override + public double getMovementSpeedMultiplier() + { + return super.getMovementSpeedMultiplier() * (isUncontrollable() ? 0.5d : 1.0d); + } + + @Override + public double getMoveSpeed() + { + if (isInsideZone(ZoneId.WATER)) + { + return isRunning() ? getSwimRunSpeed() : getSwimWalkSpeed(); + } + + return isRunning() ? getRunSpeed() : getWalkSpeed(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/PetManager.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/PetManager.java new file mode 100644 index 0000000..0b672fa --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/PetManager.java @@ -0,0 +1,355 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.instance; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.util.logging.Level; + +import org.l2jmobius.commons.database.DatabaseFactory; +import org.l2jmobius.commons.threads.ThreadPool; +import org.l2jmobius.gameserver.config.GeneralConfig; +import org.l2jmobius.gameserver.data.holders.PetData; +import org.l2jmobius.gameserver.data.xml.NpcData; +import org.l2jmobius.gameserver.data.xml.PetDataTable; +import org.l2jmobius.gameserver.entity.World; +import org.l2jmobius.gameserver.entity.actor.Npc; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.enums.creature.InstanceType; +import org.l2jmobius.gameserver.entity.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.entity.item.enums.ItemProcessType; +import org.l2jmobius.gameserver.entity.item.instance.Item; +import org.l2jmobius.gameserver.managers.PunishmentManager; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.serverpackets.InventoryUpdate; +import org.l2jmobius.gameserver.network.serverpackets.MagicSkillLaunched; +import org.l2jmobius.gameserver.network.serverpackets.MagicSkillUse; +import org.l2jmobius.gameserver.network.serverpackets.NpcHtmlMessage; +import org.l2jmobius.gameserver.network.serverpackets.StatusUpdate; +import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; + +public class PetManager extends Merchant +{ + /** + * Creates a pet manager. + * @param template the pet manager NPC template. + */ + public PetManager(NpcTemplate template) + { + super(template); + setInstanceType(InstanceType.PetManager); + } + + @Override + public String getHtmlPath(int npcId, int value) + { + String pom = ""; + if (value == 0) + { + pom = Integer.toString(npcId); + } + else + { + pom = npcId + "-" + value; + } + + return "data/html/petmanager/" + pom + ".htm"; + } + + @Override + public void showChatWindow(Player player) + { + String filename = "data/html/petmanager/" + getId() + ".htm"; + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, filename); + html.replace("%objectId%", String.valueOf(getObjectId())); + html.replace("%npcname%", getName()); + player.sendPacket(html); + } + + public static boolean doEvolve(Player player, Npc npc, int itemIdtake, int itemIdgive, int petminLevel) + { + if ((itemIdtake == 0) || (itemIdgive == 0) || (petminLevel == 0)) + { + return false; + } + + if (!player.hasPet()) + { + return false; + } + + final Pet currentPet = player.getSummon().asPet(); + if (currentPet.isAlikeDead()) + { + PunishmentManager.handleIllegalPlayerAction(player, player + " tried to use death pet exploit!", GeneralConfig.DEFAULT_PUNISH); + return false; + } + + Item item = null; + final PetData oldData = PetDataTable.getInstance().getPetDataByItemId(itemIdtake); + if (oldData == null) + { + return false; + } + + final int oldnpcID = oldData.getNpcId(); + if ((currentPet.getStat().getLevel() < petminLevel) || (currentPet.getId() != oldnpcID)) + { + return false; + } + + final PetData petData = PetDataTable.getInstance().getPetDataByItemId(itemIdgive); + if (petData == null) + { + return false; + } + + final int npcID = petData.getNpcId(); + if (npcID == 0) + { + return false; + } + + long petexp = currentPet.getStat().getExp(); + final String oldname = currentPet.getName(); + final int oldX = currentPet.getX(); + final int oldY = currentPet.getY(); + final int oldZ = currentPet.getZ(); + final NpcTemplate npcTemplate = NpcData.getInstance().getTemplate(npcID); + currentPet.unSummon(player); + + // Deleting old pet item. + currentPet.destroyControlItem(player, true); + item = player.getInventory().addItem(ItemProcessType.RESTORE, itemIdgive, 1, player, npc); + + // Summoning new pet + final Pet petSummon = Pet.spawnPet(npcTemplate, player, item); + if (petSummon == null) + { + return false; + } + + // Fix for non-linear baby pet exp. + final long _minimumexp = petSummon.getStat().getExpForLevel(petminLevel); + if (petexp < _minimumexp) + { + petexp = _minimumexp; + } + + petSummon.getStat().addExp(petexp); + petSummon.setCurrentHp(petSummon.getMaxHp()); + petSummon.setCurrentMp(petSummon.getMaxMp()); + petSummon.setCurrentFed(petSummon.getMaxFed()); + petSummon.setTitle(player.getName()); + petSummon.setName(oldname); + petSummon.setRunning(); + petSummon.storeMe(); + + player.setPet(petSummon); + + player.sendPacket(new MagicSkillUse(npc, 2046, 1, 1000, 600000)); + player.sendPacket(SystemMessageId.SUMMONING_YOUR_PET); + + // World.storeObject(petSummon); + petSummon.spawnMe(oldX, oldY, oldZ); + petSummon.startFeed(); + item.setEnchantLevel(petSummon.getLevel()); + + ThreadPool.schedule(new EvolveFinalizer(player, petSummon), 900); + if (petSummon.getCurrentFed() <= 0) + { + ThreadPool.schedule(new EvolveFeedWait(player, petSummon), 60000); + } + else + { + petSummon.startFeed(); + } + + return true; + } + + public static boolean doRestore(Player player, Npc npc, int itemIdtake, int itemIdgive, int petminLevel) + { + if ((itemIdtake == 0) || (itemIdgive == 0) || (petminLevel == 0)) + { + return false; + } + + final Item item = player.getInventory().getItemByItemId(itemIdtake); + if (item == null) + { + return false; + } + + final PetData oldData = PetDataTable.getInstance().getPetDataByItemId(itemIdtake); + if (oldData == null) + { + return false; + } + + final PetData petData = PetDataTable.getInstance().getPetDataByItemId(itemIdgive); + if (petData == null) + { + return false; + } + + final int npcId = petData.getNpcId(); + if (npcId == 0) + { + return false; + } + + final int oldpetlvl = item.getEnchantLevel() < petminLevel ? petminLevel : item.getEnchantLevel(); + final NpcTemplate npcTemplate = NpcData.getInstance().getTemplate(npcId); + + // Deleting old pet item. + final Item removedItem = player.getInventory().destroyItem(ItemProcessType.RESTORE, item, player, npc); + final SystemMessage sm = new SystemMessage(SystemMessageId.S1_HAS_DISAPPEARED); + sm.addItemName(removedItem); + player.sendPacket(sm); + + // Give new pet item. + final Item addedItem = player.getInventory().addItem(ItemProcessType.RESTORE, itemIdgive, 1, player, npc); + + // Summoning new pet + final Pet petSummon = Pet.spawnPet(npcTemplate, player, addedItem); + if (petSummon == null) + { + return false; + } + + final long _maxexp = petSummon.getStat().getExpForLevel(oldpetlvl); + petSummon.getStat().addExp(_maxexp); + petSummon.setCurrentHp(petSummon.getMaxHp()); + petSummon.setCurrentMp(petSummon.getMaxMp()); + petSummon.setCurrentFed(petSummon.getMaxFed()); + petSummon.setTitle(player.getName()); + petSummon.setRunning(); + petSummon.storeMe(); + + player.setPet(petSummon); + + player.sendPacket(new MagicSkillUse(npc, 2046, 1, 1000, 600000)); + player.sendPacket(SystemMessageId.SUMMONING_YOUR_PET); + + // World.storeObject(petSummon); + petSummon.spawnMe(player.getX(), player.getY(), player.getZ()); + petSummon.startFeed(); + addedItem.setEnchantLevel(petSummon.getLevel()); + + // Inventory update + final InventoryUpdate iu = new InventoryUpdate(); + iu.addRemovedItem(removedItem); + player.sendInventoryUpdate(iu); + + final StatusUpdate su = new StatusUpdate(player); + su.addAttribute(StatusUpdate.CUR_LOAD, player.getCurrentLoad()); + player.sendPacket(su); + + player.broadcastUserInfo(); + + World.removeObject(removedItem); + + ThreadPool.schedule(new EvolveFinalizer(player, petSummon), 900); + if (petSummon.getCurrentFed() <= 0) + { + ThreadPool.schedule(new EvolveFeedWait(player, petSummon), 60000); + } + else + { + petSummon.startFeed(); + } + + // Pet control item no longer exists, delete the pet from the db. + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement("DELETE FROM pets WHERE item_obj_id=?")) + { + ps.setInt(1, removedItem.getObjectId()); + ps.execute(); + } + catch (Exception e) + { + // Ignore. + } + + return true; + } + + public static class EvolveFeedWait implements Runnable + { + private final Player _player; + private final Pet _petSummon; + + EvolveFeedWait(Player player, Pet petSummon) + { + _player = player; + _petSummon = petSummon; + } + + @Override + public void run() + { + try + { + if (_petSummon.getCurrentFed() <= 0) + { + _petSummon.unSummon(_player); + } + else + { + _petSummon.startFeed(); + } + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "run: ", e); + } + } + } + + public static class EvolveFinalizer implements Runnable + { + private final Player _player; + private final Pet _petSummon; + + EvolveFinalizer(Player player, Pet petSummon) + { + _player = player; + _petSummon = petSummon; + } + + @Override + public void run() + { + try + { + _player.sendPacket(new MagicSkillLaunched(_player, 2046, 1)); + _petSummon.setFollowStatus(true); + _petSummon.setShowSummonAnimation(false); + } + catch (Throwable e) + { + LOGGER.log(Level.WARNING, "run: ", e); + } + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/QuestGuard.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/QuestGuard.java new file mode 100644 index 0000000..e65c028 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/QuestGuard.java @@ -0,0 +1,107 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.actor.instance; + +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.enums.creature.InstanceType; +import org.l2jmobius.gameserver.entity.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.mechanics.events.EventDispatcher; +import org.l2jmobius.gameserver.mechanics.events.EventType; +import org.l2jmobius.gameserver.mechanics.events.holders.actor.npc.attackable.OnAttackableAttack; +import org.l2jmobius.gameserver.mechanics.events.holders.actor.npc.attackable.OnAttackableKill; +import org.l2jmobius.gameserver.mechanics.skill.Skill; + +/** + * This class extends Guard class for quests, that require tracking of onAttack and onKill events from monsters' attacks. + * @author GKR + */ +public class QuestGuard extends Guard +{ + private boolean _isAutoAttackable = true; + private boolean _isPassive = false; + + /** + * Creates a quest guard. + * @param template the quest guard NPC template + */ + public QuestGuard(NpcTemplate template) + { + super(template); + setInstanceType(InstanceType.QuestGuard); + setCanReturnToSpawnPoint(false); + } + + @Override + public void addDamage(Creature attacker, int damage, Skill skill) + { + super.addDamage(attacker, damage, skill); + + if (attacker.isAttackable() && EventDispatcher.getInstance().hasListener(EventType.ON_ATTACKABLE_ATTACK, this)) + { + EventDispatcher.getInstance().notifyEventAsync(new OnAttackableAttack(null, this, damage, skill, false), this); + } + } + + @Override + public boolean doDie(Creature killer) + { + // Kill the Npc (the corpse disappeared after 7 seconds). + if (!super.doDie(killer)) + { + return false; + } + + // Delayed notification + if (killer.isAttackable() && EventDispatcher.getInstance().hasListener(EventType.ON_ATTACKABLE_KILL, this)) + { + EventDispatcher.getInstance().notifyEventAsyncDelayed(new OnAttackableKill(null, this, false), this, _onKillDelay); + } + + return true; + } + + @Override + public void addDamageHate(Creature attacker, long damage, long aggro) + { + if (!_isPassive && !attacker.isPlayer()) + { + super.addDamageHate(attacker, damage, aggro); + } + } + + public void setPassive(boolean value) + { + _isPassive = value; + } + + @Override + public boolean isAutoAttackable(Creature attacker) + { + return _isAutoAttackable && !attacker.isPlayer(); + } + + @Override + public void setAutoAttackable(boolean value) + { + _isAutoAttackable = value; + } + + public boolean isPassive() + { + return _isPassive; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/RaceManager.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/RaceManager.java new file mode 100644 index 0000000..d9c5470 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/RaceManager.java @@ -0,0 +1,382 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.instance; + +import java.util.List; +import java.util.Locale; + +import org.l2jmobius.commons.util.StringUtil; +import org.l2jmobius.gameserver.config.GeneralConfig; +import org.l2jmobius.gameserver.entity.actor.Npc; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.entity.item.enums.ItemProcessType; +import org.l2jmobius.gameserver.entity.item.instance.Item; +import org.l2jmobius.gameserver.managers.IdManager; +import org.l2jmobius.gameserver.managers.games.MonsterRaceManager; +import org.l2jmobius.gameserver.managers.games.MonsterRaceManager.HistoryInfo; +import org.l2jmobius.gameserver.managers.games.MonsterRaceManager.RaceState; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.serverpackets.ActionFailed; +import org.l2jmobius.gameserver.network.serverpackets.NpcHtmlMessage; +import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; + +public class RaceManager extends Npc +{ + protected static final int[] TICKET_PRICES = + { + 100, + 500, + 1000, + 5000, + 10000, + 20000, + 50000, + 100000 + }; + + public RaceManager(NpcTemplate template) + { + super(template); + } + + @Override + public void onBypassFeedback(Player player, String command) + { + if (command.startsWith("BuyTicket")) + { + if (!GeneralConfig.ALLOW_RACE || (MonsterRaceManager.getInstance().getCurrentRaceState() != RaceState.ACCEPTING_BETS)) + { + player.sendPacket(SystemMessageId.MONSTER_RACE_TICKETS_ARE_NO_LONGER_AVAILABLE); + super.onBypassFeedback(player, "Chat 0"); + return; + } + + int val = Integer.parseInt(command.substring(10)); + if (val == 0) + { + player.setRaceTicket(0, 0); + player.setRaceTicket(1, 0); + } + + if (((val == 10) && (player.getRaceTicket(0) == 0)) || ((val == 20) && (player.getRaceTicket(0) == 0) && (player.getRaceTicket(1) == 0))) + { + val = 0; + } + + String search; + String replace; + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + if (val < 10) + { + html.setFile(player, getHtmlPath(getId(), 2)); + for (int i = 0; i < 8; i++) + { + int n = i + 1; + search = "Mob" + n; + html.replace(search, MonsterRaceManager.getInstance().getMonsters()[i].getTemplate().getName()); + } + + search = "No1"; + if (val == 0) + { + html.replace(search, ""); + } + else + { + html.replace(search, val); + player.setRaceTicket(0, val); + } + } + else if (val < 20) + { + if (player.getRaceTicket(0) == 0) + { + return; + } + + html.setFile(player, getHtmlPath(getId(), 3)); + html.replace("0place", player.getRaceTicket(0)); + search = "Mob1"; + replace = MonsterRaceManager.getInstance().getMonsters()[player.getRaceTicket(0) - 1].getTemplate().getName(); + html.replace(search, replace); + search = "0adena"; + if (val == 10) + { + html.replace(search, ""); + } + else + { + html.replace(search, TICKET_PRICES[val - 11]); + player.setRaceTicket(1, val - 10); + } + } + else if (val == 20) + { + if ((player.getRaceTicket(0) == 0) || (player.getRaceTicket(1) == 0)) + { + return; + } + + html.setFile(player, getHtmlPath(getId(), 4)); + html.replace("0place", player.getRaceTicket(0)); + search = "Mob1"; + replace = MonsterRaceManager.getInstance().getMonsters()[player.getRaceTicket(0) - 1].getTemplate().getName(); + html.replace(search, replace); + search = "0adena"; + int price = TICKET_PRICES[player.getRaceTicket(1) - 1]; + html.replace(search, price); + search = "0tax"; + int tax = 0; + html.replace(search, tax); + search = "0total"; + int total = price + tax; + html.replace(search, total); + } + else + { + if ((player.getRaceTicket(0) == 0) || (player.getRaceTicket(1) == 0)) + { + return; + } + + int priceId = player.getRaceTicket(1); + if (!player.reduceAdena(ItemProcessType.FEE, TICKET_PRICES[priceId - 1], this, true)) + { + return; + } + + int ticket = player.getRaceTicket(0); + + player.setRaceTicket(0, 0); + player.setRaceTicket(1, 0); + Item item = new Item(IdManager.getInstance().getNextId(), 4443); + item.setCount(1); + item.setEnchantLevel(MonsterRaceManager.getInstance().getRaceNumber()); + item.setCustomType1(ticket); + item.setCustomType2(TICKET_PRICES[priceId - 1] / 100); + player.addItem(ItemProcessType.QUEST, item, player, false); + final SystemMessage msg = new SystemMessage(SystemMessageId.ACQUIRED_S1_S2); + msg.addInt(MonsterRaceManager.getInstance().getRaceNumber()); + msg.addItemName(4443); + player.sendPacket(msg); + + // Refresh lane bet. + MonsterRaceManager.getInstance().setBetOnLane(ticket, TICKET_PRICES[priceId - 1], true); + super.onBypassFeedback(player, "Chat 0"); + return; + } + + html.replace("1race", MonsterRaceManager.getInstance().getRaceNumber()); + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); + } + else if (command.equals("ShowOdds")) + { + if (!GeneralConfig.ALLOW_RACE || (MonsterRaceManager.getInstance().getCurrentRaceState() == RaceState.ACCEPTING_BETS)) + { + player.sendPacket(SystemMessageId.MONSTER_RACE_PAYOUT_INFORMATION_IS_NOT_AVAILABLE_WHILE_TICKETS_ARE_BEING_SOLD); + super.onBypassFeedback(player, "Chat 0"); + return; + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 5)); + for (int i = 0; i < 8; i++) + { + final int n = i + 1; + html.replace("Mob" + n, MonsterRaceManager.getInstance().getMonsters()[i].getTemplate().getName()); + + // Odd + final double odd = MonsterRaceManager.getInstance().getOdds().get(i); + html.replace("Odd" + n, (odd > 0D) ? String.format(Locale.ENGLISH, "%.1f", odd) : "&$804;"); + } + + html.replace("1race", MonsterRaceManager.getInstance().getRaceNumber()); + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); + } + else if (command.equals("ShowInfo")) + { + if (!GeneralConfig.ALLOW_RACE) + { + return; + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 6)); + for (int i = 0; i < 8; i++) + { + int n = i + 1; + String search = "Mob" + n; + html.replace(search, MonsterRaceManager.getInstance().getMonsters()[i].getTemplate().getName()); + } + + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); + } + else if (command.equals("ShowTickets")) + { + if (!GeneralConfig.ALLOW_RACE) + { + super.onBypassFeedback(player, "Chat 0"); + return; + } + + // Generate data. + final StringBuilder sb = new StringBuilder(); + + // Retrieve player's tickets. + for (Item ticket : player.getInventory().getAllItemsByItemId(4443)) + { + // Don't list current race tickets. + if (ticket.getEnchantLevel() == MonsterRaceManager.getInstance().getRaceNumber()) + { + continue; + } + + StringUtil.append(sb, "", String.valueOf(ticket.getEnchantLevel()), " Race Number", String.valueOf(ticket.getCustomType1()), " Number", String.valueOf(ticket.getCustomType2() * 100), " Adena"); + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 7)); + html.replace("%tickets%", sb.toString()); + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); + } + else if (command.startsWith("ShowTicket")) + { + // Retrieve ticket objectId. + final int val = Integer.parseInt(command.substring(11)); + if (!GeneralConfig.ALLOW_RACE || (val == 0)) + { + super.onBypassFeedback(player, "Chat 0"); + return; + } + + // Retrieve ticket on player's inventory. + final Item ticket = player.getInventory().getItemByObjectId(val); + if (ticket == null) + { + super.onBypassFeedback(player, "Chat 0"); + return; + } + + final int raceId = ticket.getEnchantLevel(); + + // Retrieve HistoryInfo for that race. + final HistoryInfo info = MonsterRaceManager.getInstance().getHistory().get(raceId - 1); + if (info == null) + { + super.onBypassFeedback(player, "Chat 0"); + return; + } + + final int lane = ticket.getCustomType1(); + final int bet = ticket.getCustomType2() * 100; + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 8)); + html.replace("%raceId%", raceId); + html.replace("%lane%", lane); + html.replace("%bet%", bet); + html.replace("%firstLane%", info.getFirst() + 1); + html.replace("%odd%", (lane == (info.getFirst() + 1)) ? String.format(Locale.ENGLISH, "%.2f", info.getOddRate()) : "0.01"); + html.replace("%objectId%", getObjectId()); + html.replace("%ticketObjectId%", val); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); + } + else if (command.startsWith("CalculateWin")) + { + // Retrieve ticket objectId. + final int val = Integer.parseInt(command.substring(13)); + if (!GeneralConfig.ALLOW_RACE || (val == 0)) + { + super.onBypassFeedback(player, "Chat 0"); + return; + } + + // Delete ticket on player's inventory. + final Item ticket = player.getInventory().getItemByObjectId(val); + if (ticket == null) + { + super.onBypassFeedback(player, "Chat 0"); + return; + } + + final int raceId = ticket.getEnchantLevel(); + + // Retrieve HistoryInfo for that race. + final HistoryInfo info = MonsterRaceManager.getInstance().getHistory().get(raceId - 1); + if (info == null) + { + super.onBypassFeedback(player, "Chat 0"); + return; + } + + // Destroy the ticket. + if (player.destroyItem(ItemProcessType.FEE, ticket, this, true)) + { + final int lane = ticket.getCustomType1(); + final int bet = ticket.getCustomType2() * 100; + player.addAdena(ItemProcessType.REWARD, (int) (bet * ((lane == (info.getFirst() + 1)) ? info.getOddRate() : 0.01)), this, true); + } + + super.onBypassFeedback(player, "Chat 0"); + return; + } + else if (command.equals("ViewHistory")) + { + if (!GeneralConfig.ALLOW_RACE) + { + super.onBypassFeedback(player, "Chat 0"); + return; + } + + // Generate data. + final StringBuilder sb = new StringBuilder(); + + // Use whole history, pickup from 'last element' and stop at 'latest element - 7'. + final List history = MonsterRaceManager.getInstance().getHistory(); + for (int i = history.size() - 1; i >= Math.max(0, history.size() - 7); i--) + { + final HistoryInfo info = history.get(i); + StringUtil.append(sb, "", String.valueOf(info.getRaceId()), " th", String.valueOf(info.getFirst() + 1), " Lane ", String.valueOf(info.getSecond() + 1), " Lane", String.format(Locale.ENGLISH, "%.2f", info.getOddRate()), " Times"); + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 9)); + html.replace("%infos%", sb.toString()); + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); + } + else + { + super.onBypassFeedback(player, command); + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/RaidBoss.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/RaidBoss.java new file mode 100644 index 0000000..325a03d --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/RaidBoss.java @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.instance; + +import org.l2jmobius.commons.util.Rnd; +import org.l2jmobius.gameserver.config.PlayerConfig; +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.enums.creature.InstanceType; +import org.l2jmobius.gameserver.entity.actor.enums.npc.RaidBossStatus; +import org.l2jmobius.gameserver.entity.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.entity.groups.Party; +import org.l2jmobius.gameserver.managers.RaidBossPointsManager; +import org.l2jmobius.gameserver.managers.RaidBossSpawnManager; +import org.l2jmobius.gameserver.mechanics.olympiad.Hero; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; + +/** + * This class manages all RaidBoss.
    + * In a group mob, there are one master called RaidBoss and several slaves called Minions. + */ +public class RaidBoss extends Monster +{ + private RaidBossStatus _raidStatus; + private boolean _useRaidCurse = true; + + /** + * Creates a raid boss. + * @param template the raid boss template + */ + public RaidBoss(NpcTemplate template) + { + super(template); + setInstanceType(InstanceType.RaidBoss); + setIsRaid(true); + setLethalable(false); + } + + @Override + public void onSpawn() + { + setRandomWalking(false); + super.onSpawn(); + } + + @Override + public boolean doDie(Creature killer) + { + if (!super.doDie(killer)) + { + return false; + } + + if ((killer != null) && killer.isPlayable()) + { + final SystemMessage msg = new SystemMessage(SystemMessageId.CONGRATULATIONS_YOUR_RAID_WAS_SUCCESSFUL); + final Player player = killer.asPlayer(); + final Party party = player.getParty(); + if (party != null) + { + party.broadcastPacket(msg); + for (Player member : party.getMembers()) + { + RaidBossPointsManager.getInstance().addPoints(member, getId(), (getLevel() / 2) + Rnd.get(-5, 5)); + if (member.isNoble()) + { + Hero.getInstance().setRBkilled(member.getObjectId(), getId()); + } + } + } + else + { + player.sendPacket(msg); + RaidBossPointsManager.getInstance().addPoints(player, getId(), (getLevel() / 2) + Rnd.get(-5, 5)); + if (player.isNoble()) + { + Hero.getInstance().setRBkilled(player.getObjectId(), getId()); + } + } + } + + RaidBossSpawnManager.getInstance().updateStatus(this, true); + return true; + } + + public void setRaidStatus(RaidBossStatus status) + { + _raidStatus = status; + } + + public RaidBossStatus getRaidStatus() + { + return _raidStatus; + } + + @Override + public float getVitalityPoints(int level, long damage) + { + return -super.getVitalityPoints(level, damage) / 100; + } + + @Override + public boolean useVitalityRate() + { + return PlayerConfig.RAIDBOSS_USE_VITALITY; + } + + public void setUseRaidCurse(boolean value) + { + _useRaidCurse = value; + } + + @Override + public boolean giveRaidCurse() + { + return _useRaidCurse; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/RiftInvader.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/RiftInvader.java new file mode 100644 index 0000000..ece4452 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/RiftInvader.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.instance; + +import org.l2jmobius.gameserver.entity.actor.enums.creature.InstanceType; +import org.l2jmobius.gameserver.entity.actor.templates.NpcTemplate; + +public class RiftInvader extends Monster +{ + // Not longer needed since rift monster targeting control now is handled by the room zones for any mob. + public RiftInvader(NpcTemplate template) + { + super(template); + setInstanceType(InstanceType.RiftInvader); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/SchemeBuffer.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/SchemeBuffer.java new file mode 100644 index 0000000..ecc65be --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/SchemeBuffer.java @@ -0,0 +1,841 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.instance; + +import java.text.NumberFormat; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Map.Entry; +import java.util.StringTokenizer; + +import org.l2jmobius.commons.util.StringUtil; +import org.l2jmobius.gameserver.config.PlayerConfig; +import org.l2jmobius.gameserver.config.custom.SchemeBufferConfig; +import org.l2jmobius.gameserver.data.SchemeBufferTable; +import org.l2jmobius.gameserver.data.xml.SkillData; +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.Npc; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.Summon; +import org.l2jmobius.gameserver.entity.actor.holders.npc.BuffSkillHolder; +import org.l2jmobius.gameserver.entity.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.entity.item.enums.ItemProcessType; +import org.l2jmobius.gameserver.mechanics.skill.Skill; +import org.l2jmobius.gameserver.network.serverpackets.NpcHtmlMessage; +import org.l2jmobius.gameserver.util.HtmlUtil; + +/** + * Scheme Buffer NPC handler supporting scheme management and manual buff casting.
    + * Provides extra pages (Buffs/Dances/Songs/etc.) and auto-buff presets without exposing internal groups in the edit UI. + *
      + *
    • Creates, edits, deletes and casts player schemes.
    • + *
    • Manually casts skills by category with pagination and target selection.
    • + *
    • Applies class-based auto-buffs using MAGE_GROUP / FIGHTER_GROUP.
    • + *
    + * @author Mobius, BazookaRpm + */ +public class SchemeBuffer extends Npc +{ + // Constants. + private static final int PAGE_LIMIT = 6; + private static final int SCHEME_NAME_MAX_LENGTH = 14; + private static final int TYPES_PER_ROW = 4; + private static final String AUTO_BUFF_MAGE_GROUP = "MAGE_GROUP"; + private static final String AUTO_BUFF_FIGHTER_GROUP = "FIGHTER_GROUP"; + + // HTML layout. + private static final int HTML_WIDTH = 280; + private static final int HTML_HALF_WIDTH = 140; + private static final int HTML_NAV_CELL_WIDTH = 70; + private static final int HTML_PAGE_CELL_WIDTH = 100; + private static final int HTML_ICON_CELL_SIZE = 40; + private static final int HTML_ICON_SIZE = 32; + private static final int HTML_NAME_CELL_WIDTH = 190; + + /** + * Creates a SchemeBuffer instance. + * @param template + */ + public SchemeBuffer(NpcTemplate template) + { + super(template); + } + + /** + * Handles Scheme Buffer bypass commands. + * @param player + * @param commandValue + */ + @Override + public void onBypassFeedback(Player player, String commandValue) + { + if ((player == null) || (commandValue == null) || commandValue.isEmpty()) + { + return; + } + + final SchemeBufferTable schemeBufferTable = SchemeBufferTable.getInstance(); + final SkillData skillData = SkillData.getInstance(); + + final StringTokenizer tokenizer = new StringTokenizer(commandValue.replace("createscheme ", "createscheme;"), ";"); + if (!tokenizer.hasMoreTokens()) + { + return; + } + + final String currentCommand = tokenizer.nextToken(); + + if (currentCommand.startsWith("menu")) + { + showMainMenu(player); + return; + } + else if (currentCommand.startsWith("cleanup")) + { + player.stopAllEffects(); + + final Summon summon = player.getSummon(); + if (summon != null) + { + summon.stopAllEffects(); + } + + showMainMenu(player); + return; + } + else if (currentCommand.startsWith("heal")) + { + player.setCurrentHpMp(player.getMaxHp(), player.getMaxMp()); + player.setCurrentCp(player.getMaxCp()); + + final Summon summon = player.getSummon(); + if (summon != null) + { + summon.setCurrentHpMp(summon.getMaxHp(), summon.getMaxMp()); + } + + showMainMenu(player); + return; + } + else if (currentCommand.startsWith("support")) + { + showGiveBuffsWindow(player); + return; + } + else if (currentCommand.startsWith("givebuffs")) + { + if (tokenizer.countTokens() < 2) + { + return; + } + + final String schemeName = tokenizer.nextToken(); + + // NOTE: Cost from bypass is client-controlled. Always recalculate server-side. + // Keep consuming the token to preserve bypass format. + tokenizer.nextToken(); + + final List scheme = schemeBufferTable.getScheme(player.getObjectId(), schemeName); + final int cost = getFee(scheme); + + Creature target = player; + if (tokenizer.hasMoreTokens() && "pet".equalsIgnoreCase(tokenizer.nextToken())) + { + target = player.getSummon(); + } + + if (target == null) + { + player.sendMessage("You don't have a pet."); + return; + } + + if ((cost == 0) || ((SchemeBufferConfig.BUFFER_ITEM_ID == 57) && player.reduceAdena(ItemProcessType.FEE, cost, this, true)) || ((SchemeBufferConfig.BUFFER_ITEM_ID != 57) && player.destroyItemByItemId(ItemProcessType.FEE, SchemeBufferConfig.BUFFER_ITEM_ID, cost, player, true))) + { + for (int skillId : scheme) + { + final BuffSkillHolder holder = schemeBufferTable.getAvailableBuff(skillId); + if (holder == null) + { + continue; + } + + final Skill skill = skillData.getSkill(skillId, holder.getLevel()); + if (skill != null) + { + skill.applyEffects(this, target); + } + } + } + return; + } + else if (currentCommand.startsWith("editschemes")) + { + if (tokenizer.countTokens() < 3) + { + return; + } + + final String groupType = tokenizer.nextToken(); + final String schemeName = tokenizer.nextToken(); + + final int page; + try + { + page = Integer.parseInt(tokenizer.nextToken()); + } + catch (NumberFormatException e) + { + return; + } + + showEditSchemeWindow(player, groupType, schemeName, page); + return; + } + else if (currentCommand.startsWith("skill")) + { + if (tokenizer.countTokens() < 4) + { + return; + } + + final String groupType = tokenizer.nextToken(); + final String schemeName = tokenizer.nextToken(); + + final int skillId; + final int page; + try + { + skillId = Integer.parseInt(tokenizer.nextToken()); + page = Integer.parseInt(tokenizer.nextToken()); + } + catch (NumberFormatException e) + { + return; + } + + final Map> playerSchemes = schemeBufferTable.getPlayerSchemes(player.getObjectId()); + if ((playerSchemes == null) || !playerSchemes.containsKey(schemeName)) + { + player.sendMessage("Invalid scheme name: " + schemeName + "."); + showGiveBuffsWindow(player); + return; + } + + final List schemeSkills = schemeBufferTable.getScheme(player.getObjectId(), schemeName); + + if (currentCommand.startsWith("skillselect") && !schemeName.equalsIgnoreCase("none")) + { + final Skill skill = skillData.getSkill(skillId, 1); + if (skill == null) + { + showEditSchemeWindow(player, groupType, schemeName, page); + return; + } + + final int totalBuffs = schemeSkills.size(); + final int currentDanceSongCount = getCountOf(schemeSkills, true); + final boolean isDanceOrSong = skill.isDance(); + final int maxCount = player.getStat().getMaxBuffCount(); + + if (totalBuffs >= maxCount) + { + player.sendMessage("This scheme has reached the maximum amount of buffs."); + } + else if (isDanceOrSong && (currentDanceSongCount >= PlayerConfig.DANCES_MAX_AMOUNT)) + { + player.sendMessage("You cannot add more than " + PlayerConfig.DANCES_MAX_AMOUNT + " songs/dances to this scheme."); + } + else if (!schemeSkills.contains(skillId)) + { + schemeBufferTable.addSkillToScheme(player.getObjectId(), schemeName, skillId); + } + } + else if (currentCommand.startsWith("skillunselect")) + { + schemeBufferTable.removeSkillFromScheme(player.getObjectId(), schemeName, skillId); + } + + showEditSchemeWindow(player, groupType, schemeName, page); + return; + } + else if (currentCommand.startsWith("createscheme")) + { + if (!tokenizer.hasMoreTokens()) + { + player.sendMessage("Scheme's name must contain up to " + SCHEME_NAME_MAX_LENGTH + " chars."); + return; + } + + final String schemeName = tokenizer.nextToken().trim(); + if (schemeName.isEmpty() || (schemeName.length() > SCHEME_NAME_MAX_LENGTH)) + { + player.sendMessage("Scheme's name must contain up to " + SCHEME_NAME_MAX_LENGTH + " chars."); + return; + } + + if (!StringUtil.isAlphaNumeric(schemeName.replace(" ", "").replace(".", "").replace(",", "").replace("-", "").replace("+", "").replace("!", "").replace("?", ""))) + { + player.sendMessage("Please use plain alphanumeric characters."); + return; + } + + final Map> playerSchemes = schemeBufferTable.getPlayerSchemes(player.getObjectId()); + if (playerSchemes != null) + { + if (playerSchemes.size() >= SchemeBufferConfig.BUFFER_MAX_SCHEMES) + { + player.sendMessage("Maximum schemes amount is already reached."); + return; + } + if (playerSchemes.containsKey(schemeName)) + { + player.sendMessage("The scheme name already exists: " + schemeName + "."); + return; + } + } + + schemeBufferTable.setScheme(player.getObjectId(), schemeName, new ArrayList<>()); + showGiveBuffsWindow(player); + return; + } + else if (currentCommand.startsWith("deletescheme")) + { + if (!tokenizer.hasMoreTokens()) + { + player.sendMessage("This scheme name is invalid."); + showGiveBuffsWindow(player); + return; + } + + final String schemeName = tokenizer.nextToken(); + if (schemeName.isEmpty()) + { + player.sendMessage("This scheme name is invalid."); + showGiveBuffsWindow(player); + return; + } + + schemeBufferTable.deleteScheme(player.getObjectId(), schemeName); + + showGiveBuffsWindow(player); + return; + } + else if (currentCommand.startsWith("manual")) + { + final String category = tokenizer.hasMoreTokens() ? tokenizer.nextToken() : "Buffs"; + + final int page; + try + { + page = tokenizer.hasMoreTokens() ? Integer.parseInt(tokenizer.nextToken()) : 1; + } + catch (NumberFormatException e) + { + showMainMenu(player); + return; + } + + final String targetType = tokenizer.hasMoreTokens() ? tokenizer.nextToken() : "me"; + showManualWindow(player, category, page, targetType); + return; + } + else if (currentCommand.startsWith("castbuff")) + { + if (tokenizer.countTokens() < 4) + { + return; + } + + try + { + final int skillId = Integer.parseInt(tokenizer.nextToken()); + final String category = tokenizer.nextToken(); + final int page = Integer.parseInt(tokenizer.nextToken()); + final String targetType = tokenizer.nextToken(); + + final Creature target = "pet".equalsIgnoreCase(targetType) ? player.getSummon() : player; + if (target == null) + { + player.sendMessage("You don't have a pet."); + showManualWindow(player, category, page, targetType); + return; + } + + final BuffSkillHolder holder = schemeBufferTable.getAvailableBuff(category, skillId); + if (holder != null) + { + final Skill skill = skillData.getSkill(skillId, holder.getLevel()); + if (skill != null) + { + skill.applyEffects(this, target); + } + } + + showManualWindow(player, category, page, targetType); + } + catch (NumberFormatException e) + { + return; + } + return; + } + else if (currentCommand.startsWith("autobuff")) + { + final String targetType = tokenizer.hasMoreTokens() ? tokenizer.nextToken() : "me"; + applyAutoBuff(player, targetType); + return; + } + + super.onBypassFeedback(player, commandValue); + } + + /** + * Returns the HTML path used by this NPC. + * @param npcId + * @param value + * @return the HTML file path. + */ + @Override + public String getHtmlPath(int npcId, int value) + { + return "data/html/mods/SchemeBuffer/" + ((value == 0) ? Integer.toString(npcId) : (npcId + "-" + value)) + ".htm"; + } + + /** + * Shows the main menu window. + * @param player + */ + private void showMainMenu(Player player) + { + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 0)); + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + } + + /** + * Shows the scheme list and actions window. + * @param player + */ + private void showGiveBuffsWindow(Player player) + { + final StringBuilder htmlBuilder = new StringBuilder(200); + final Map> playerSchemes = SchemeBufferTable.getInstance().getPlayerSchemes(player.getObjectId()); + + if ((playerSchemes == null) || playerSchemes.isEmpty()) + { + htmlBuilder.append("You haven't defined any scheme."); + } + else + { + for (Entry> scheme : playerSchemes.entrySet()) + { + final int cost = getFee(scheme.getValue()); + htmlBuilder.append("").append(scheme.getKey()).append(" [").append(scheme.getValue().size()).append(" skill(s)]").append((cost > 0) ? (" - cost: " + NumberFormat.getInstance(Locale.ENGLISH).format(cost)) : "").append(""); + + htmlBuilder.append("Use on Me | "); + htmlBuilder.append("Use on Pet | "); + htmlBuilder.append("Edit | "); + htmlBuilder.append("Delete
    "); + } + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 1)); + html.replace("%schemes%", htmlBuilder.toString()); + html.replace("%max_schemes%", SchemeBufferConfig.BUFFER_MAX_SCHEMES); + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + } + + /** + * Shows the scheme edit window for adding and removing skills. + * @param player + * @param groupType + * @param schemeName + * @param page + */ + private void showEditSchemeWindow(Player player, String groupType, String schemeName, int page) + { + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + final List schemeSkills = SchemeBufferTable.getInstance().getScheme(player.getObjectId(), schemeName); + + html.setFile(player, getHtmlPath(getId(), 2)); + html.replace("%schemename%", schemeName); + html.replace("%count%", (getCountOf(schemeSkills, false) + getCountOf(schemeSkills, true)) + " / " + player.getStat().getMaxBuffCount() + " buffs"); + html.replace("%typesframe%", getTypesFrame(groupType, schemeName)); + html.replace("%skilllistframe%", getGroupSkillList(player, groupType, schemeName, page)); + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + } + + /** + * Shows manual casting window by category with pagination and target selection. + * @param player + * @param category + * @param pageValue + * @param targetType + */ + private void showManualWindow(Player player, String category, int pageValue, String targetType) + { + final SchemeBufferTable schemeBufferTable = SchemeBufferTable.getInstance(); + final SkillData skillData = SkillData.getInstance(); + + List skillIds = schemeBufferTable.getSkillsIdsByType(category); + if (skillIds.isEmpty()) + { + player.sendMessage("That category doesn't contain any skills."); + return; + } + + final int maxPage = HtmlUtil.countPageNumber(skillIds.size(), PAGE_LIMIT); + int page = pageValue; + if (page > maxPage) + { + page = maxPage; + } + if (page < 1) + { + page = 1; + } + + skillIds = skillIds.subList((page - 1) * PAGE_LIMIT, Math.min(page * PAGE_LIMIT, skillIds.size())); + + final StringBuilder htmlBuilder = new StringBuilder(skillIds.size() * 200); + + htmlBuilder.append(""); + + htmlBuilder.append(""); + + htmlBuilder.append(""); + + htmlBuilder.append("
    "); + if ("me".equalsIgnoreCase(targetType)) + { + htmlBuilder.append("Me"); + } + else + { + htmlBuilder.append("Me"); + } + htmlBuilder.append(""); + if ("pet".equalsIgnoreCase(targetType)) + { + htmlBuilder.append("Pet"); + } + else + { + htmlBuilder.append("Pet"); + } + htmlBuilder.append("
    "); + + int row = 0; + for (int skillId : skillIds) + { + final BuffSkillHolder holder = schemeBufferTable.getAvailableBuff(category, skillId); + if (holder == null) + { + continue; + } + + final Skill skill = skillData.getSkill(skillId, holder.getLevel()); + if (skill == null) + { + continue; + } + + htmlBuilder.append(((row % 2) == 0) ? "" : "
    "); + htmlBuilder.append(""); + htmlBuilder.append(""); + htmlBuilder.append(""); + htmlBuilder.append("
    "); + htmlBuilder.append("").append(holder.getDescription()).append("
    "); + htmlBuilder.append(""); + row++; + } + + htmlBuilder.append(""); + + if (page > 1) + { + htmlBuilder.append(""); + } + else + { + htmlBuilder.append(""); + } + + htmlBuilder.append(""); + + if (page < maxPage) + { + htmlBuilder.append(""); + } + else + { + htmlBuilder.append(""); + } + + htmlBuilder.append("
    PreviousPreviousPage ").append(page).append("NextNext
    "); + htmlBuilder.append("
    Back
    "); + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 3)); + html.replace("%category%", category); + html.replace("%skills%", htmlBuilder.toString()); + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + } + + /** + * Applies auto-buff preset based on player class to player or pet. + * @param player + * @param targetType + */ + private void applyAutoBuff(Player player, String targetType) + { + final SchemeBufferTable schemeBufferTable = SchemeBufferTable.getInstance(); + final SkillData skillData = SkillData.getInstance(); + + final Creature target = "pet".equalsIgnoreCase(targetType) ? player.getSummon() : player; + if (target == null) + { + player.sendMessage("You don't have a pet."); + showMainMenu(player); + return; + } + + final String category = player.isMageClass() ? AUTO_BUFF_MAGE_GROUP : AUTO_BUFF_FIGHTER_GROUP; + final List skillIds = schemeBufferTable.getSkillsIdsByType(category); + + if (skillIds.isEmpty()) + { + player.sendMessage("Auto buff configuration is not available."); + showMainMenu(player); + return; + } + + for (int skillId : skillIds) + { + final BuffSkillHolder holder = schemeBufferTable.getAvailableBuff(category, skillId); + if (holder == null) + { + continue; + } + + final Skill skill = skillData.getSkill(skillId, holder.getLevel()); + if (skill != null) + { + skill.applyEffects(this, target); + } + } + + player.sendMessage("Auto buff applied successfully!"); + showMainMenu(player); + } + + /** + * Builds paginated skill list for scheme edit window. + * @param player + * @param groupType + * @param schemeName + * @param pageValue + * @return the HTML fragment. + */ + private String getGroupSkillList(Player player, String groupType, String schemeName, int pageValue) + { + final SchemeBufferTable schemeBufferTable = SchemeBufferTable.getInstance(); + final SkillData skillData = SkillData.getInstance(); + + List skillIds = schemeBufferTable.getSkillsIdsByType(groupType); + if (skillIds.isEmpty()) + { + return "That group doesn't contain any skills."; + } + + final int maxPage = HtmlUtil.countPageNumber(skillIds.size(), PAGE_LIMIT); + int page = pageValue; + if (page > maxPage) + { + page = maxPage; + } + if (page < 1) + { + page = 1; + } + + skillIds = skillIds.subList((page - 1) * PAGE_LIMIT, Math.min(page * PAGE_LIMIT, skillIds.size())); + + final List schemeSkills = schemeBufferTable.getScheme(player.getObjectId(), schemeName); + final StringBuilder htmlBuilder = new StringBuilder(skillIds.size() * 150); + + int row = 0; + for (int skillId : skillIds) + { + final BuffSkillHolder holder = schemeBufferTable.getAvailableBuff(groupType, skillId); + if (holder == null) + { + continue; + } + + final Skill skill = skillData.getSkill(skillId, 1); + if (skill == null) + { + continue; + } + + htmlBuilder.append(((row % 2) == 0) ? "" : "
    "); + htmlBuilder.append(""); + htmlBuilder.append(""); + + if (schemeSkills.contains(skillId)) + { + htmlBuilder.append(""); + } + else + { + htmlBuilder.append(""); + } + + htmlBuilder.append("
    ").append(holder.getDescription()).append("
    "); + row++; + } + + htmlBuilder.append(""); + + if (page > 1) + { + htmlBuilder.append(""); + } + else + { + htmlBuilder.append(""); + } + + htmlBuilder.append(""); + + if (page < maxPage) + { + htmlBuilder.append(""); + } + else + { + htmlBuilder.append(""); + } + + htmlBuilder.append("
    PreviousPreviousPage ").append(page).append("NextNext
    "); + return htmlBuilder.toString(); + } + + /** + * Builds the category selector frame for scheme editing. + * @param groupType + * @param schemeName + * @return the HTML fragment. + */ + private static String getTypesFrame(String groupType, String schemeName) + { + final StringBuilder htmlBuilder = new StringBuilder(500); + htmlBuilder.append(""); + + int count = 0; + for (String type : SchemeBufferTable.getInstance().getSkillTypes()) + { + if (count == 0) + { + htmlBuilder.append(""); + } + + if (groupType.equalsIgnoreCase(type)) + { + htmlBuilder.append(""); + } + else + { + htmlBuilder.append(""); + } + + count++; + if (count == TYPES_PER_ROW) + { + htmlBuilder.append(""); + count = 0; + } + } + + if (count != 0) + { + htmlBuilder.append(""); + } + + htmlBuilder.append("
    ").append(type).append("").append(type).append("
    "); + return htmlBuilder.toString(); + } + + /** + * Computes the total fee for a skill list. + * @param list + * @return the fee. + */ + private static int getFee(List list) + { + if (SchemeBufferConfig.BUFFER_STATIC_BUFF_COST > 0) + { + return list.size() * SchemeBufferConfig.BUFFER_STATIC_BUFF_COST; + } + + int fee = 0; + for (int skillId : list) + { + final BuffSkillHolder holder = SchemeBufferTable.getInstance().getAvailableBuff(skillId); + if (holder != null) + { + fee += holder.getPrice(); + } + } + return fee; + } + + /** + * Counts skills in a list as dances/songs or non-dances. + * @param skills + * @param dances + * @return the count. + */ + private static int getCountOf(List skills, boolean dances) + { + final SkillData skillData = SkillData.getInstance(); + + int count = 0; + for (int skillId : skills) + { + final Skill skill = skillData.getSkill(skillId, 1); + if ((skill != null) && (skill.isDance() == dances)) + { + count++; + } + } + return count; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/Servitor.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/Servitor.java new file mode 100644 index 0000000..0f5b029 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/Servitor.java @@ -0,0 +1,548 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.actor.instance; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Future; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.commons.database.DatabaseFactory; +import org.l2jmobius.commons.threads.ThreadPool; +import org.l2jmobius.gameserver.config.PlayerConfig; +import org.l2jmobius.gameserver.data.sql.CharSummonTable; +import org.l2jmobius.gameserver.data.sql.SummonEffectTable; +import org.l2jmobius.gameserver.data.xml.SkillData; +import org.l2jmobius.gameserver.entity.WorldObject; +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.Summon; +import org.l2jmobius.gameserver.entity.actor.enums.creature.InstanceType; +import org.l2jmobius.gameserver.entity.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.entity.item.enums.ItemProcessType; +import org.l2jmobius.gameserver.entity.item.holders.ItemHolder; +import org.l2jmobius.gameserver.mechanics.skill.AbnormalType; +import org.l2jmobius.gameserver.mechanics.skill.BuffInfo; +import org.l2jmobius.gameserver.mechanics.skill.EffectScope; +import org.l2jmobius.gameserver.mechanics.skill.Skill; +import org.l2jmobius.gameserver.mechanics.skill.enums.SkillFinishType; +import org.l2jmobius.gameserver.mechanics.stats.Stat; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.serverpackets.SetSummonRemainTime; +import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; + +/** + * @author UnAfraid + */ +public class Servitor extends Summon implements Runnable +{ + protected static final Logger log = Logger.getLogger(Servitor.class.getName()); + + private static final String ADD_SKILL_SAVE = "REPLACE INTO character_summon_skills_save (ownerId,ownerClassIndex,summonSkillId,skill_id,skill_level,remaining_time,buff_index) VALUES (?,?,?,?,?,?,?)"; + private static final String RESTORE_SKILL_SAVE = "SELECT skill_id,skill_level,remaining_time,buff_index FROM character_summon_skills_save WHERE ownerId=? AND ownerClassIndex=? AND summonSkillId=? ORDER BY buff_index ASC"; + private static final String DELETE_SKILL_SAVE = "DELETE FROM character_summon_skills_save WHERE ownerId=? AND ownerClassIndex=? AND summonSkillId=?"; + + private float _expMultiplier = 0; + private ItemHolder _itemConsume; + private int _lifeTime; + private int _lifeTimeRemaining; + private int _consumeItemInterval; + private int _consumeItemIntervalRemaining; + protected Future _summonLifeTask; + + private int _referenceSkill; + + public Servitor(NpcTemplate template, Player owner) + { + super(template, owner); + setInstanceType(InstanceType.Servitor); + setShowSummonAnimation(true); + } + + @Override + public void onSpawn() + { + super.onSpawn(); + if (_summonLifeTask == null) + { + _summonLifeTask = ThreadPool.scheduleAtFixedRate(this, 0, 5000); + } + } + + @Override + public int getLevel() + { + return getTemplate() != null ? getTemplate().getLevel() : 0; + } + + @Override + public int getSummonType() + { + return 1; + } + + public void setExpMultiplier(float expMultiplier) + { + _expMultiplier = expMultiplier; + } + + public float getExpMultiplier() + { + return _expMultiplier; + } + + public void setItemConsume(ItemHolder item) + { + _itemConsume = item; + } + + public ItemHolder getItemConsume() + { + return _itemConsume; + } + + public void setItemConsumeInterval(int interval) + { + _consumeItemInterval = interval; + _consumeItemIntervalRemaining = interval; + } + + public int getItemConsumeInterval() + { + return _consumeItemInterval; + } + + public void setLifeTime(int lifeTime) + { + _lifeTime = lifeTime; + _lifeTimeRemaining = lifeTime; + } + + public int getLifeTime() + { + return _lifeTime; + } + + public void setLifeTimeRemaining(int time) + { + _lifeTimeRemaining = time; + } + + public int getLifeTimeRemaining() + { + return _lifeTimeRemaining; + } + + public void setReferenceSkill(int skillId) + { + _referenceSkill = skillId; + } + + public int getReferenceSkill() + { + return _referenceSkill; + } + + @Override + public boolean doDie(Creature killer) + { + if (!super.doDie(killer)) + { + return false; + } + + if (_summonLifeTask != null) + { + _summonLifeTask.cancel(false); + } + + CharSummonTable.getInstance().removeServitor(getOwner()); + return true; + } + + @Override + public void doPickupItem(WorldObject object) + { + } + + @Override + public void setRestoreSummon(boolean value) + { + _restoreSummon = value; + } + + @Override + public void stopSkillEffects(SkillFinishType type, int skillId) + { + super.stopSkillEffects(type, skillId); + SummonEffectTable.getInstance().removeServitorEffects(getOwner(), getReferenceSkill(), skillId); + } + + @Override + public void storeMe() + { + if ((_referenceSkill == 0) || isDead()) + { + return; + } + + if (PlayerConfig.RESTORE_SERVITOR_ON_RECONNECT) + { + CharSummonTable.getInstance().saveSummon(this); + } + } + + @Override + public void storeEffect(boolean storeEffects) + { + if (!PlayerConfig.SUMMON_STORE_SKILL_COOLTIME || (getOwner() == null) || getOwner().isInOlympiadMode()) + { + return; + } + + // Clear list for overwrite. + SummonEffectTable.getInstance().clearServitorEffects(getOwner(), getReferenceSkill()); + + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement(DELETE_SKILL_SAVE)) + { + // Delete all current stored effects for summon to avoid dupe. + ps.setInt(1, getOwner().getObjectId()); + ps.setInt(2, getOwner().getClassIndex()); + ps.setInt(3, _referenceSkill); + ps.execute(); + + int buffIndex = 0; + + final List storedSkills = new ArrayList<>(); + + // Store all effect data along with calculated remaining. + if (storeEffects) + { + try (PreparedStatement ps2 = con.prepareStatement(ADD_SKILL_SAVE)) + { + for (BuffInfo info : getEffectList().getEffects()) + { + if (info == null) + { + continue; + } + + final Skill skill = info.getSkill(); + + // Do not save heals. + if (skill.getAbnormalType() == AbnormalType.LIFE_FORCE_OTHERS) + { + continue; + } + + if (skill.isToggle()) + { + continue; + } + + // Dances and songs are not kept in retail. + if (skill.isDance() && !PlayerConfig.ALT_STORE_DANCES) + { + continue; + } + + if (storedSkills.contains(skill.getReuseHashCode())) + { + continue; + } + + storedSkills.add(skill.getReuseHashCode()); + + ps2.setInt(1, getOwner().getObjectId()); + ps2.setInt(2, getOwner().getClassIndex()); + ps2.setInt(3, _referenceSkill); + ps2.setInt(4, skill.getId()); + ps2.setInt(5, skill.getLevel()); + ps2.setInt(6, info.getTime()); + ps2.setInt(7, ++buffIndex); + ps2.addBatch(); + + SummonEffectTable.getInstance().addServitorEffect(getOwner(), getReferenceSkill(), skill, info.getTime()); + } + + ps2.executeBatch(); + } + } + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Could not store summon effect data: ", e); + } + } + + @Override + public void restoreEffects() + { + if (getOwner().isInOlympiadMode()) + { + return; + } + + try (Connection con = DatabaseFactory.getConnection()) + { + if (!SummonEffectTable.getInstance().containsSkill(getOwner(), getReferenceSkill())) + { + try (PreparedStatement ps = con.prepareStatement(RESTORE_SKILL_SAVE)) + { + ps.setInt(1, getOwner().getObjectId()); + ps.setInt(2, getOwner().getClassIndex()); + ps.setInt(3, _referenceSkill); + try (ResultSet rs = ps.executeQuery()) + { + while (rs.next()) + { + final Skill skill = SkillData.getInstance().getSkill(rs.getInt("skill_id"), rs.getInt("skill_level")); + if (skill == null) + { + continue; + } + + if (skill.hasEffects(EffectScope.GENERAL)) + { + SummonEffectTable.getInstance().addServitorEffect(getOwner(), getReferenceSkill(), skill, rs.getInt("remaining_time")); + } + } + } + } + } + + try (PreparedStatement statement = con.prepareStatement(DELETE_SKILL_SAVE)) + { + statement.setInt(1, getOwner().getObjectId()); + statement.setInt(2, getOwner().getClassIndex()); + statement.setInt(3, _referenceSkill); + statement.executeUpdate(); + } + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Could not restore " + this + " active effect data: " + e.getMessage(), e); + } + finally + { + SummonEffectTable.getInstance().applyServitorEffects(this, getOwner(), getReferenceSkill()); + } + } + + @Override + public void unSummon(Player owner) + { + if (_summonLifeTask != null) + { + _summonLifeTask.cancel(false); + } + + super.unSummon(owner); + + if (!_restoreSummon) + { + CharSummonTable.getInstance().removeServitor(owner); + } + } + + @Override + public boolean destroyItem(ItemProcessType process, int objectId, int count, WorldObject reference, boolean sendMessage) + { + return getOwner().destroyItem(process, objectId, count, reference, sendMessage); + } + + @Override + public boolean destroyItemByItemId(ItemProcessType process, int itemId, int count, WorldObject reference, boolean sendMessage) + { + return getOwner().destroyItemByItemId(process, itemId, count, reference, sendMessage); + } + + @Override + public boolean isServitor() + { + return true; + } + + @Override + public Servitor asServitor() + { + return this; + } + + @Override + public void run() + { + final int usedtime = 5000; + _lifeTimeRemaining -= usedtime; + if (isDead() || !isSpawned()) + { + if (_summonLifeTask != null) + { + _summonLifeTask.cancel(false); + } + return; + } + + // check if the summon's lifetime has ran out + if (_lifeTimeRemaining < 0) + { + sendPacket(SystemMessageId.SERVITOR_PASSED_AWAY); + unSummon(getOwner()); + return; + } + + if (_consumeItemInterval > 0) + { + _consumeItemIntervalRemaining -= usedtime; + + // check if it is time to consume another item + if ((_consumeItemIntervalRemaining <= 0) && (_itemConsume.getCount() > 0) && (_itemConsume.getId() > 0) && !isDead()) + { + if (destroyItemByItemId(null, _itemConsume.getId(), _itemConsume.getCount(), this, false)) + { + final SystemMessage msg = new SystemMessage(SystemMessageId.A_SUMMONED_MONSTER_USES_S1); + msg.addItemName(_itemConsume.getId()); + sendPacket(msg); + + // Reset + _consumeItemIntervalRemaining = _consumeItemInterval; + } + else + { + sendPacket(SystemMessageId.SINCE_YOU_DO_NOT_HAVE_ENOUGH_ITEMS_TO_MAINTAIN_THE_SERVITOR_S_STAY_THE_SERVITOR_WILL_DISAPPEAR); + unSummon(getOwner()); + } + } + } + + sendPacket(new SetSummonRemainTime(_lifeTime, _lifeTimeRemaining)); + updateEffectIcons(); + } + + @Override + public double getMAtk(Creature target, Skill skill) + { + final Player player = asPlayer(); + if (player == null) + { + return super.getMAtk(target, skill); + } + + return super.getMAtk(target, skill) + (player.getMAtk(target, skill) * (player.getServitorShareBonus(Stat.MAGIC_ATTACK) - 1.0)); + } + + @Override + public double getMDef(Creature target, Skill skill) + { + final Player player = asPlayer(); + if (player == null) + { + return super.getMDef(target, skill); + } + + return super.getMDef(target, skill) + (player.getMDef(target, skill) * (player.getServitorShareBonus(Stat.MAGIC_DEFENCE) - 1.0)); + } + + @Override + public double getPAtk(Creature target) + { + final Player player = asPlayer(); + if (player == null) + { + return super.getPAtk(target); + } + + return super.getPAtk(target) + (player.getPAtk(target) * (player.getServitorShareBonus(Stat.POWER_ATTACK) - 1.0)); + } + + @Override + public double getPDef(Creature target) + { + final Player player = asPlayer(); + if (player == null) + { + return super.getPDef(target); + } + + return super.getPDef(target) + (player.getPDef(target) * (player.getServitorShareBonus(Stat.POWER_DEFENCE) - 1.0)); + } + + @Override + public int getMAtkSpd() + { + final Player player = asPlayer(); + if (player == null) + { + return super.getMAtkSpd(); + } + + return (int) (super.getMAtkSpd() + (player.getMAtkSpd() * (player.getServitorShareBonus(Stat.MAGIC_ATTACK_SPEED) - 1.0))); + } + + @Override + public int getMaxHp() + { + final Player player = asPlayer(); + if (player == null) + { + return super.getMaxHp(); + } + + return (int) (super.getMaxHp() + (player.getMaxHp() * (player.getServitorShareBonus(Stat.MAX_HP) - 1.0))); + } + + @Override + public int getMaxMp() + { + final Player player = asPlayer(); + if (player == null) + { + return super.getMaxMp(); + } + + return (int) (super.getMaxMp() + (player.getMaxMp() * (player.getServitorShareBonus(Stat.MAX_MP) - 1.0))); + } + + @Override + public int getCriticalHit(Creature target, Skill skill) + { + final Player player = asPlayer(); + if (player == null) + { + return super.getCriticalHit(target, skill); + } + + return (int) (super.getCriticalHit(target, skill) + ((player.getCriticalHit(target, skill)) * (player.getServitorShareBonus(Stat.CRITICAL_RATE) - 1.0))); + } + + @Override + public double getPAtkSpd() + { + final Player player = asPlayer(); + if (player == null) + { + return super.getPAtkSpd(); + } + + return super.getPAtkSpd() + (player.getPAtkSpd() * (player.getServitorShareBonus(Stat.POWER_ATTACK_SPEED) - 1.0)); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/SiegeFlag.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/SiegeFlag.java new file mode 100644 index 0000000..2e2e9cb --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/SiegeFlag.java @@ -0,0 +1,202 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.instance; + +import org.l2jmobius.commons.threads.ThreadPool; +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.Npc; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.enums.creature.InstanceType; +import org.l2jmobius.gameserver.entity.actor.status.SiegeFlagStatus; +import org.l2jmobius.gameserver.entity.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.entity.clan.Clan; +import org.l2jmobius.gameserver.managers.CHSiegeManager; +import org.l2jmobius.gameserver.managers.SiegeManager; +import org.l2jmobius.gameserver.mechanics.siege.Siegable; +import org.l2jmobius.gameserver.mechanics.siege.SiegeClan; +import org.l2jmobius.gameserver.mechanics.skill.Skill; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.serverpackets.ActionFailed; +import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; + +public class SiegeFlag extends Npc +{ + private final Clan _clan; + private Siegable _siege; + private final boolean _isAdvanced; + private boolean _canTalk; + + /** + * Creates a siege flag. + * @param player + * @param template + * @param advanced + * @param outPost + */ + public SiegeFlag(Player player, NpcTemplate template, boolean advanced, boolean outPost) + { + super(template); + setInstanceType(InstanceType.SiegeFlag); + + _clan = player.getClan(); + _canTalk = true; + _siege = SiegeManager.getInstance().getSiege(player.getX(), player.getY(), player.getZ()); + if (_siege == null) + { + _siege = CHSiegeManager.getInstance().getSiege(player); + } + + if ((_clan == null) || (_siege == null)) + { + throw new NullPointerException(getClass().getSimpleName() + ": Initialization failed."); + } + + final SiegeClan sc = _siege.getAttackerClan(_clan); + if (sc == null) + { + throw new NullPointerException(getClass().getSimpleName() + ": Cannot find siege clan."); + } + + sc.addFlag(this); + _isAdvanced = advanced; + getStatus(); + setInvul(false); + } + + @Override + public boolean canBeAttacked() + { + return !isInvul(); + } + + @Override + public boolean isAutoAttackable(Creature attacker) + { + return !isInvul(); + } + + @Override + public boolean doDie(Creature killer) + { + if (!super.doDie(killer)) + { + return false; + } + + if ((_siege != null) && (_clan != null)) + { + final SiegeClan sc = _siege.getAttackerClan(_clan); + if (sc != null) + { + sc.removeFlag(this); + } + } + + return true; + } + + @Override + public void onForcedAttack(Player player) + { + onAction(player); + } + + @Override + public void onAction(Player player, boolean interact) + { + if ((player == null) || !canTarget(player)) + { + return; + } + + // Check if the Player already target the Npc. + if (this != player.getTarget()) + { + // Set the target of the Player player. + player.setTarget(this); + } + else if (interact) + { + if (isAutoAttackable(player) && (Math.abs(player.getZ() - getZ()) < 100)) + { + player.getAI().setIntentionAttack(this); + } + else + { + // Send a Server->Client ActionFailed to the Player in order to avoid that the client wait another packet. + player.sendPacket(ActionFailed.STATIC_PACKET); + } + } + } + + public boolean isAdvancedHeadquarter() + { + return _isAdvanced; + } + + @Override + public SiegeFlagStatus getStatus() + { + return (SiegeFlagStatus) super.getStatus(); + } + + @Override + public void initCharStatus() + { + setStatus(new SiegeFlagStatus(this)); + } + + @Override + public void reduceCurrentHp(double damage, Creature attacker, Skill skill) + { + super.reduceCurrentHp(damage, attacker, skill); + if (canTalk() && (((getCastle() != null) && getCastle().getSiege().isInProgress()) || ((getConquerableHall() != null) && getConquerableHall().isInSiege())) && (_clan != null)) + { + // Send warning to owners of headquarters that theirs base is under attack. + _clan.broadcastToOnlineMembers(new SystemMessage(SystemMessageId.YOUR_BASE_IS_BEING_ATTACKED)); + setCanTalk(false); + ThreadPool.schedule(new ScheduleTalkTask(), 20000); + } + } + + private class ScheduleTalkTask implements Runnable + { + public ScheduleTalkTask() + { + } + + @Override + public void run() + { + setCanTalk(true); + } + } + + void setCanTalk(boolean value) + { + _canTalk = value; + } + + private boolean canTalk() + { + return _canTalk; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/SignsPriest.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/SignsPriest.java new file mode 100644 index 0000000..046456c --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/SignsPriest.java @@ -0,0 +1,955 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.actor.instance; + +import java.util.StringTokenizer; +import java.util.logging.Level; + +import org.l2jmobius.gameserver.cache.HtmCache; +import org.l2jmobius.gameserver.config.FeatureConfig; +import org.l2jmobius.gameserver.entity.actor.Npc; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.enums.creature.InstanceType; +import org.l2jmobius.gameserver.entity.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.entity.clan.Clan; +import org.l2jmobius.gameserver.entity.item.enums.ItemProcessType; +import org.l2jmobius.gameserver.entity.item.instance.Item; +import org.l2jmobius.gameserver.mechanics.sevensigns.SevenSigns; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.serverpackets.NpcHtmlMessage; +import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; + +/** + * Dawn/Dusk Seven Signs Priest Instance + * @author Tempy + */ +public class SignsPriest extends Npc +{ + public SignsPriest(NpcTemplate template) + { + super(template); + setInstanceType(InstanceType.SignsPriest); + } + + @Override + public void onBypassFeedback(Player player, String command) + { + if ((player.getLastFolkNPC() == null) || (player.getLastFolkNPC().getObjectId() != getObjectId())) + { + return; + } + + if (command.startsWith("SevenSignsDesc")) + { + final int val = Integer.parseInt(command.substring(15)); + showChatWindow(player, val, null, true); + } + else if (command.startsWith("SevenSigns")) + { + SystemMessage sm; + String path; + int cabal = SevenSigns.CABAL_NULL; + int stoneType = 0; + + final long ancientAdenaAmount = player.getAncientAdena(); + int val = Integer.parseInt(command.substring(11, 12).trim()); + if (command.length() > 12) + { + val = Integer.parseInt(command.substring(11, 13).trim()); + } + + if (command.length() > 13) + { + try + { + cabal = Integer.parseInt(command.substring(14, 15).trim()); + } + catch (Exception e) + { + try + { + cabal = Integer.parseInt(command.substring(13, 14).trim()); + } + catch (Exception e2) + { + try + { + final StringTokenizer st = new StringTokenizer(command.trim()); + st.nextToken(); + cabal = Integer.parseInt(st.nextToken()); + } + catch (Exception e3) + { + LOGGER.warning("Failed to retrieve cabal from bypass command. NpcId: " + getId() + "; Command: " + command); + } + } + } + } + + switch (val) + { + case 2: // Purchase Record of the Seven Signs + { + if (!player.getInventory().validateCapacity(1)) + { + player.sendPacket(SystemMessageId.YOUR_INVENTORY_IS_FULL); + break; + } + + if (!player.reduceAdena(ItemProcessType.FEE, SevenSigns.RECORD_SEVEN_SIGNS_COST, this, true)) + { + player.sendPacket(SystemMessageId.YOU_DO_NOT_HAVE_ENOUGH_ADENA); + break; + } + + player.getInventory().addItem(ItemProcessType.REWARD, SevenSigns.RECORD_SEVEN_SIGNS_ID, 1, player, this); + sm = new SystemMessage(SystemMessageId.YOU_HAVE_EARNED_S1); + sm.addItemName(SevenSigns.RECORD_SEVEN_SIGNS_ID); + player.sendPacket(sm); + + if (this instanceof DawnPriest) + { + showChatWindow(player, val, "dawn", false); + } + else + { + showChatWindow(player, val, "dusk", false); + } + break; + } + case 33: // "I want to participate" request + { + final int oldCabal = SevenSigns.getInstance().getPlayerCabal(player.getObjectId()); + if (oldCabal != SevenSigns.CABAL_NULL) + { + if (this instanceof DawnPriest) + { + showChatWindow(player, val, "dawn_member", false); + } + else + { + showChatWindow(player, val, "dusk_member", false); + } + return; + } + else if (player.getPlayerClass().level() == 0) + { + if (this instanceof DawnPriest) + { + showChatWindow(player, val, "dawn_firstclass", false); + } + else + { + showChatWindow(player, val, "dusk_firstclass", false); + } + return; + } + else if ((cabal == SevenSigns.CABAL_DUSK) && FeatureConfig.ALT_GAME_CASTLE_DUSK) // dusk + { + // castle owners cannot participate with dusk side + final Clan clan = player.getClan(); + if ((clan != null) && (clan.getCastleId() > 0)) + { + showChatWindow(player, SevenSigns.SEVEN_SIGNS_HTML_PATH + "signs_33_dusk_no.htm"); + break; + } + } + else if ((cabal == SevenSigns.CABAL_DAWN) && FeatureConfig.ALT_GAME_CASTLE_DAWN) // dawn + { + // clans without castle need to pay participation fee + final Clan clan = player.getClan(); + if ((clan == null) || (clan.getCastleId() == 0)) + { + showChatWindow(player, SevenSigns.SEVEN_SIGNS_HTML_PATH + "signs_33_dawn_fee.htm"); + break; + } + } + + if (this instanceof DawnPriest) + { + showChatWindow(player, val, "dawn", false); + } + else + { + showChatWindow(player, val, "dusk", false); + } + break; + } + case 34: // Pay the participation fee request + { + if ((player.getPlayerClass().level() > 0) && ((player.getAdena() >= FeatureConfig.SSQ_JOIN_DAWN_ADENA_FEE) || (player.getInventory().getInventoryItemCount(FeatureConfig.SSQ_MANORS_AGREEMENT_ID, -1) > 0))) + { + showChatWindow(player, SevenSigns.SEVEN_SIGNS_HTML_PATH + "signs_33_dawn.htm"); + } + else + { + showChatWindow(player, SevenSigns.SEVEN_SIGNS_HTML_PATH + "signs_33_dawn_no.htm"); + } + break; + } + case 3: // Join Cabal Intro 1 + case 8: // Festival of Darkness Intro - SevenSigns x [0]1 + { + showChatWindow(player, val, SevenSigns.getCabalShortName(cabal), false); + break; + } + case 4: // Join a Cabal - SevenSigns 4 [0]1 x + { + final int newSeal = Integer.parseInt(command.substring(15)); + if (player.getPlayerClass().level() >= 1) + { + // even if in htmls is said that ally can have castle too, but it is not + final Clan clan = player.getClan(); + if ((cabal == SevenSigns.CABAL_DUSK) && FeatureConfig.ALT_GAME_CASTLE_DUSK && (clan != null) && (clan.getCastleId() > 0)) + { + showChatWindow(player, SevenSigns.SEVEN_SIGNS_HTML_PATH + "signs_33_dusk_no.htm"); + return; + } + + // If the player is trying to join the Lords of Dawn, check if they are carrying a Lord's certificate. If not then try to take the required amount of adena instead. + if (FeatureConfig.ALT_GAME_CASTLE_DAWN && (cabal == SevenSigns.CABAL_DAWN)) + { + boolean allowJoinDawn = false; + if ((clan != null) && (clan.getCastleId() > 0)) + { + allowJoinDawn = true; + } + else if (player.destroyItemByItemId(ItemProcessType.FEE, FeatureConfig.SSQ_MANORS_AGREEMENT_ID, 1, this, true)) + { + allowJoinDawn = true; + } + else if (player.reduceAdena(ItemProcessType.FEE, FeatureConfig.SSQ_JOIN_DAWN_ADENA_FEE, this, true)) + { + allowJoinDawn = true; + } + + if (!allowJoinDawn) + { + showChatWindow(player, SevenSigns.SEVEN_SIGNS_HTML_PATH + "signs_33_dawn_fee.htm"); + return; + } + } + } + + SevenSigns.getInstance().setPlayerInfo(player.getObjectId(), cabal, newSeal); + if (cabal == SevenSigns.CABAL_DAWN) + { + player.sendPacket(SystemMessageId.YOU_WILL_PARTICIPATE_IN_THE_SEVEN_SIGNS_AS_A_MEMBER_OF_THE_LORDS_OF_DAWN); // Joined Dawn + } + else + { + player.sendPacket(SystemMessageId.YOU_WILL_PARTICIPATE_IN_THE_SEVEN_SIGNS_AS_A_MEMBER_OF_THE_REVOLUTIONARIES_OF_DUSK); // Joined Dusk + } + + // Show a confirmation message to the user, indicating which seal they chose. + switch (newSeal) + { + case SevenSigns.SEAL_AVARICE: + { + player.sendPacket(SystemMessageId.YOU_VE_CHOSEN_TO_FIGHT_FOR_THE_SEAL_OF_AVARICE_DURING_THIS_QUEST_EVENT_PERIOD); + break; + } + case SevenSigns.SEAL_GNOSIS: + { + player.sendPacket(SystemMessageId.YOU_VE_CHOSEN_TO_FIGHT_FOR_THE_SEAL_OF_GNOSIS_DURING_THIS_QUEST_EVENT_PERIOD); + break; + } + case SevenSigns.SEAL_STRIFE: + { + player.sendPacket(SystemMessageId.YOU_VE_CHOSEN_TO_FIGHT_FOR_THE_SEAL_OF_STRIFE_DURING_THIS_QUEST_EVENT_PERIOD); + break; + } + } + + showChatWindow(player, 4, SevenSigns.getCabalShortName(cabal), false); + break; + } + case 5: + { + if (this instanceof DawnPriest) + { + if (SevenSigns.getInstance().getPlayerCabal(player.getObjectId()) == SevenSigns.CABAL_NULL) + { + showChatWindow(player, val, "dawn_no", false); + } + else + { + showChatWindow(player, val, "dawn", false); + } + } + else + { + if (SevenSigns.getInstance().getPlayerCabal(player.getObjectId()) == SevenSigns.CABAL_NULL) + { + showChatWindow(player, val, "dusk_no", false); + } + else + { + showChatWindow(player, val, "dusk", false); + } + } + break; + } + case 21: + { + final int contribStoneId = Integer.parseInt(command.substring(14, 18)); + final Item contribBlueStones = player.getInventory().getItemByItemId(SevenSigns.SEAL_STONE_BLUE_ID); + final Item contribGreenStones = player.getInventory().getItemByItemId(SevenSigns.SEAL_STONE_GREEN_ID); + final Item contribRedStones = player.getInventory().getItemByItemId(SevenSigns.SEAL_STONE_RED_ID); + final int contribBlueStoneCount = contribBlueStones == null ? 0 : contribBlueStones.getCount(); + final int contribGreenStoneCount = contribGreenStones == null ? 0 : contribGreenStones.getCount(); + final int contribRedStoneCount = contribRedStones == null ? 0 : contribRedStones.getCount(); + int score = SevenSigns.getInstance().getPlayerContribScore(player.getObjectId()); + int contributionCount = 0; + boolean contribStonesFound = false; + int redContrib = 0; + int greenContrib = 0; + int blueContrib = 0; + + try + { + contributionCount = Integer.parseInt(command.substring(19).trim()); + } + catch (Exception nfe) + { + if (this instanceof DawnPriest) + { + showChatWindow(player, 6, "dawn_failure", false); + } + else + { + showChatWindow(player, 6, "dusk_failure", false); + } + break; + } + + switch (contribStoneId) + { + case SevenSigns.SEAL_STONE_BLUE_ID: + { + blueContrib = (FeatureConfig.ALT_MAXIMUM_PLAYER_CONTRIB - score) / SevenSigns.BLUE_CONTRIB_POINTS; + if (blueContrib > contribBlueStoneCount) + { + blueContrib = contributionCount; + } + break; + } + case SevenSigns.SEAL_STONE_GREEN_ID: + { + greenContrib = (FeatureConfig.ALT_MAXIMUM_PLAYER_CONTRIB - score) / SevenSigns.GREEN_CONTRIB_POINTS; + if (greenContrib > contribGreenStoneCount) + { + greenContrib = contributionCount; + } + break; + } + case SevenSigns.SEAL_STONE_RED_ID: + { + redContrib = (FeatureConfig.ALT_MAXIMUM_PLAYER_CONTRIB - score) / SevenSigns.RED_CONTRIB_POINTS; + if (redContrib > contribRedStoneCount) + { + redContrib = contributionCount; + } + break; + } + } + + if ((redContrib > 0) && player.destroyItemByItemId(ItemProcessType.FEE, SevenSigns.SEAL_STONE_RED_ID, redContrib, this, false)) + { + contribStonesFound = true; + final SystemMessage msg = new SystemMessage(SystemMessageId.S2_S1_HAS_DISAPPEARED); + msg.addItemName(SevenSigns.SEAL_STONE_RED_ID); + msg.addInt(redContrib); + player.sendPacket(msg); + } + + if ((greenContrib > 0) && player.destroyItemByItemId(ItemProcessType.FEE, SevenSigns.SEAL_STONE_GREEN_ID, greenContrib, this, false)) + { + contribStonesFound = true; + final SystemMessage msg = new SystemMessage(SystemMessageId.S2_S1_HAS_DISAPPEARED); + msg.addItemName(SevenSigns.SEAL_STONE_GREEN_ID); + msg.addInt(greenContrib); + player.sendPacket(msg); + } + + if ((blueContrib > 0) && player.destroyItemByItemId(ItemProcessType.FEE, SevenSigns.SEAL_STONE_BLUE_ID, blueContrib, this, false)) + { + contribStonesFound = true; + final SystemMessage msg = new SystemMessage(SystemMessageId.S2_S1_HAS_DISAPPEARED); + msg.addItemName(SevenSigns.SEAL_STONE_BLUE_ID); + msg.addInt(blueContrib); + player.sendPacket(msg); + } + + if (!contribStonesFound) + { + if (this instanceof DawnPriest) + { + showChatWindow(player, 6, "dawn_low_stones", false); + } + else + { + showChatWindow(player, 6, "dusk_low_stones", false); + } + } + else + { + score = SevenSigns.getInstance().addPlayerStoneContrib(player.getObjectId(), blueContrib, greenContrib, redContrib); + sm = new SystemMessage(SystemMessageId.YOUR_CONTRIBUTION_SCORE_IS_INCREASED_BY_S1); + sm.addInt(score); + player.sendPacket(sm); + + if (this instanceof DawnPriest) + { + showChatWindow(player, 6, "dawn", false); + } + else + { + showChatWindow(player, 6, "dusk", false); + } + } + break; + } + case 6: // Contribute Seal Stones - SevenSigns 6 x + { + stoneType = Integer.parseInt(command.substring(13)); + + final Item blueStones = player.getInventory().getItemByItemId(SevenSigns.SEAL_STONE_BLUE_ID); + final Item greenStones = player.getInventory().getItemByItemId(SevenSigns.SEAL_STONE_GREEN_ID); + final Item redStones = player.getInventory().getItemByItemId(SevenSigns.SEAL_STONE_RED_ID); + final int blueStoneCount = blueStones == null ? 0 : blueStones.getCount(); + final int greenStoneCount = greenStones == null ? 0 : greenStones.getCount(); + final int redStoneCount = redStones == null ? 0 : redStones.getCount(); + int contribScore = SevenSigns.getInstance().getPlayerContribScore(player.getObjectId()); + boolean stonesFound = false; + if (contribScore == FeatureConfig.ALT_MAXIMUM_PLAYER_CONTRIB) + { + player.sendPacket(SystemMessageId.CONTRIBUTION_LEVEL_HAS_EXCEEDED_THE_LIMIT_YOU_MAY_NOT_CONTINUE); + } + else + { + int redContribCount = 0; + int greenContribCount = 0; + int blueContribCount = 0; + String contribStoneColor = null; + String stoneColorContr = null; + int stoneCountContr = 0; + int stoneIdContr = 0; + + switch (stoneType) + { + case 1: + { + contribStoneColor = "Blue"; + stoneColorContr = "blue"; + stoneIdContr = SevenSigns.SEAL_STONE_BLUE_ID; + stoneCountContr = blueStoneCount; + break; + } + case 2: + { + contribStoneColor = "Green"; + stoneColorContr = "green"; + stoneIdContr = SevenSigns.SEAL_STONE_GREEN_ID; + stoneCountContr = greenStoneCount; + break; + } + case 3: + { + contribStoneColor = "Red"; + stoneColorContr = "red"; + stoneIdContr = SevenSigns.SEAL_STONE_RED_ID; + stoneCountContr = redStoneCount; + break; + } + case 4: + { + int tempContribScore = contribScore; + redContribCount = (FeatureConfig.ALT_MAXIMUM_PLAYER_CONTRIB - tempContribScore) / SevenSigns.RED_CONTRIB_POINTS; + if (redContribCount > redStoneCount) + { + redContribCount = redStoneCount; + } + + tempContribScore += redContribCount * SevenSigns.RED_CONTRIB_POINTS; + greenContribCount = (FeatureConfig.ALT_MAXIMUM_PLAYER_CONTRIB - tempContribScore) / SevenSigns.GREEN_CONTRIB_POINTS; + if (greenContribCount > greenStoneCount) + { + greenContribCount = greenStoneCount; + } + + tempContribScore += greenContribCount * SevenSigns.GREEN_CONTRIB_POINTS; + blueContribCount = (FeatureConfig.ALT_MAXIMUM_PLAYER_CONTRIB - tempContribScore) / SevenSigns.BLUE_CONTRIB_POINTS; + if (blueContribCount > blueStoneCount) + { + blueContribCount = blueStoneCount; + } + + if ((redContribCount > 0) && player.destroyItemByItemId(ItemProcessType.FEE, SevenSigns.SEAL_STONE_RED_ID, redContribCount, this, false)) + { + stonesFound = true; + final SystemMessage msg = new SystemMessage(SystemMessageId.S2_S1_HAS_DISAPPEARED); + msg.addItemName(SevenSigns.SEAL_STONE_RED_ID); + msg.addInt(redContribCount); + player.sendPacket(msg); + } + + if ((greenContribCount > 0) && player.destroyItemByItemId(ItemProcessType.FEE, SevenSigns.SEAL_STONE_GREEN_ID, greenContribCount, this, false)) + { + stonesFound = true; + final SystemMessage msg = new SystemMessage(SystemMessageId.S2_S1_HAS_DISAPPEARED); + msg.addItemName(SevenSigns.SEAL_STONE_GREEN_ID); + msg.addInt(greenContribCount); + player.sendPacket(msg); + } + + if ((blueContribCount > 0) && player.destroyItemByItemId(ItemProcessType.FEE, SevenSigns.SEAL_STONE_BLUE_ID, blueContribCount, this, false)) + { + stonesFound = true; + final SystemMessage msg = new SystemMessage(SystemMessageId.S2_S1_HAS_DISAPPEARED); + msg.addItemName(SevenSigns.SEAL_STONE_BLUE_ID); + msg.addInt(blueContribCount); + player.sendPacket(msg); + } + + if (!stonesFound) + { + if (this instanceof DawnPriest) + { + showChatWindow(player, val, "dawn_no_stones", false); + } + else + { + showChatWindow(player, val, "dusk_no_stones", false); + } + } + else + { + contribScore = SevenSigns.getInstance().addPlayerStoneContrib(player.getObjectId(), blueContribCount, greenContribCount, redContribCount); + sm = new SystemMessage(SystemMessageId.YOUR_CONTRIBUTION_SCORE_IS_INCREASED_BY_S1); + sm.addInt(contribScore); + player.sendPacket(sm); + + if (this instanceof DawnPriest) + { + showChatWindow(player, 6, "dawn", false); + } + else + { + showChatWindow(player, 6, "dusk", false); + } + } + return; + } + } + + if (this instanceof DawnPriest) + { + path = SevenSigns.SEVEN_SIGNS_HTML_PATH + "signs_6_dawn_contribute.htm"; + } + else + { + path = SevenSigns.SEVEN_SIGNS_HTML_PATH + "signs_6_dusk_contribute.htm"; + } + + String contentContr = HtmCache.getInstance().getHtm(player, path); + if (contentContr != null) + { + contentContr = contentContr.replace("%contribStoneColor%", contribStoneColor); + contentContr = contentContr.replace("%stoneColor%", stoneColorContr); + contentContr = contentContr.replace("%stoneCount%", String.valueOf(stoneCountContr)); + contentContr = contentContr.replace("%stoneItemId%", String.valueOf(stoneIdContr)); + contentContr = contentContr.replace("%objectId%", String.valueOf(getObjectId())); + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setHtml(contentContr); + player.sendPacket(html); + } + else + { + LOGGER.warning("Problem with HTML text " + path); + } + } + break; + } + case 7: // Exchange Ancient Adena for Adena - SevenSigns 7 xxxxxxx + { + int ancientAdenaConvert = 0; + + try + { + ancientAdenaConvert = Integer.parseInt(command.substring(13).trim()); + } + catch (NumberFormatException e) + { + showChatWindow(player, SevenSigns.SEVEN_SIGNS_HTML_PATH + "blkmrkt_3.htm"); + break; + } + catch (StringIndexOutOfBoundsException e) + { + showChatWindow(player, SevenSigns.SEVEN_SIGNS_HTML_PATH + "blkmrkt_3.htm"); + break; + } + + if (ancientAdenaConvert < 1) + { + showChatWindow(player, SevenSigns.SEVEN_SIGNS_HTML_PATH + "blkmrkt_3.htm"); + break; + } + + if (ancientAdenaAmount < ancientAdenaConvert) + { + showChatWindow(player, SevenSigns.SEVEN_SIGNS_HTML_PATH + "blkmrkt_4.htm"); + break; + } + + player.reduceAncientAdena(ItemProcessType.SELL, ancientAdenaConvert, this, true); + player.addAdena(ItemProcessType.BUY, ancientAdenaConvert, this, true); + showChatWindow(player, SevenSigns.SEVEN_SIGNS_HTML_PATH + "blkmrkt_5.htm"); + break; + } + case 9: // Receive Contribution Rewards + { + final int playerCabal = SevenSigns.getInstance().getPlayerCabal(player.getObjectId()); + final int winningCabal = SevenSigns.getInstance().getCabalHighestScore(); + if (SevenSigns.getInstance().isSealValidationPeriod() && (playerCabal == winningCabal)) + { + final int ancientAdenaReward = SevenSigns.getInstance().getAncientAdenaReward(player.getObjectId(), true); + if (ancientAdenaReward < 3) + { + if (this instanceof DawnPriest) + { + showChatWindow(player, 9, "dawn_b", false); + } + else + { + showChatWindow(player, 9, "dusk_b", false); + } + break; + } + + player.addAncientAdena(ItemProcessType.REWARD, ancientAdenaReward, this, true); + if (this instanceof DawnPriest) + { + showChatWindow(player, 9, "dawn_a", false); + } + else + { + showChatWindow(player, 9, "dusk_a", false); + } + } + break; + } + case 11: // Teleport to Hunting Grounds + { + try + { + final String portInfo = command.substring(14).trim(); + final StringTokenizer st = new StringTokenizer(portInfo); + final int x = Integer.parseInt(st.nextToken()); + final int y = Integer.parseInt(st.nextToken()); + final int z = Integer.parseInt(st.nextToken()); + final int ancientAdenaCost = Integer.parseInt(st.nextToken()); + if ((ancientAdenaCost > 0) && !player.reduceAncientAdena(ItemProcessType.FEE, ancientAdenaCost, this, true)) + { + break; + } + + player.teleToLocation(x, y, z); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "SevenSigns: Error occurred while teleporting player: " + e.getMessage(), e); + } + break; + } + case 16: + { + if (this instanceof DawnPriest) + { + showChatWindow(player, val, "dawn", false); + } + else + { + showChatWindow(player, val, "dusk", false); + } + break; + } + case 17: // Exchange Seal Stones for Ancient Adena (Type Choice) - SevenSigns 17 x + { + stoneType = Integer.parseInt(command.substring(14)); + int stoneId = 0; + long stoneCount = 0; + int stoneValue = 0; + String stoneColor = null; + + switch (stoneType) + { + case 1: + { + stoneColor = "blue"; + stoneId = SevenSigns.SEAL_STONE_BLUE_ID; + stoneValue = SevenSigns.SEAL_STONE_BLUE_VALUE; + break; + } + case 2: + { + stoneColor = "green"; + stoneId = SevenSigns.SEAL_STONE_GREEN_ID; + stoneValue = SevenSigns.SEAL_STONE_GREEN_VALUE; + break; + } + case 3: + { + stoneColor = "red"; + stoneId = SevenSigns.SEAL_STONE_RED_ID; + stoneValue = SevenSigns.SEAL_STONE_RED_VALUE; + break; + } + case 4: + { + final Item blueStonesAll = player.getInventory().getItemByItemId(SevenSigns.SEAL_STONE_BLUE_ID); + final Item greenStonesAll = player.getInventory().getItemByItemId(SevenSigns.SEAL_STONE_GREEN_ID); + final Item redStonesAll = player.getInventory().getItemByItemId(SevenSigns.SEAL_STONE_RED_ID); + final int blueStoneCountAll = blueStonesAll == null ? 0 : blueStonesAll.getCount(); + final int greenStoneCountAll = greenStonesAll == null ? 0 : greenStonesAll.getCount(); + final int redStoneCountAll = redStonesAll == null ? 0 : redStonesAll.getCount(); + int ancientAdenaRewardAll = 0; + ancientAdenaRewardAll = SevenSigns.calcAncientAdenaReward(blueStoneCountAll, greenStoneCountAll, redStoneCountAll); + if (ancientAdenaRewardAll == 0) + { + if (this instanceof DawnPriest) + { + showChatWindow(player, 18, "dawn_no_stones", false); + } + else + { + showChatWindow(player, 18, "dusk_no_stones", false); + } + return; + } + + if (blueStoneCountAll > 0) + { + player.destroyItemByItemId(ItemProcessType.FEE, SevenSigns.SEAL_STONE_BLUE_ID, blueStoneCountAll, this, true); + } + + if (greenStoneCountAll > 0) + { + player.destroyItemByItemId(ItemProcessType.FEE, SevenSigns.SEAL_STONE_GREEN_ID, greenStoneCountAll, this, true); + } + + if (redStoneCountAll > 0) + { + player.destroyItemByItemId(ItemProcessType.FEE, SevenSigns.SEAL_STONE_RED_ID, redStoneCountAll, this, true); + } + + player.addAncientAdena(ItemProcessType.REWARD, ancientAdenaRewardAll, this, true); + if (this instanceof DawnPriest) + { + showChatWindow(player, 18, "dawn", false); + } + else + { + showChatWindow(player, 18, "dusk", false); + } + return; + } + } + + final Item stoneInstance = player.getInventory().getItemByItemId(stoneId); + if (stoneInstance != null) + { + stoneCount = stoneInstance.getCount(); + } + + if (this instanceof DawnPriest) + { + path = SevenSigns.SEVEN_SIGNS_HTML_PATH + "signs_17_dawn.htm"; + } + else + { + path = SevenSigns.SEVEN_SIGNS_HTML_PATH + "signs_17_dusk.htm"; + } + + String content = HtmCache.getInstance().getHtm(player, path); + if (content != null) + { + content = content.replace("%stoneColor%", stoneColor); + content = content.replace("%stoneValue%", String.valueOf(stoneValue)); + content = content.replace("%stoneCount%", String.valueOf(stoneCount)); + content = content.replace("%stoneItemId%", String.valueOf(stoneId)); + content = content.replace("%objectId%", String.valueOf(getObjectId())); + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setHtml(content); + player.sendPacket(html); + } + else + { + LOGGER.warning("Problem with HTML text " + SevenSigns.SEVEN_SIGNS_HTML_PATH + "signs_17.htm: " + path); + } + break; + } + case 18: // Exchange Seal Stones for Ancient Adena - SevenSigns 18 xxxx xxxxxx + { + final int convertStoneId = Integer.parseInt(command.substring(14, 18)); + int convertCount = 0; + + try + { + convertCount = Integer.parseInt(command.substring(19).trim()); + } + catch (Exception nfe) + { + if (this instanceof DawnPriest) + { + showChatWindow(player, 18, "dawn_failed", false); + } + else + { + showChatWindow(player, 18, "dusk_failed", false); + } + break; + } + + final Item convertItem = player.getInventory().getItemByItemId(convertStoneId); + if (convertItem != null) + { + int ancientAdenaReward = 0; + final int totalCount = convertItem.getCount(); + if ((convertCount <= totalCount) && (convertCount > 0)) + { + switch (convertStoneId) + { + case SevenSigns.SEAL_STONE_BLUE_ID: + { + ancientAdenaReward = SevenSigns.calcAncientAdenaReward(convertCount, 0, 0); + break; + } + case SevenSigns.SEAL_STONE_GREEN_ID: + { + ancientAdenaReward = SevenSigns.calcAncientAdenaReward(0, convertCount, 0); + break; + } + case SevenSigns.SEAL_STONE_RED_ID: + { + ancientAdenaReward = SevenSigns.calcAncientAdenaReward(0, 0, convertCount); + break; + } + } + + if (player.destroyItemByItemId(ItemProcessType.FEE, convertStoneId, convertCount, this, true)) + { + player.addAncientAdena(ItemProcessType.REWARD, ancientAdenaReward, this, true); + if (this instanceof DawnPriest) + { + showChatWindow(player, 18, "dawn", false); + } + else + { + showChatWindow(player, 18, "dusk", false); + } + } + } + else + { + if (this instanceof DawnPriest) + { + showChatWindow(player, 18, "dawn_low_stones", false); + } + else + { + showChatWindow(player, 18, "dusk_low_stones", false); + } + break; + } + } + else + { + if (this instanceof DawnPriest) + { + showChatWindow(player, 18, "dawn_no_stones", false); + } + else + { + showChatWindow(player, 18, "dusk_no_stones", false); + } + break; + } + break; + } + case 19: // Seal Information (for when joining a cabal) + { + final int chosenSeal = Integer.parseInt(command.substring(16)); + final String fileSuffix = SevenSigns.getSealName(chosenSeal, true) + "_" + SevenSigns.getCabalShortName(cabal); + showChatWindow(player, val, fileSuffix, false); + break; + } + case 20: // Seal Status (for when joining a cabal) + { + final StringBuilder contentBuffer = new StringBuilder(); + if (this instanceof DawnPriest) + { + contentBuffer.append("Priest of Dawn:
    [ Seal Status ]
    "); + } + else + { + contentBuffer.append("Dusk Priestess:
    [ Status of the Seals ]
    "); + } + + for (int i = 1; i < 4; i++) + { + final int sealOwner = SevenSigns.getInstance().getSealOwner(i); + if (sealOwner != SevenSigns.CABAL_NULL) + { + contentBuffer.append("[" + SevenSigns.getSealName(i, false) + ": " + SevenSigns.getCabalName(sealOwner) + "]
    "); + } + else + { + contentBuffer.append("[" + SevenSigns.getSealName(i, false) + ": Nothingness]
    "); + } + } + + contentBuffer.append("Go back."); + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setHtml(contentBuffer.toString()); + player.sendPacket(html); + break; + } + default: + { + showChatWindow(player, val, null, false); + break; + } + } + } + else + { + super.onBypassFeedback(player, command); + } + } + + private void showChatWindow(Player player, int value, String suffix, boolean isDescription) + { + String filename = SevenSigns.SEVEN_SIGNS_HTML_PATH; + filename += (isDescription) ? "desc_" + value : "signs_" + value; + filename += (suffix != null) ? "_" + suffix + ".htm" : ".htm"; + showChatWindow(player, filename); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/StaticObject.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/StaticObject.java new file mode 100644 index 0000000..28ab437 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/StaticObject.java @@ -0,0 +1,214 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.actor.instance; + +import org.l2jmobius.gameserver.ai.CreatureAI; +import org.l2jmobius.gameserver.entity.Location; +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.enums.creature.InstanceType; +import org.l2jmobius.gameserver.entity.actor.stat.StaticObjectStat; +import org.l2jmobius.gameserver.entity.actor.status.StaticObjectStatus; +import org.l2jmobius.gameserver.entity.actor.templates.CreatureTemplate; +import org.l2jmobius.gameserver.entity.item.Weapon; +import org.l2jmobius.gameserver.entity.item.instance.Item; +import org.l2jmobius.gameserver.mechanics.skill.Skill; +import org.l2jmobius.gameserver.network.serverpackets.ShowTownMap; +import org.l2jmobius.gameserver.network.serverpackets.StaticObjectInfo; + +/** + * Static Object instance. + * @author godson + */ +public class StaticObject extends Creature +{ + /** The interaction distance of the StaticObject */ + public static final int INTERACTION_DISTANCE = 150; + + private final int _staticObjectId; + private int _meshIndex = 0; // 0 - static objects, alternate static objects + private int _type = -1; // 0 - map signs, 1 - throne , 2 - arena signs + private ShowTownMap _map; + + /** + * Creates a static object. + * @param template the static object + * @param staticId the static ID + */ + public StaticObject(CreatureTemplate template, int staticId) + { + super(template); + setInstanceType(InstanceType.StaticObject); + _staticObjectId = staticId; + } + + @Override + protected CreatureAI initAI() + { + return null; + } + + /** + * Gets the static object ID. + * @return the static object ID + */ + @Override + public int getId() + { + return _staticObjectId; + } + + @Override + public StaticObjectStat getStat() + { + return (StaticObjectStat) super.getStat(); + } + + @Override + public void initCharStat() + { + setStat(new StaticObjectStat(this)); + } + + @Override + public StaticObjectStatus getStatus() + { + return (StaticObjectStatus) super.getStatus(); + } + + @Override + public void initCharStatus() + { + setStatus(new StaticObjectStatus(this)); + } + + public int getType() + { + return _type; + } + + public void setType(int type) + { + _type = type; + } + + public void setMap(String texture, int x, int y) + { + _map = new ShowTownMap("town_map." + texture, x, y); + } + + public ShowTownMap getMap() + { + return _map; + } + + @Override + public int getLevel() + { + return 1; + } + + @Override + public Item getActiveWeaponInstance() + { + return null; + } + + @Override + public Weapon getActiveWeaponItem() + { + return null; + } + + @Override + public Item getSecondaryWeaponInstance() + { + return null; + } + + @Override + public Weapon getSecondaryWeaponItem() + { + return null; + } + + @Override + public boolean isAutoAttackable(Creature attacker) + { + return false; + } + + /** + * Set the meshIndex of the object.
    + *
    + * Values: + *
      + *
    • default textures : 0
    • + *
    • alternate textures : 1
    • + *
    + * @param meshIndex + */ + public void setMeshIndex(int meshIndex) + { + _meshIndex = meshIndex; + broadcastPacket(new StaticObjectInfo(this)); + } + + /** + * Values: + *
      + *
    • default textures : 0
    • + *
    • alternate textures : 1
    • + *
    + * @return the meshIndex of the object + */ + public int getMeshIndex() + { + return _meshIndex; + } + + @Override + public void updateAbnormalEffect() + { + } + + @Override + public void sendInfo(Player player) + { + player.sendPacket(new StaticObjectInfo(this)); + } + + @Override + public void moveToLocation(int x, int y, int z, int offset) + { + } + + @Override + public void stopMove(Location loc) + { + } + + @Override + public void doAttack(Creature target) + { + } + + @Override + public void doCast(Skill skill) + { + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/TamedBeast.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/TamedBeast.java new file mode 100644 index 0000000..bde20a2 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/TamedBeast.java @@ -0,0 +1,603 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.instance; + +import java.util.Collection; +import java.util.Collections; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Future; + +import org.l2jmobius.commons.threads.ThreadPool; +import org.l2jmobius.commons.util.Rnd; +import org.l2jmobius.gameserver.data.xml.NpcData; +import org.l2jmobius.gameserver.data.xml.SkillData; +import org.l2jmobius.gameserver.entity.Location; +import org.l2jmobius.gameserver.entity.WorldObject; +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.enums.creature.InstanceType; +import org.l2jmobius.gameserver.entity.item.enums.ItemProcessType; +import org.l2jmobius.gameserver.entity.item.instance.Item; +import org.l2jmobius.gameserver.mechanics.effects.EffectType; +import org.l2jmobius.gameserver.mechanics.skill.Skill; +import org.l2jmobius.gameserver.network.serverpackets.AbstractNpcInfo; +import org.l2jmobius.gameserver.network.serverpackets.ActionFailed; +import org.l2jmobius.gameserver.network.serverpackets.SocialAction; +import org.l2jmobius.gameserver.network.serverpackets.StopMove; + +// While a tamed beast behaves a lot like a pet (ingame) and does have +// an owner, in all other aspects, it acts like a mob. +// In addition, it can be fed in order to increase its duration. +// This class handles the running tasks, AI, and feed of the mob. +// The (mostly optional) AI on feeding the spawn is handled by the datapack ai script. +public class TamedBeast extends FeedableBeast +{ + private int _foodSkillId; + private static final int MAX_DISTANCE_FROM_HOME = 30000; + private static final int MAX_DISTANCE_FROM_OWNER = 2000; + private static final int MAX_DURATION = 1200000; // 20 minutes + private static final int DURATION_CHECK_INTERVAL = 60000; // 1 minute + private static final int DURATION_INCREASE_INTERVAL = 20000; // 20 secs (gained upon feeding) + private static final int BUFF_INTERVAL = 5000; // 5 seconds + private int _remainingTime = MAX_DURATION; + private int _homeX; + private int _homeY; + private int _homeZ; + protected Player _owner; + private Future _buffTask = null; + private Future _durationCheckTask = null; + protected boolean _isFreyaBeast; + private Collection _beastSkills = null; + + public TamedBeast(int npcTemplateId) + { + super(NpcData.getInstance().getTemplate(npcTemplateId)); + setInstanceType(InstanceType.TamedBeast); + setHome(this); + } + + public TamedBeast(int npcTemplateId, Player owner, int foodSkillId, int x, int y, int z) + { + super(NpcData.getInstance().getTemplate(npcTemplateId)); + _isFreyaBeast = false; + setInstanceType(InstanceType.TamedBeast); + fullRestore(); + setOwner(owner); + setFoodType(foodSkillId); + setHome(x, y, z); + spawnMe(x, y, z); + } + + public TamedBeast(int npcTemplateId, Player owner, int food, int x, int y, int z, boolean isFreyaBeast) + { + super(NpcData.getInstance().getTemplate(npcTemplateId)); + _isFreyaBeast = isFreyaBeast; + setInstanceType(InstanceType.TamedBeast); + fullRestore(); + setFoodType(food); + setHome(x, y, z); + spawnMe(x, y, z); + setOwner(owner); + if (isFreyaBeast) + { + getAI().setIntentionFollow(_owner); + } + } + + public void onReceiveFood() + { + // Eating food extends the duration by 20secs, to a max of 20minutes. + _remainingTime += DURATION_INCREASE_INTERVAL; + if (_remainingTime > MAX_DURATION) + { + _remainingTime = MAX_DURATION; + } + } + + public Location getHome() + { + return new Location(_homeX, _homeY, _homeZ); + } + + public void setHome(int x, int y, int z) + { + _homeX = x; + _homeY = y; + _homeZ = z; + } + + public void setHome(Creature c) + { + setHome(c.getX(), c.getY(), c.getZ()); + } + + public int getRemainingTime() + { + return _remainingTime; + } + + public void setRemainingTime(int duration) + { + _remainingTime = duration; + } + + public int getFoodType() + { + return _foodSkillId; + } + + public void setFoodType(int foodItemId) + { + if (foodItemId > 0) + { + _foodSkillId = foodItemId; + + // Start the duration checks. + // Start the buff tasks. + if (_durationCheckTask != null) + { + _durationCheckTask.cancel(true); + } + + _durationCheckTask = ThreadPool.scheduleAtFixedRate(new CheckDuration(this), DURATION_CHECK_INTERVAL, DURATION_CHECK_INTERVAL); + } + } + + @Override + public boolean doDie(Creature killer) + { + if (!super.doDie(killer)) + { + return false; + } + + getAI().stopFollow(); + if (_buffTask != null) + { + _buffTask.cancel(true); + } + + if (_durationCheckTask != null) + { + _durationCheckTask.cancel(true); + } + + // Clean up variables. + if ((_owner != null) && (_owner.getTrainedBeasts() != null)) + { + _owner.getTrainedBeasts().remove(this); + } + + _buffTask = null; + _durationCheckTask = null; + _owner = null; + _foodSkillId = 0; + _remainingTime = 0; + return true; + } + + @Override + public boolean isAutoAttackable(Creature attacker) + { + return !_isFreyaBeast; + } + + public boolean isFreyaBeast() + { + return _isFreyaBeast; + } + + public void addBeastSkill(Skill skill) + { + if (_beastSkills == null) + { + _beastSkills = ConcurrentHashMap.newKeySet(); + } + + _beastSkills.add(skill); + } + + public void castBeastSkills() + { + if ((_owner == null) || (_beastSkills == null)) + { + return; + } + + int delay = 100; + for (Skill skill : _beastSkills) + { + ThreadPool.schedule(new buffCast(skill), delay); + delay += (100 + skill.getHitTime()); + } + + ThreadPool.schedule(new buffCast(null), delay); + } + + private class buffCast implements Runnable + { + private final Skill _skill; + + public buffCast(Skill skill) + { + _skill = skill; + } + + @Override + public void run() + { + if (_skill == null) + { + getAI().setIntentionFollow(_owner); + } + else + { + sitCastAndFollow(_skill, _owner); + } + } + } + + public Player getOwner() + { + return _owner; + } + + public void setOwner(Player owner) + { + if (owner != null) + { + _owner = owner; + setTitle(owner.getName()); + + // Broadcast the new title. + setShowSummonAnimation(true); + broadcastPacket(new AbstractNpcInfo.NpcInfo(this, owner)); + owner.addTrainedBeast(this); + + // Always and automatically follow the owner. + getAI().startFollow(_owner, 100); + if (!_isFreyaBeast) + { + // Instead of calculating this value each time, let's get this now and pass it on. + int totalBuffsAvailable = 0; + for (Skill skill : getTemplate().getSkills().values()) + { + // If the skill is a buff, check if the owner has it already [ owner.getEffect(Skill skill) ]. + if (skill.isContinuous() && !skill.isDebuff()) + { + totalBuffsAvailable++; + } + } + + // Start the buff tasks. + if (_buffTask != null) + { + _buffTask.cancel(true); + } + + _buffTask = ThreadPool.scheduleAtFixedRate(new CheckOwnerBuffs(this, totalBuffsAvailable), BUFF_INTERVAL, BUFF_INTERVAL); + } + } + else + { + deleteMe(); // despawn if no owner + } + } + + public boolean isTooFarFromHome() + { + return !isInsideRadius3D(_homeX, _homeY, _homeZ, MAX_DISTANCE_FROM_HOME); + } + + @Override + public boolean deleteMe() + { + if (_buffTask != null) + { + _buffTask.cancel(true); + } + + _durationCheckTask.cancel(true); + stopHpMpRegeneration(); + + // Clean up variables. + if ((_owner != null) && (_owner.getTrainedBeasts() != null)) + { + _owner.getTrainedBeasts().remove(this); + } + + setTarget(null); + _buffTask = null; + _durationCheckTask = null; + _owner = null; + _foodSkillId = 0; + _remainingTime = 0; + + // Remove the spawn. + return super.deleteMe(); + } + + // Notification triggered by the owner when the owner is attacked. + // Tamed mobs will heal/recharge or debuff the enemy according to their skills. + public void onOwnerGotAttacked(Creature attacker) + { + // Check if the owner is no longer around...if so, despawn. + if ((_owner == null) || !_owner.isOnline()) + { + deleteMe(); + return; + } + + // If the owner is too far away, stop anything else and immediately run towards the owner. + if (!_owner.isInsideRadius3D(this, MAX_DISTANCE_FROM_OWNER)) + { + getAI().startFollow(_owner); + return; + } + + // If the owner is dead, do nothing... + if (_owner.isDead() || _isFreyaBeast) + { + return; + } + + // If the tamed beast is currently in the middle of casting, let it complete its skill... + if (isCastingNow()) + { + return; + } + + final float HPRatio = ((float) _owner.getCurrentHp()) / _owner.getMaxHp(); + + // If the owner has a lot of HP, then debuff the enemy with a random debuff among the available skills. + // Use of more than one debuff at this moment is acceptable. + if (HPRatio >= 0.8) + { + for (Skill skill : getTemplate().getSkills().values()) + { + // if the skill is a debuff, check if the attacker has it already [ attacker.getEffect(Skill skill) ] + if (skill.isDebuff() && (Rnd.get(3) < 1) && ((attacker != null) && attacker.isAffectedBySkill(skill.getId()))) + { + sitCastAndFollow(skill, attacker); + } + } + } + + // for HP levels between 80% and 50%, do not react to attack events (so that MP can regenerate a bit) + // for lower HP ranges, heal or recharge the owner with 1 skill use per attack. + else if (HPRatio < 0.5) + { + int chance = 1; + if (HPRatio < 0.25) + { + chance = 2; + } + + // If the owner has a lot of HP, then debuff the enemy with a random debuff among the available skills. + for (Skill skill : getTemplate().getSkills().values()) + { + // If the skill is a buff, check if the owner has it already [ owner.getEffect(Skill skill) ]. + if ((Rnd.get(5) < chance) && skill.hasEffectType(EffectType.CPHEAL, EffectType.HEAL, EffectType.MANAHEAL_BY_LEVEL, EffectType.MANAHEAL_PERCENT)) + { + sitCastAndFollow(skill, _owner); + } + } + } + } + + /** + * Prepare and cast a skill:
    + * First smoothly prepare the beast for casting, by abandoning other actions.
    + * Next, call super.doCast(skill) in order to actually cast the spell.
    + * Finally, return to auto-following the owner. + * @param skill + * @param target + */ + protected void sitCastAndFollow(Skill skill, Creature target) + { + stopMove(null); + broadcastPacket(new StopMove(this)); + getAI().setIntentionIdle(); + + setTarget(target); + doCast(skill); + getAI().setIntentionFollow(_owner); + } + + private static class CheckDuration implements Runnable + { + private final TamedBeast _tamedBeast; + + CheckDuration(TamedBeast tamedBeast) + { + _tamedBeast = tamedBeast; + } + + @Override + public void run() + { + final int foodTypeSkillId = _tamedBeast.getFoodType(); + final Player owner = _tamedBeast.getOwner(); + Item item = null; + if (_tamedBeast._isFreyaBeast) + { + item = owner.getInventory().getItemByItemId(foodTypeSkillId); + if ((item != null) && (item.getCount() >= 1)) + { + owner.destroyItem(ItemProcessType.DESTROY, item, 1, _tamedBeast, true); + _tamedBeast.broadcastPacket(new SocialAction(_tamedBeast.getObjectId(), 3)); + } + else + { + _tamedBeast.deleteMe(); + } + } + else + { + _tamedBeast.setRemainingTime(_tamedBeast.getRemainingTime() - DURATION_CHECK_INTERVAL); + + // I tried to avoid this as much as possible...but it seems I can't avoid hardcoding. + // ids further, except by carrying an additional variable just for these two lines... + // Find which food item needs to be consumed. + if (foodTypeSkillId == 2188) + { + item = owner.getInventory().getItemByItemId(6643); + } + else if (foodTypeSkillId == 2189) + { + item = owner.getInventory().getItemByItemId(6644); + } + + // If the owner has enough food, call the item handler (use the food and triffer all necessary actions). + if ((item != null) && (item.getCount() >= 1)) + { + final WorldObject oldTarget = owner.getTarget(); + owner.setTarget(_tamedBeast); + + // Emulate a call to the owner using food, but bypass all checks for range, etc. + // This also causes a call to the AI tasks handling feeding, which may call onReceiveFood as required. + owner.callSkill(SkillData.getInstance().getSkill(foodTypeSkillId, 1), Collections.singletonList(_tamedBeast)); + owner.setTarget(oldTarget); + } + else + { + // if the owner has no food, the beast immediately despawns, except when it was only + // newly spawned. Newly spawned beasts can last up to 5 minutes + if (_tamedBeast.getRemainingTime() < (MAX_DURATION - 300000)) + { + _tamedBeast.setRemainingTime(-1); + } + } + + // There are too many conflicting reports about whether distance from home should be taken into consideration. Disabled for now. + // if (_tamedBeast.isTooFarFromHome()) + // _tamedBeast.setRemainingTime(-1); + if (_tamedBeast.getRemainingTime() <= 0) + { + _tamedBeast.deleteMe(); + } + } + } + } + + private class CheckOwnerBuffs implements Runnable + { + private final TamedBeast _tamedBeast; + private final int _numBuffs; + + CheckOwnerBuffs(TamedBeast tamedBeast, int numBuffs) + { + _tamedBeast = tamedBeast; + _numBuffs = numBuffs; + } + + @Override + public void run() + { + final Player owner = _tamedBeast.getOwner(); + + // Check if the owner is no longer around...if so, despawn. + if ((owner == null) || !owner.isOnline()) + { + deleteMe(); + return; + } + + // If the owner is too far away, stop anything else and immediately run towards the owner. + if (!isInsideRadius3D(owner, MAX_DISTANCE_FROM_OWNER)) + { + getAI().startFollow(owner); + return; + } + + // If the owner is dead, do nothing... + if (owner.isDead()) + { + return; + } + + // If the tamed beast is currently casting a spell, do not interfere (do not attempt to cast anything new yet). + if (isCastingNow()) + { + return; + } + + int totalBuffsOnOwner = 0; + int i = 0; + final int rand = Rnd.get(_numBuffs); + Skill buffToGive = null; + + // Get this npc's skills: getSkills(). + for (Skill skill : _tamedBeast.getTemplate().getSkills().values()) + { + // If the skill is a buff, check if the owner has it already [ owner.getEffect(Skill skill) ]. + if (skill.isContinuous() && !skill.isDebuff()) + { + if (i++ == rand) + { + buffToGive = skill; + } + + if (owner.isAffectedBySkill(skill.getId())) + { + totalBuffsOnOwner++; + } + } + } + + // If the owner has less than 60% of this beast's available buff, cast a random buff. + if (((_numBuffs * 2) / 3) > totalBuffsOnOwner) + { + _tamedBeast.sitCastAndFollow(buffToGive, owner); + } + + getAI().setIntentionFollow(_tamedBeast.getOwner()); + } + } + + @Override + public void onAction(Player player, boolean interact) + { + if ((player == null) || !canTarget(player)) + { + return; + } + + // Check if the Player already target the Npc. + if (this != player.getTarget()) + { + // Set the target of the Player player. + player.setTarget(this); + } + else if (interact) + { + if (isAutoAttackable(player) && (Math.abs(player.getZ() - getZ()) < 100)) + { + player.getAI().setIntentionAttack(this); + } + else + { + // Send a Server->Client ActionFailed to the Player in order to avoid that the client wait another packet. + player.sendPacket(ActionFailed.STATIC_PACKET); + } + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/Teleporter.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/Teleporter.java new file mode 100644 index 0000000..eba5fdb --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/Teleporter.java @@ -0,0 +1,180 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.actor.instance; + +import java.util.StringTokenizer; +import java.util.logging.Logger; + +import org.l2jmobius.commons.util.StringUtil; +import org.l2jmobius.gameserver.data.xml.TeleporterData; +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.enums.creature.InstanceType; +import org.l2jmobius.gameserver.entity.actor.enums.player.TeleportType; +import org.l2jmobius.gameserver.entity.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.entity.teleporter.TeleportHolder; +import org.l2jmobius.gameserver.managers.CastleManager; +import org.l2jmobius.gameserver.network.serverpackets.NpcHtmlMessage; + +/** + * @author NightMarez + */ +public class Teleporter extends Merchant +{ + private static final Logger LOGGER = Logger.getLogger(Teleporter.class.getName()); + + public Teleporter(NpcTemplate template) + { + super(template); + setInstanceType(InstanceType.Teleporter); + } + + @Override + public boolean isAutoAttackable(Creature attacker) + { + return attacker.isMonster() || super.isAutoAttackable(attacker); + } + + @Override + public void onBypassFeedback(Player player, String command) + { + // Process bypass + final StringTokenizer st = new StringTokenizer(command, " "); + switch (st.nextToken()) + { + case "showNoblesSelect": + { + sendHtmlMessage(player, "data/html/teleporter/" + (player.isNoble() ? "nobles_select" : "not_nobles") + ".htm"); + break; + } + case "showTeleports": + { + final String listName = (st.hasMoreTokens()) ? st.nextToken() : TeleportType.NORMAL.name(); + final TeleportHolder holder = TeleporterData.getInstance().getHolder(getId(), listName); + if (holder == null) + { + LOGGER.warning(player + " requested show teleports for list with name " + listName + " at NPC " + getId() + "!"); + return; + } + + holder.showTeleportList(player, this); + break; + } + case "teleport": + { + // Check for required count of params. + if (st.countTokens() != 2) + { + LOGGER.warning(player + " send unhandled teleport command: " + command); + return; + } + + final String listName = st.nextToken(); + final TeleportHolder holder = TeleporterData.getInstance().getHolder(getId(), listName); + if (holder == null) + { + LOGGER.warning(player + " requested unknown teleport list: " + listName + " for npc: " + getId() + "!"); + return; + } + + holder.doTeleport(player, this, parseNextInt(st, -1)); + break; + } + case "chat": + { + int val = 0; + try + { + val = Integer.parseInt(command.substring(5)); + } + catch (IndexOutOfBoundsException | NumberFormatException ignored) + { + } + + showChatWindow(player, val); + break; + } + default: + { + super.onBypassFeedback(player, command); + } + } + } + + private int parseNextInt(StringTokenizer st, int defaultVal) + { + if (st.hasMoreTokens()) + { + final String token = st.nextToken(); + if (StringUtil.isNumeric(token)) + { + return Integer.parseInt(token); + } + } + + return defaultVal; + } + + @Override + public String getHtmlPath(int npcId, int value) + { + String pom; + if (value == 0) + { + pom = String.valueOf(npcId); + } + else + { + pom = (npcId + "-" + value); + } + + return "data/html/teleporter/" + pom + ".htm"; + } + + @Override + public void showChatWindow(Player player) + { + // Teleporter isn't on castle ground. + if (CastleManager.getInstance().getCastle(this) == null) + { + super.showChatWindow(player); + return; + } + + // Teleporter is on castle ground. + String filename = "data/html/teleporter/castleteleporter-no.htm"; + if ((player.getClan() != null) && (getCastle().getOwnerId() == player.getClanId())) // Clan owns castle + { + filename = getHtmlPath(getId(), 0); // Owner message window + } + else if (getCastle().getSiege().isInProgress()) // Teleporter is busy due siege. + { + filename = "data/html/teleporter/castleteleporter-busy.htm"; // Busy because of siege + } + + sendHtmlMessage(player, filename); + } + + private void sendHtmlMessage(Player player, String filename) + { + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, filename); + html.replace("%objectId%", String.valueOf(getObjectId())); + html.replace("%npcname%", getName()); + player.sendPacket(html); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/TerrainObject.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/TerrainObject.java new file mode 100644 index 0000000..291d6c8 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/TerrainObject.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.instance; + +import org.l2jmobius.gameserver.entity.actor.Npc; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.enums.creature.InstanceType; +import org.l2jmobius.gameserver.entity.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.network.serverpackets.ActionFailed; + +public class TerrainObject extends Npc +{ + /** + * Creates a terrain object. + * @param template the terrain object NPC template + */ + public TerrainObject(NpcTemplate template) + { + super(template); + setInstanceType(InstanceType.TerrainObject); + } + + @Override + public void onAction(Player player, boolean interact) + { + player.sendPacket(ActionFailed.STATIC_PACKET); + } + + @Override + public void onActionShift(Player player) + { + if (player.isGM()) + { + super.onActionShift(player); + } + else + { + player.sendPacket(ActionFailed.STATIC_PACKET); + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/Trainer.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/Trainer.java new file mode 100644 index 0000000..f67291a --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/Trainer.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.instance; + +import org.l2jmobius.gameserver.entity.actor.enums.creature.InstanceType; +import org.l2jmobius.gameserver.entity.actor.templates.NpcTemplate; + +public class Trainer extends Folk +{ + /** + * Creates a trainer. + * @param template the trainer NPC template + */ + public Trainer(NpcTemplate template) + { + super(template); + setInstanceType(InstanceType.Trainer); + } + + @Override + public String getHtmlPath(int npcId, int value) + { + String pom = ""; + if (value == 0) + { + pom = Integer.toString(npcId); + } + else + { + pom = npcId + "-" + value; + } + + return "data/html/trainer/" + pom + ".htm"; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/Trap.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/Trap.java new file mode 100644 index 0000000..b29c27d --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/Trap.java @@ -0,0 +1,446 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.actor.instance; + +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.ScheduledFuture; + +import org.l2jmobius.commons.threads.ThreadPool; +import org.l2jmobius.gameserver.entity.World; +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.Npc; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.enums.creature.InstanceType; +import org.l2jmobius.gameserver.entity.actor.enums.creature.TrapAction; +import org.l2jmobius.gameserver.entity.actor.tasks.npc.trap.TrapTask; +import org.l2jmobius.gameserver.entity.actor.tasks.npc.trap.TrapTriggerTask; +import org.l2jmobius.gameserver.entity.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.entity.item.Weapon; +import org.l2jmobius.gameserver.entity.item.instance.Item; +import org.l2jmobius.gameserver.entity.zone.ZoneId; +import org.l2jmobius.gameserver.managers.ZoneManager; +import org.l2jmobius.gameserver.mechanics.events.EventDispatcher; +import org.l2jmobius.gameserver.mechanics.events.EventType; +import org.l2jmobius.gameserver.mechanics.events.holders.actor.trap.OnTrapAction; +import org.l2jmobius.gameserver.mechanics.olympiad.Olympiad; +import org.l2jmobius.gameserver.mechanics.skill.Skill; +import org.l2jmobius.gameserver.mechanics.skill.holders.SkillHolder; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.serverpackets.AbstractNpcInfo.TrapInfo; +import org.l2jmobius.gameserver.network.serverpackets.ServerPacket; +import org.l2jmobius.gameserver.taskmanagers.DecayTaskManager; + +/** + * Trap instance. + * @author nBd + */ +public class Trap extends Npc +{ + private static final int TICK = 1000; // 1s + private boolean _hasLifeTime; + private boolean _isInArena = false; + private boolean _isTriggered; + private final int _lifeTime; + private Player _owner; + private final Set _playersWhoDetectedMe = new HashSet<>(); + private final SkillHolder _skill; + private int _remainingTime; + + // Tasks + private ScheduledFuture _trapTask = null; + + /** + * Creates a trap. + * @param template the trap NPC template + * @param instanceId the instance ID + * @param lifeTime the life time + */ + public Trap(NpcTemplate template, int instanceId, int lifeTime) + { + super(template); + setInstanceType(InstanceType.Trap); + setInstanceId(instanceId); + setName(template.getName()); + setInvul(false); + + _owner = null; + _isTriggered = false; + _skill = getTemplate().getParameters().getObject("trap_skill", SkillHolder.class); + _hasLifeTime = lifeTime >= 0; + _lifeTime = lifeTime != 0 ? lifeTime : 30000; + _remainingTime = _lifeTime; + if (_skill != null) + { + _trapTask = ThreadPool.scheduleAtFixedRate(new TrapTask(this), TICK, TICK); + } + } + + /** + * Creates a trap. + * @param template the trap NPC template + * @param owner the owner + * @param lifeTime the life time + */ + public Trap(NpcTemplate template, Player owner, int lifeTime) + { + this(template, owner.getInstanceId(), lifeTime); + _owner = owner; + } + + @Override + public void broadcastPacket(ServerPacket packet) + { + World.forEachVisibleObject(this, Player.class, player -> + { + if (_isTriggered || canBeSeen(player)) + { + player.sendPacket(packet); + } + }); + } + + /** + * Verify if the character can see the trap. + * @param creature The creature to verify + * @return {@code true} if the character can see the trap, {@code false} otherwise + */ + public boolean canBeSeen(Creature creature) + { + if ((creature != null) && _playersWhoDetectedMe.contains(creature.getObjectId())) + { + return true; + } + + if ((_owner == null) || (creature == null)) + { + return false; + } + + if (creature == _owner) + { + return true; + } + + if (creature.isPlayer()) + { + // observers can't see trap + if (creature.asPlayer().inObserverMode()) + { + return false; + } + + // olympiad competitors can't see trap + if (_owner.isInOlympiadMode() && creature.asPlayer().isInOlympiadMode() && (creature.asPlayer().getOlympiadSide() != _owner.getOlympiadSide())) + { + return false; + } + } + + if (_isInArena) + { + return true; + } + + if (_owner.isInParty() && creature.isInParty() && (_owner.getParty().getLeaderObjectId() == creature.getParty().getLeaderObjectId())) + { + return true; + } + + return false; + } + + public boolean checkTarget(Creature target) + { + // Range seems to be reduced from Freya(300) to H5(150). + if (!target.isInsideRadius2D(this, 150)) + { + return false; + } + + if (!Skill.checkForAreaOffensiveSkills(this, target, _skill.getSkill(), _isInArena)) + { + return false; + } + + // observers + final Player player = target.asPlayer(); + if (target.isPlayer() && player.inObserverMode()) + { + return false; + } + + // olympiad own team and their summons not attacked + if ((_owner != null) && _owner.isInOlympiadMode() && (player != null) && player.isInOlympiadMode() && (player.getOlympiadSide() == _owner.getOlympiadSide())) + { + return false; + } + + if (_isInArena) + { + return true; + } + + // trap owned by players not attack non-flagged players + if (_owner != null) + { + if (target.isAttackable()) + { + return true; + } + + if ((player == null) || ((player.getPvpFlag() == 0) && (player.getKarma() == 0))) + { + return false; + } + } + + return true; + } + + @Override + public boolean deleteMe() + { + if (_owner != null) + { + _owner.setTrap(null); + _owner = null; + } + + return super.deleteMe(); + } + + @Override + public Player asPlayer() + { + return _owner; + } + + @Override + public Weapon getActiveWeaponItem() + { + return null; + } + + @Override + public int getKarma() + { + return _owner != null ? _owner.getKarma() : 0; + } + + /** + * Get the owner of this trap. + * @return the owner + */ + public Player getOwner() + { + return _owner; + } + + public byte getPvpFlag() + { + return _owner != null ? _owner.getPvpFlag() : 0; + } + + @Override + public Item getSecondaryWeaponInstance() + { + return null; + } + + @Override + public Weapon getSecondaryWeaponItem() + { + return null; + } + + public Skill getSkill() + { + return _skill.getSkill(); + } + + @Override + public boolean isAutoAttackable(Creature attacker) + { + return !canBeSeen(attacker); + } + + @Override + public boolean isTrap() + { + return true; + } + + /** + * Checks is triggered + * @return True if trap is triggered. + */ + public boolean isTriggered() + { + return _isTriggered; + } + + @Override + public void onSpawn() + { + super.onSpawn(); + _isInArena = isInsideZone(ZoneId.PVP) && !isInsideZone(ZoneId.SIEGE); + _playersWhoDetectedMe.clear(); + } + + @Override + public void sendDamageMessage(Creature target, int damage, boolean mcrit, boolean pcrit, boolean miss) + { + if (miss || (_owner == null)) + { + return; + } + + if (_owner.isInOlympiadMode() && target.isPlayer() && target.asPlayer().isInOlympiadMode() && (target.asPlayer().getOlympiadGameId() == _owner.getOlympiadGameId())) + { + Olympiad.getInstance().notifyCompetitorDamage(getOwner(), damage, _owner.getOlympiadGameId()); + } + + if (target.isInvul() && !target.isNpc()) + { + _owner.sendPacket(SystemMessageId.THE_ATTACK_HAS_BEEN_BLOCKED); + } + else + { + _owner.sendMessage(getName() + " has done " + damage + " points of damage to " + target.getName() + "."); + } + } + + @Override + public void sendInfo(Player player) + { + if (_isTriggered || canBeSeen(player)) + { + player.sendPacket(new TrapInfo(this, player)); + } + } + + public void setDetected(Creature detector) + { + if (_isInArena) + { + if (detector.isPlayable()) + { + sendInfo(detector.asPlayer()); + } + return; + } + + if ((_owner != null) && (_owner.getPvpFlag() == 0) && (_owner.getKarma() == 0)) + { + return; + } + + _playersWhoDetectedMe.add(detector.getObjectId()); + + // Notify to scripts. + if (EventDispatcher.getInstance().hasListener(EventType.ON_TRAP_ACTION, this)) + { + EventDispatcher.getInstance().notifyEventAsync(new OnTrapAction(this, detector, TrapAction.TRAP_DETECTED), this); + } + + if (detector.isPlayable()) + { + sendInfo(detector.asPlayer()); + } + } + + public void stopDecay() + { + DecayTaskManager.getInstance().cancel(this); + } + + /** + * Trigger the trap. + * @param target the target + */ + public void triggerTrap(Creature target) + { + if (_trapTask != null) + { + _trapTask.cancel(true); + _trapTask = null; + } + + _isTriggered = true; + broadcastPacket(new TrapInfo(this, null)); + setTarget(target); + + if (EventDispatcher.getInstance().hasListener(EventType.ON_TRAP_ACTION, this)) + { + EventDispatcher.getInstance().notifyEventAsync(new OnTrapAction(this, target, TrapAction.TRAP_TRIGGERED), this); + } + + ThreadPool.schedule(new TrapTriggerTask(this), 500); + } + + public void unSummon() + { + if (_trapTask != null) + { + _trapTask.cancel(true); + _trapTask = null; + } + + if (_owner != null) + { + _owner.setTrap(null); + _owner = null; + } + + if (isSpawned() && !isDead()) + { + ZoneManager.getInstance().getRegion(this).removeFromZones(this); + deleteMe(); + } + } + + @Override + public void updateAbnormalEffect() + { + } + + public boolean hasLifeTime() + { + return _hasLifeTime; + } + + public void setHasLifeTime(boolean value) + { + _hasLifeTime = value; + } + + public int getRemainingTime() + { + return _remainingTime; + } + + public void setRemainingTime(int time) + { + _remainingTime = time; + } + + public int getLifeTime() + { + return _lifeTime; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/VillageMaster.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/VillageMaster.java new file mode 100644 index 0000000..1cb1207 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/VillageMaster.java @@ -0,0 +1,1267 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.instance; + +import java.util.EnumMap; +import java.util.EnumSet; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.logging.Logger; +import java.util.stream.Collectors; + +import org.l2jmobius.commons.util.StringUtil; +import org.l2jmobius.gameserver.config.PlayerConfig; +import org.l2jmobius.gameserver.config.ServerConfig; +import org.l2jmobius.gameserver.data.enums.CategoryType; +import org.l2jmobius.gameserver.data.sql.ClanTable; +import org.l2jmobius.gameserver.data.xml.CategoryData; +import org.l2jmobius.gameserver.data.xml.ClassListData; +import org.l2jmobius.gameserver.data.xml.SkillTreeData; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.enums.creature.InstanceType; +import org.l2jmobius.gameserver.entity.actor.enums.creature.Race; +import org.l2jmobius.gameserver.entity.actor.enums.player.PlayerClass; +import org.l2jmobius.gameserver.entity.actor.holders.player.ClassType; +import org.l2jmobius.gameserver.entity.actor.holders.player.SubClassHolder; +import org.l2jmobius.gameserver.entity.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.entity.clan.Clan; +import org.l2jmobius.gameserver.entity.clan.Clan.SubPledge; +import org.l2jmobius.gameserver.entity.clan.ClanMember; +import org.l2jmobius.gameserver.entity.zone.ZoneId; +import org.l2jmobius.gameserver.managers.CastleManager; +import org.l2jmobius.gameserver.managers.SiegeManager; +import org.l2jmobius.gameserver.mechanics.olympiad.Olympiad; +import org.l2jmobius.gameserver.mechanics.script.QuestState; +import org.l2jmobius.gameserver.mechanics.siege.Castle; +import org.l2jmobius.gameserver.mechanics.skill.enums.AcquireSkillType; +import org.l2jmobius.gameserver.mechanics.skill.holders.SkillLearn; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.serverpackets.AcquireSkillList; +import org.l2jmobius.gameserver.network.serverpackets.ActionFailed; +import org.l2jmobius.gameserver.network.serverpackets.MagicSkillLaunched; +import org.l2jmobius.gameserver.network.serverpackets.MagicSkillUse; +import org.l2jmobius.gameserver.network.serverpackets.NpcHtmlMessage; +import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; + +/** + * @version $Revision: 1.4.2.3.2.8 $ $Date: 2005/03/29 23:15:15 $ + */ +public class VillageMaster extends Folk +{ + private static final Logger LOGGER = Logger.getLogger(VillageMaster.class.getName()); + + private static final Set mainSubclassSet; + private static final Set neverSubclassed = EnumSet.of(PlayerClass.OVERLORD, PlayerClass.WARSMITH); + private static final Set subclasseSet1 = EnumSet.of(PlayerClass.DARK_AVENGER, PlayerClass.PALADIN, PlayerClass.TEMPLE_KNIGHT, PlayerClass.SHILLIEN_KNIGHT); + private static final Set subclasseSet2 = EnumSet.of(PlayerClass.TREASURE_HUNTER, PlayerClass.ABYSS_WALKER, PlayerClass.PLAINS_WALKER); + private static final Set subclasseSet3 = EnumSet.of(PlayerClass.HAWKEYE, PlayerClass.SILVER_RANGER, PlayerClass.PHANTOM_RANGER); + private static final Set subclasseSet4 = EnumSet.of(PlayerClass.WARLOCK, PlayerClass.ELEMENTAL_SUMMONER, PlayerClass.PHANTOM_SUMMONER); + private static final Set subclasseSet5 = EnumSet.of(PlayerClass.SORCERER, PlayerClass.SPELLSINGER, PlayerClass.SPELLHOWLER); + private static final EnumMap> subclassSetMap = new EnumMap<>(PlayerClass.class); + static + { + final Set subclasses = CategoryData.getInstance().getCategoryByType(CategoryType.THIRD_CLASS_GROUP).stream().map(PlayerClass::getPlayerClass).collect(Collectors.toSet()); + subclasses.removeAll(neverSubclassed); + mainSubclassSet = subclasses; + subclassSetMap.put(PlayerClass.DARK_AVENGER, subclasseSet1); + subclassSetMap.put(PlayerClass.PALADIN, subclasseSet1); + subclassSetMap.put(PlayerClass.TEMPLE_KNIGHT, subclasseSet1); + subclassSetMap.put(PlayerClass.SHILLIEN_KNIGHT, subclasseSet1); + subclassSetMap.put(PlayerClass.TREASURE_HUNTER, subclasseSet2); + subclassSetMap.put(PlayerClass.ABYSS_WALKER, subclasseSet2); + subclassSetMap.put(PlayerClass.PLAINS_WALKER, subclasseSet2); + subclassSetMap.put(PlayerClass.HAWKEYE, subclasseSet3); + subclassSetMap.put(PlayerClass.SILVER_RANGER, subclasseSet3); + subclassSetMap.put(PlayerClass.PHANTOM_RANGER, subclasseSet3); + subclassSetMap.put(PlayerClass.WARLOCK, subclasseSet4); + subclassSetMap.put(PlayerClass.ELEMENTAL_SUMMONER, subclasseSet4); + subclassSetMap.put(PlayerClass.PHANTOM_SUMMONER, subclasseSet4); + subclassSetMap.put(PlayerClass.SORCERER, subclasseSet5); + subclassSetMap.put(PlayerClass.SPELLSINGER, subclasseSet5); + subclassSetMap.put(PlayerClass.SPELLHOWLER, subclasseSet5); + } + + /** + * Creates a village master. + * @param template the village master NPC template + */ + public VillageMaster(NpcTemplate template) + { + super(template); + setInstanceType(InstanceType.VillageMaster); + } + + @Override + public String getHtmlPath(int npcId, int value) + { + String pom = ""; + if (value == 0) + { + pom = Integer.toString(npcId); + } + else + { + pom = npcId + "-" + value; + } + + return "data/html/villagemaster/" + pom + ".htm"; + } + + @Override + public void onBypassFeedback(Player player, String command) + { + final String[] commandStr = command.split(" "); + final String actualCommand = commandStr[0]; // Get actual command + String cmdParams = ""; + String cmdParams2 = ""; + if (commandStr.length >= 2) + { + cmdParams = commandStr[1]; + } + + if (commandStr.length >= 3) + { + cmdParams2 = commandStr[2]; + } + + if (actualCommand.equalsIgnoreCase("create_clan")) + { + if (cmdParams.isEmpty()) + { + player.sendPacket(SystemMessageId.PLEASE_CREATE_YOUR_CLAN_NAME); + return; + } + + if (!cmdParams2.isEmpty() || !isValidName(cmdParams)) + { + player.sendPacket(SystemMessageId.CLAN_NAME_IS_INVALID); + return; + } + + ClanTable.getInstance().createClan(player, cmdParams); + } + else if (actualCommand.equalsIgnoreCase("create_academy")) + { + if (cmdParams.isEmpty()) + { + player.sendPacket(SystemMessageId.PLEASE_ENTER_THE_NAME_OF_THE_CLAN_ACADEMY); + return; + } + + createSubPledge(player, cmdParams, null, Clan.SUBUNIT_ACADEMY, 5); + } + else if (actualCommand.equalsIgnoreCase("rename_pledge")) + { + if (cmdParams.isEmpty() || cmdParams2.isEmpty()) + { + return; + } + + renameSubPledge(player, Integer.parseInt(cmdParams), cmdParams2); + } + else if (actualCommand.equalsIgnoreCase("create_royal")) + { + if (cmdParams.isEmpty()) + { + return; + } + + createSubPledge(player, cmdParams, cmdParams2, Clan.SUBUNIT_ROYAL1, 6); + } + else if (actualCommand.equalsIgnoreCase("create_knight")) + { + if (cmdParams.isEmpty()) + { + return; + } + + createSubPledge(player, cmdParams, cmdParams2, Clan.SUBUNIT_KNIGHT1, 7); + } + else if (actualCommand.equalsIgnoreCase("assign_subpl_leader")) + { + if (cmdParams.isEmpty()) + { + return; + } + + assignSubPledgeLeader(player, cmdParams, cmdParams2); + } + else if (actualCommand.equalsIgnoreCase("create_ally")) + { + if (cmdParams.isEmpty()) + { + return; + } + + if (player.getClan() == null) + { + player.sendPacket(SystemMessageId.ONLY_CLAN_LEADERS_MAY_CREATE_ALLIANCES); + } + else + { + player.getClan().createAlly(player, cmdParams); + } + } + else if (actualCommand.equalsIgnoreCase("dissolve_ally")) + { + player.getClan().dissolveAlly(player); + } + else if (actualCommand.equalsIgnoreCase("dissolve_clan")) + { + dissolveClan(player, player.getClanId()); + } + else if (actualCommand.equalsIgnoreCase("change_clan_leader")) + { + if (cmdParams.isEmpty()) + { + return; + } + + if (!player.isClanLeader()) + { + player.sendPacket(SystemMessageId.YOU_ARE_NOT_AUTHORIZED_TO_DO_THAT); + return; + } + + if (player.getName().equalsIgnoreCase(cmdParams)) + { + return; + } + + final Clan clan = player.getClan(); + final ClanMember member = clan.getClanMember(cmdParams); + if (member == null) + { + final SystemMessage sm = new SystemMessage(SystemMessageId.S1_DOES_NOT_EXIST); + sm.addString(cmdParams); + player.sendPacket(sm); + return; + } + + if (!member.isOnline()) + { + player.sendPacket(SystemMessageId.THAT_PLAYER_IS_NOT_CURRENTLY_ONLINE); + return; + } + + // To avoid clans with null clan leader, academy members shouldn't be eligible for clan leader. + if (member.getPlayer().isAcademyMember()) + { + player.sendPacket(SystemMessageId.THAT_PRIVILEGE_CANNOT_BE_GRANTED_TO_A_CLAN_ACADEMY_MEMBER); + return; + } + + if (PlayerConfig.ALT_CLAN_LEADER_INSTANT_ACTIVATION) + { + clan.setNewLeader(member); + } + else + { + final NpcHtmlMessage msg = new NpcHtmlMessage(getObjectId()); + if (clan.getNewLeaderId() == 0) + { + clan.setNewLeaderId(member.getObjectId(), true); + msg.setFile(player, "data/scripts/village_master/ClanMaster/9000-07-success.htm"); + } + else + { + msg.setFile(player, "data/scripts/village_master/ClanMaster/9000-07-in-progress.htm"); + } + + player.sendPacket(msg); + } + } + else if (actualCommand.equalsIgnoreCase("cancel_clan_leader_change")) + { + if (!player.isClanLeader()) + { + player.sendPacket(SystemMessageId.YOU_ARE_NOT_AUTHORIZED_TO_DO_THAT); + return; + } + + final Clan clan = player.getClan(); + final NpcHtmlMessage msg = new NpcHtmlMessage(getObjectId()); + if (clan.getNewLeaderId() != 0) + { + clan.setNewLeaderId(0, true); + msg.setFile(player, "data/scripts/village_master/ClanMaster/9000-07-canceled.htm"); + } + else + { + msg.setHtml("You don't have clan leader delegation applications submitted yet!"); + } + + player.sendPacket(msg); + } + else if (actualCommand.equalsIgnoreCase("recover_clan")) + { + recoverClan(player, player.getClanId()); + } + else if (actualCommand.equalsIgnoreCase("increase_clan_level")) + { + if (player.getClan().levelUpClan(player)) + { + player.broadcastSkillPacket(new MagicSkillUse(player, 5103, 1, 0, 0), player); + player.broadcastSkillPacket(new MagicSkillLaunched(player, 5103, 1), player); + } + } + else if (actualCommand.equalsIgnoreCase("learn_clan_skills")) + { + showPledgeSkillList(player); + } + else if (command.startsWith("Subclass")) + { + // Subclasses may not be changed while a skill is in use. + if (player.isCastingNow() || player.isAllSkillsDisabled()) + { + player.sendPacket(SystemMessageId.SUB_CLASSES_MAY_NOT_BE_CREATED_OR_CHANGED_WHILE_A_SKILL_IS_IN_USE); + return; + } + + if (Olympiad.getInstance().isRegisteredInComp(player)) + { + Olympiad.getInstance().unRegisterNoble(player); + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + + // Subclasses may not be changed while a summon is active. + if (player.hasSummon()) + { + html.setFile(player, "data/html/villagemaster/SubClass_NoSummon.htm"); + player.sendPacket(html); + return; + } + + // Subclasses may not be changed while you have exceeded your inventory limit. + if (!player.isInventoryUnder90(true)) + { + player.sendPacket(SystemMessageId.A_SUB_CLASS_CANNOT_BE_CREATED_OR_CHANGED_BECAUSE_YOU_HAVE_EXCEEDED_YOUR_INVENTORY_LIMIT); + return; + } + + // Subclasses may not be changed while a you are over your weight limit. + if (player.getWeightPenalty() >= 2) + { + player.sendPacket(SystemMessageId.A_SUB_CLASS_CANNOT_BE_CREATED_OR_CHANGED_WHILE_YOU_ARE_OVER_YOUR_WEIGHT_LIMIT); + return; + } + + int cmdChoice = 0; + int paramOne = 0; + int paramTwo = 0; + try + { + cmdChoice = Integer.parseInt(command.substring(9, 10).trim()); + int endIndex = command.indexOf(' ', 11); + if (endIndex == -1) + { + endIndex = command.length(); + } + + if (command.length() > 11) + { + paramOne = Integer.parseInt(command.substring(11, endIndex).trim()); + if (command.length() > endIndex) + { + paramTwo = Integer.parseInt(command.substring(endIndex).trim()); + } + } + } + catch (Exception nfe) + { + LOGGER.warning(VillageMaster.class.getName() + ": Wrong numeric values for command " + command); + } + + Set subsAvailable = null; + switch (cmdChoice) + { + case 0: // Subclass change menu + { + html.setFile(player, getSubClassMenu(player.getRace())); + break; + } + case 1: // Add Subclass - Initial + { + // Avoid giving player an option to add a new sub class, if they have max sub-classes already. + if (player.getTotalSubClasses() >= PlayerConfig.MAX_SUBCLASS) + { + html.setFile(player, getSubClassFail()); + break; + } + + subsAvailable = getAvailableSubClasses(player); + if ((subsAvailable != null) && !subsAvailable.isEmpty()) + { + html.setFile(player, "data/html/villagemaster/SubClass_Add.htm"); + final StringBuilder content1 = new StringBuilder(200); + for (PlayerClass subClass : subsAvailable) + { + content1.append("" + ClassListData.getInstance().getClass(subClass.getId()).getClassName() + "
    "); + } + + html.replace("%list%", content1.toString()); + } + else + { + player.sendMessage("There are no sub classes available at this time."); + return; + } + break; + } + case 2: // Change Class - Initial + { + if (player.getSubClasses().isEmpty()) + { + html.setFile(player, "data/html/villagemaster/SubClass_ChangeNo.htm"); + } + else + { + final StringBuilder content2 = new StringBuilder(200); + content2.append("" + ClassListData.getInstance().getClass(player.getBaseClass()).getClassName() + "
    "); + + for (Iterator subList = iterSubClasses(player); subList.hasNext();) + { + final SubClassHolder subClass = subList.next(); + content2.append("" + ClassListData.getInstance().getClass(subClass.getId()).getClassName() + "
    "); + } + + if (!content2.isEmpty()) + { + html.setFile(player, "data/html/villagemaster/SubClass_Change.htm"); + html.replace("%list%", content2.toString()); + } + else + { + html.setFile(player, "data/html/villagemaster/SubClass_ChangeNotFound.htm"); + } + } + break; + } + case 3: // Change/Cancel Subclass - Initial + { + if ((player.getSubClasses() == null) || player.getSubClasses().isEmpty()) + { + html.setFile(player, "data/html/villagemaster/SubClass_ModifyEmpty.htm"); + break; + } + + // custom value + if (player.getTotalSubClasses() > 3) + { + html.setFile(player, "data/html/villagemaster/SubClass_ModifyCustom.htm"); + final StringBuilder content3 = new StringBuilder(200); + int classIndex = 1; + for (Iterator subList = iterSubClasses(player); subList.hasNext();) + { + final SubClassHolder subClass = subList.next(); + content3.append("Sub-class " + classIndex++ + "
    " + ClassListData.getInstance().getClass(subClass.getId()).getClassName() + "
    "); + } + + html.replace("%list%", content3.toString()); + } + else + { + // retail html contain only 3 subclasses + html.setFile(player, "data/html/villagemaster/SubClass_Modify.htm"); + if (player.getSubClasses().containsKey(1)) + { + html.replace("%sub1%", ClassListData.getInstance().getClass(player.getSubClasses().get(1).getId()).getClassName()); + } + else + { + html.replace("%sub1%
    ", ""); + } + + if (player.getSubClasses().containsKey(2)) + { + html.replace("%sub2%", ClassListData.getInstance().getClass(player.getSubClasses().get(2).getId()).getClassName()); + } + else + { + html.replace("%sub2%
    ", ""); + } + + if (player.getSubClasses().containsKey(3)) + { + html.replace("%sub3%", ClassListData.getInstance().getClass(player.getSubClasses().get(3).getId()).getClassName()); + } + else + { + html.replace("%sub3%
    ", ""); + } + } + break; + } + case 4: // Add Subclass - Action (Subclass 4 x[x]) + { + /** + * If the character is less than level 75 on any of their previously chosen classes then disallow them to change to their most recently added sub-class choice. + */ + if (!player.getClient().getFloodProtectors().canChangeSubclass()) + { + return; + } + + boolean allowAddition = true; + if (player.getTotalSubClasses() >= PlayerConfig.MAX_SUBCLASS) + { + allowAddition = false; + } + + if (player.getLevel() < 75) + { + allowAddition = false; + } + + if (allowAddition && !player.getSubClasses().isEmpty()) + { + for (Iterator subList = iterSubClasses(player); subList.hasNext();) + { + final SubClassHolder subClass = subList.next(); + if (subClass.getLevel() < 75) + { + allowAddition = false; + break; + } + } + } + + /** + * If quest checking is enabled, verify if the character has completed the Mimir's Elixir (Path to Subclass) and Fate's Whisper (A Grade Weapon) quests by checking for instances of their unique reward items. If they both exist, remove both unique items and continue with adding + * the sub-class. + */ + if (allowAddition && !PlayerConfig.ALT_GAME_SUBCLASS_WITHOUT_QUESTS) + { + allowAddition = checkQuests(player); + } + + if (allowAddition && isValidNewSubClass(player, paramOne)) + { + if (!player.addSubClass(paramOne, player.getTotalSubClasses() + 1)) + { + return; + } + + player.setActiveClass(player.getTotalSubClasses()); + + html.setFile(player, "data/html/villagemaster/SubClass_AddOk.htm"); + player.sendPacket(SystemMessageId.THE_NEW_SUB_CLASS_HAS_BEEN_ADDED); // Subclass added. + } + else + { + html.setFile(player, getSubClassFail()); + } + break; + } + case 5: // Change Class - Action + { + /** + * If the character is less than level 75 on any of their previously chosen classes then disallow them to change to their most recently added sub-class choice. Note: paramOne = classIndex + */ + if (!player.getClient().getFloodProtectors().canChangeSubclass()) + { + return; + } + + if (player.getClassIndex() == paramOne) + { + html.setFile(player, "data/html/villagemaster/SubClass_Current.htm"); + break; + } + + player.setActiveClass(paramOne); + player.sendPacket(SystemMessageId.THE_TRANSFER_OF_SUB_CLASS_HAS_BEEN_COMPLETED); // Transfer completed. + player.broadcastPacket(new MagicSkillUse(player, 5103, 1, 0, 0)); + return; + } + case 6: // Change/Cancel Subclass - Choice + { + // validity check + if ((paramOne < 1) || (paramOne > PlayerConfig.MAX_SUBCLASS)) + { + return; + } + + subsAvailable = getAvailableSubClasses(player); + + // another validity check + if ((subsAvailable == null) || subsAvailable.isEmpty()) + { + // TODO: Retail message + player.sendMessage("There are no sub classes available at this time."); + return; + } + + final StringBuilder content6 = new StringBuilder(200); + for (PlayerClass subClass : subsAvailable) + { + content6.append("" + ClassListData.getInstance().getClass(subClass.getId()).getClassName() + "
    "); + } + + switch (paramOne) + { + case 1: + { + html.setFile(player, "data/html/villagemaster/SubClass_ModifyChoice1.htm"); + break; + } + case 2: + { + html.setFile(player, "data/html/villagemaster/SubClass_ModifyChoice2.htm"); + break; + } + case 3: + { + html.setFile(player, "data/html/villagemaster/SubClass_ModifyChoice3.htm"); + break; + } + default: + { + html.setFile(player, "data/html/villagemaster/SubClass_ModifyChoice.htm"); + } + } + + html.replace("%list%", content6.toString()); + break; + } + case 7: // Change Subclass - Action + { + /** + * Warning: the information about this subclass will be removed from the subclass list even if false! + */ + if (!player.getClient().getFloodProtectors().canChangeSubclass()) + { + return; + } + + if (!isValidNewSubClass(player, paramTwo)) + { + return; + } + + if (player.modifySubClass(paramOne, paramTwo)) + { + player.abortCast(); + player.stopAllEffectsExceptThoseThatLastThroughDeath(); // all effects from old subclass stopped! + player.stopAllEffectsNotStayOnSubclassChange(); + player.stopCubics(); + player.setActiveClass(paramOne); + + html.setFile(player, "data/html/villagemaster/SubClass_ModifyOk.htm"); + html.replace("%name%", ClassListData.getInstance().getClass(paramTwo).getClassName()); + player.sendPacket(SystemMessageId.THE_NEW_SUB_CLASS_HAS_BEEN_ADDED); // Subclass added. + player.broadcastPacket(new MagicSkillUse(player, 5103, 1, 0, 0)); + } + else + { + /** + * This isn't good! modifySubClass() removed subclass from memory we must update _classIndex! Else IndexOutOfBoundsException can turn up some place down the line along with other seemingly unrelated problems. + */ + player.setActiveClass(0); // Also updates _classIndex plus switching _classid to baseclass. + player.sendMessage("The sub class could not be added, you have been reverted to your base class."); + return; + } + break; + } + } + + html.replace("%objectId%", String.valueOf(getObjectId())); + player.sendPacket(html); + } + else + { + super.onBypassFeedback(player, command); + } + } + + protected String getSubClassMenu(Race race) + { + return "data/html/villagemaster/SubClass.htm"; + } + + protected String getSubClassFail() + { + return "data/html/villagemaster/SubClass_Fail.htm"; + } + + protected boolean checkQuests(Player player) + { + // Noble players can add Sub-Classes without quests. + if (player.isNoble()) + { + return true; + } + + QuestState qs = player.getQuestState("Q00234_FatesWhisper"); + if ((qs == null) || !qs.isCompleted()) + { + return false; + } + + qs = player.getQuestState("Q00235_MimirsElixir"); + if ((qs == null) || !qs.isCompleted()) + { + return false; + } + + return true; + } + + /** + * @return the village master race + */ + private Race getVillageMasterRace() + { + return getTemplate().getRace(); + } + + private ClassType getVillageMasterTeachType() + { + if (this instanceof VillageMasterPriest) + { + return ClassType.PRIEST; + } + + if (this instanceof VillageMasterMystic) + { + return ClassType.MYSTIC; + } + + return ClassType.FIGTHER; + } + + private Set getAvailableSubClasses(Player player) + { + final Set availSubs = getSubclasses(player, player.getBaseClass()); + final Race npcRace = getVillageMasterRace(); + final ClassType npcTeachType = getVillageMasterTeachType(); + boolean everywhereEnabled = PlayerConfig.ALT_GAME_SUBCLASS_EVERYWHERE; + + if (availSubs != null) + { + // Create a copy of the set to avoid ConcurrentModificationException. + final Set copySubs = new HashSet<>(availSubs); + for (PlayerClass availSub : copySubs) + { + // Remove subclasses already chosen by the player. + for (SubClassHolder subClass : player.getSubClasses().values()) + { + if (subClass.getId() == availSub.ordinal()) + { + availSubs.remove(availSub); + break; + } + } + + // Remove subclasses unavailable due to previous choices or base class. + final Iterator subListIterator = iterSubClasses(player); + while (subListIterator.hasNext()) + { + final SubClassHolder prevSubClass = subListIterator.next(); + int subClassId = prevSubClass.getId(); + if (subClassId >= 88) + { + subClassId = PlayerClass.getPlayerClass(subClassId).getParent().getId(); + } + + if ((availSub.ordinal() == subClassId) || (availSub.ordinal() == player.getBaseClass())) + { + availSubs.remove(availSub); + break; + } + } + + // Check if subclass restrictions based on race should be applied everywhere. + if (!everywhereEnabled) + { + // Apply race restrictions based on the village master's race. + if (((npcRace == Race.HUMAN) || (npcRace == Race.ELF))) + { + // If the master is human or light elf, ensure that fighter-type masters only teach fighter classes, and priest-type masters only teach priest classes etc. + if (!availSub.isOfType(npcTeachType) || (!availSub.isOfRace(Race.HUMAN) && !availSub.isOfRace(Race.ELF))) + { + availSubs.remove(availSub); + } + } + else if ((npcRace != Race.HUMAN) && (npcRace != Race.ELF) && !availSub.isOfRace(npcRace)) + { + // If the master is not human and not light elf, then remove any classes not of the same race as the master. + availSubs.remove(availSub); + } + } + } + } + + return availSubs; + } + + public Set getSubclasses(Player player, int classId) + { + Set subclasses = null; + final PlayerClass pClass = PlayerClass.getPlayerClass(classId); + if (CategoryData.getInstance().isInCategory(CategoryType.THIRD_CLASS_GROUP, classId) || (CategoryData.getInstance().isInCategory(CategoryType.FOURTH_CLASS_GROUP, classId))) + { + subclasses = EnumSet.copyOf(mainSubclassSet); + subclasses.remove(pClass); + + if (player.getRace() == Race.ELF) + { + for (PlayerClass cid : PlayerClass.values()) + { + if (cid.getRace() == Race.DARK_ELF) + { + subclasses.remove(cid); + } + } + } + else if (player.getRace() == Race.DARK_ELF) + { + for (PlayerClass cid : PlayerClass.values()) + { + if (cid.getRace() == Race.ELF) + { + subclasses.remove(cid); + } + } + } + + final Set unavailableClasses = subclassSetMap.get(pClass); + if (unavailableClasses != null) + { + subclasses.removeAll(unavailableClasses); + } + } + + if (subclasses != null) + { + final PlayerClass currClassId = player.getPlayerClass(); + for (PlayerClass tempClass : subclasses) + { + if (currClassId.equalsOrChildOf(tempClass)) + { + subclasses.remove(tempClass); + } + } + } + + return subclasses; + } + + /** + * Check new subclass classId for validity (villagemaster race/type, is not contains in previous subclasses, is contains in allowed subclasses) Base class not added into allowed subclasses. + * @param player + * @param classId + * @return + */ + private boolean isValidNewSubClass(Player player, int classId) + { + final PlayerClass cid = PlayerClass.getPlayerClass(classId); + SubClassHolder sub; + PlayerClass subClassId; + for (Iterator subList = iterSubClasses(player); subList.hasNext();) + { + sub = subList.next(); + subClassId = PlayerClass.getPlayerClass(sub.getId()); + if (subClassId.equalsOrChildOf(cid)) + { + return false; + } + } + + // Get player base class. + final int currentBaseId = player.getBaseClass(); + final PlayerClass baseCID = PlayerClass.getPlayerClass(currentBaseId); + + // We need 2nd occupation ID. + final int baseClassId; + if (baseCID.level() > 2) + { + baseClassId = baseCID.getParent().getId(); + } + else + { + baseClassId = currentBaseId; + } + + final Set availSubs = getSubclasses(player, baseClassId); + if ((availSubs == null) || availSubs.isEmpty()) + { + return false; + } + + boolean found = false; + for (PlayerClass pclass : availSubs) + { + if (pclass.getId() == classId) + { + found = true; + break; + } + } + + return found; + } + + private static Iterator iterSubClasses(Player player) + { + return player.getSubClasses().values().iterator(); + } + + private void dissolveClan(Player player, int clanId) + { + if (!player.isClanLeader()) + { + player.sendPacket(SystemMessageId.YOU_ARE_NOT_AUTHORIZED_TO_DO_THAT); + return; + } + + final Clan clan = player.getClan(); + if (clan.getAllyId() != 0) + { + player.sendPacket(SystemMessageId.YOU_CANNOT_DISPERSE_THE_CLANS_IN_YOUR_ALLIANCE); + return; + } + + if (clan.isAtWar()) + { + player.sendPacket(SystemMessageId.YOU_CANNOT_DISSOLVE_A_CLAN_WHILE_ENGAGED_IN_A_WAR); + return; + } + + if ((clan.getCastleId() != 0) || (clan.getHideoutId() != 0)) + { + player.sendPacket(SystemMessageId.YOU_CANNOT_DISSOLVE_A_CLAN_WHILE_OWNING_A_CLAN_HALL_OR_CASTLE); + return; + } + + for (Castle castle : CastleManager.getInstance().getCastles()) + { + if (SiegeManager.getInstance().checkIsRegistered(clan, castle.getResidenceId())) + { + player.sendPacket(SystemMessageId.YOU_CANNOT_DISSOLVE_A_CLAN_DURING_A_SIEGE_OR_WHILE_PROTECTING_A_CASTLE); + return; + } + } + + if (player.isInsideZone(ZoneId.SIEGE)) + { + player.sendPacket(SystemMessageId.YOU_CANNOT_DISSOLVE_A_CLAN_DURING_A_SIEGE_OR_WHILE_PROTECTING_A_CASTLE); + return; + } + + if (clan.getDissolvingExpiryTime() > System.currentTimeMillis()) + { + player.sendPacket(SystemMessageId.YOU_HAVE_ALREADY_REQUESTED_THE_DISSOLUTION_OF_YOUR_CLAN); + return; + } + + clan.setDissolvingExpiryTime(System.currentTimeMillis() + (PlayerConfig.ALT_CLAN_DISSOLVE_DAYS * 86400000)); // 24*60*60*1000 = 86400000 + clan.updateClanInDB(); + + // The clan leader should take the XP penalty of a full death. + player.calculateDeathExpPenalty(null, false); + ClanTable.getInstance().scheduleRemoveClan(clan.getId()); + } + + private void recoverClan(Player player, int clanId) + { + if (!player.isClanLeader()) + { + player.sendPacket(SystemMessageId.YOU_ARE_NOT_AUTHORIZED_TO_DO_THAT); + return; + } + + final Clan clan = player.getClan(); + clan.setDissolvingExpiryTime(0); + clan.updateClanInDB(); + } + + private void createSubPledge(Player player, String clanName, String leaderName, int pledgeType, int minClanLvl) + { + if (!player.isClanLeader()) + { + player.sendPacket(SystemMessageId.YOU_ARE_NOT_AUTHORIZED_TO_DO_THAT); + return; + } + + final Clan clan = player.getClan(); + if (clan.getLevel() < minClanLvl) + { + if (pledgeType == Clan.SUBUNIT_ACADEMY) + { + player.sendPacket(SystemMessageId.TO_ESTABLISH_A_CLAN_ACADEMY_YOUR_CLAN_MUST_BE_LEVEL_5_OR_HIGHER); + } + else + { + player.sendPacket(SystemMessageId.THE_CONDITIONS_NECESSARY_TO_CREATE_A_MILITARY_UNIT_HAVE_NOT_BEEN_MET); + } + + return; + } + + if (!StringUtil.isAlphaNumeric(clanName) || !isValidName(clanName) || (2 > clanName.length())) + { + player.sendPacket(SystemMessageId.CLAN_NAME_IS_INVALID); + return; + } + + if (clanName.length() > 16) + { + player.sendPacket(SystemMessageId.CLAN_NAME_S_LENGTH_IS_INCORRECT); + return; + } + + for (Clan tempClan : ClanTable.getInstance().getClans()) + { + if (tempClan.getSubPledge(clanName) != null) + { + if (pledgeType == Clan.SUBUNIT_ACADEMY) + { + final SystemMessage sm = new SystemMessage(SystemMessageId.S1_ALREADY_EXISTS); + sm.addString(clanName); + player.sendPacket(sm); + } + else + { + player.sendPacket(SystemMessageId.ANOTHER_MILITARY_UNIT_IS_ALREADY_USING_THAT_NAME_PLEASE_ENTER_A_DIFFERENT_NAME); + } + + return; + } + } + + if (pledgeType != Clan.SUBUNIT_ACADEMY) + { + final ClanMember member = clan.getClanMember(leaderName); + if ((member == null) || (member.getPledgeType() != 0) || (clan.getLeaderSubPledge(member.getObjectId()) > 0)) + { + if (pledgeType >= Clan.SUBUNIT_KNIGHT1) + { + player.sendPacket(SystemMessageId.THE_CAPTAIN_OF_THE_ORDER_OF_KNIGHTS_CANNOT_BE_APPOINTED); + } + else if (pledgeType >= Clan.SUBUNIT_ROYAL1) + { + player.sendPacket(SystemMessageId.THE_CAPTAIN_OF_THE_ROYAL_GUARD_CANNOT_BE_APPOINTED); + } + + return; + } + } + + final int leaderId = pledgeType != Clan.SUBUNIT_ACADEMY ? clan.getClanMember(leaderName).getObjectId() : 0; + if (clan.createSubPledge(player, pledgeType, leaderId, clanName) == null) + { + return; + } + + SystemMessage sm; + if (pledgeType == Clan.SUBUNIT_ACADEMY) + { + sm = new SystemMessage(SystemMessageId.CONGRATULATIONS_THE_S1_S_CLAN_ACADEMY_HAS_BEEN_CREATED); + sm.addString(player.getClan().getName()); + } + else if (pledgeType >= Clan.SUBUNIT_KNIGHT1) + { + sm = new SystemMessage(SystemMessageId.THE_KNIGHTS_OF_S1_HAVE_BEEN_CREATED); + sm.addString(player.getClan().getName()); + } + else if (pledgeType >= Clan.SUBUNIT_ROYAL1) + { + sm = new SystemMessage(SystemMessageId.THE_ROYAL_GUARD_OF_S1_HAVE_BEEN_CREATED); + sm.addString(player.getClan().getName()); + } + else + { + sm = new SystemMessage(SystemMessageId.YOUR_CLAN_HAS_BEEN_CREATED); + } + + player.sendPacket(sm); + + if (pledgeType != Clan.SUBUNIT_ACADEMY) + { + final ClanMember leaderSubPledge = clan.getClanMember(leaderName); + final Player leaderPlayer = leaderSubPledge.getPlayer(); + if (leaderPlayer != null) + { + leaderPlayer.setPledgeClass(ClanMember.calculatePledgeClass(leaderPlayer)); + leaderPlayer.updateUserInfo(); + } + } + } + + private void renameSubPledge(Player player, int pledgeType, String pledgeName) + { + if (!player.isClanLeader()) + { + player.sendPacket(SystemMessageId.YOU_ARE_NOT_AUTHORIZED_TO_DO_THAT); + return; + } + + final SubPledge subPledge = player.getClan().getSubPledge(pledgeType); + if (subPledge == null) + { + player.sendMessage("Pledge don't exists."); + return; + } + + if (!StringUtil.isAlphaNumeric(pledgeName) || !isValidName(pledgeName) || (2 > pledgeName.length())) + { + player.sendPacket(SystemMessageId.CLAN_NAME_IS_INVALID); + return; + } + + if (pledgeName.length() > 16) + { + player.sendPacket(SystemMessageId.CLAN_NAME_S_LENGTH_IS_INCORRECT); + return; + } + + subPledge.setName(pledgeName); + final Clan clan = player.getClan(); + clan.updateSubPledgeInDB(subPledge.getId()); + clan.broadcastClanStatus(); + player.sendMessage("Pledge name changed."); + } + + private void assignSubPledgeLeader(Player player, String clanName, String leaderName) + { + if (!player.isClanLeader()) + { + player.sendPacket(SystemMessageId.YOU_ARE_NOT_AUTHORIZED_TO_DO_THAT); + return; + } + + if (leaderName.length() > 16) + { + player.sendPacket(SystemMessageId.YOUR_TITLE_CANNOT_EXCEED_16_CHARACTERS_IN_LENGTH_PLEASE_TRY_AGAIN); + return; + } + + if (player.getName().equals(leaderName)) + { + player.sendPacket(SystemMessageId.THE_CAPTAIN_OF_THE_ROYAL_GUARD_CANNOT_BE_APPOINTED); + return; + } + + final SubPledge subPledge = player.getClan().getSubPledge(clanName); + if ((subPledge == null) || (subPledge.getId() == Clan.SUBUNIT_ACADEMY)) + { + player.sendPacket(SystemMessageId.CLAN_NAME_IS_INVALID); + return; + } + + final Clan clan = player.getClan(); + final ClanMember member = clan.getClanMember(leaderName); + if ((member == null) || (member.getPledgeType() != 0) || (clan.getLeaderSubPledge(member.getObjectId()) > 0)) + { + if (subPledge.getId() >= Clan.SUBUNIT_KNIGHT1) + { + player.sendPacket(SystemMessageId.THE_CAPTAIN_OF_THE_ORDER_OF_KNIGHTS_CANNOT_BE_APPOINTED); + } + else if (subPledge.getId() >= Clan.SUBUNIT_ROYAL1) + { + player.sendPacket(SystemMessageId.THE_CAPTAIN_OF_THE_ROYAL_GUARD_CANNOT_BE_APPOINTED); + } + + return; + } + + subPledge.setLeaderId(member.getObjectId()); + clan.updateSubPledgeInDB(subPledge.getId()); + + final Player leaderPlayer = member.getPlayer(); + if (leaderPlayer != null) + { + leaderPlayer.setPledgeClass(ClanMember.calculatePledgeClass(leaderPlayer)); + leaderPlayer.updateUserInfo(); + } + + clan.broadcastClanStatus(); + final SystemMessage sm = new SystemMessage(SystemMessageId.S1_HAS_BEEN_SELECTED_AS_THE_CAPTAIN_OF_S2); + sm.addString(leaderName); + sm.addString(clanName); + clan.broadcastToOnlineMembers(sm); + } + + /** + * this displays PledgeSkillList to the player. + * @param player + */ + public static void showPledgeSkillList(Player player) + { + if (!player.isClanLeader()) + { + final NpcHtmlMessage html = new NpcHtmlMessage(); + html.setFile(player, "data/html/villagemaster/NotClanLeader.htm"); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); + return; + } + + final Clan clan = player.getClan(); + final List skills = SkillTreeData.getInstance().getAvailablePledgeSkills(clan); + final AcquireSkillList asl = new AcquireSkillList(AcquireSkillType.PLEDGE); + int counts = 0; + for (SkillLearn s : skills) + { + asl.addSkill(s.getSkillId(), s.getSkillLevel(), s.getSkillLevel(), s.getLevelUpSp(), s.getSocialClass().ordinal()); + counts++; + } + + if (counts == 0) + { + if (clan.getLevel() < 8) + { + final SystemMessage sm = new SystemMessage(SystemMessageId.YOU_DO_NOT_HAVE_ANY_FURTHER_SKILLS_TO_LEARN_COME_BACK_WHEN_YOU_HAVE_REACHED_LEVEL_S1); + if (clan.getLevel() < 5) + { + sm.addInt(5); + } + else + { + sm.addInt(clan.getLevel() + 1); + } + + player.sendPacket(sm); + } + else + { + final NpcHtmlMessage html = new NpcHtmlMessage(); + html.setFile(player, "data/html/villagemaster/NoMoreSkills.htm"); + player.sendPacket(html); + } + } + else + { + player.sendPacket(asl); + } + + player.sendPacket(ActionFailed.STATIC_PACKET); + } + + private static boolean isValidName(String name) + { + return ServerConfig.CLAN_NAME_TEMPLATE_PATTERN.matcher(name).matches(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/VillageMasterDElf.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/VillageMasterDElf.java new file mode 100644 index 0000000..b4d105f --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/VillageMasterDElf.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.instance; + +import org.l2jmobius.gameserver.entity.actor.templates.NpcTemplate; + +public class VillageMasterDElf extends VillageMaster +{ + /** + * Creates a village master. + * @param template the village master NPC template + */ + public VillageMasterDElf(NpcTemplate template) + { + super(template); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/VillageMasterDwarf.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/VillageMasterDwarf.java new file mode 100644 index 0000000..f1207a4 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/VillageMasterDwarf.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.instance; + +import org.l2jmobius.gameserver.entity.actor.templates.NpcTemplate; + +public class VillageMasterDwarf extends VillageMaster +{ + /** + * Creates a village master. + * @param template the village master NPC template + */ + public VillageMasterDwarf(NpcTemplate template) + { + super(template); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/VillageMasterFighter.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/VillageMasterFighter.java new file mode 100644 index 0000000..2b666d1 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/VillageMasterFighter.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.instance; + +import org.l2jmobius.gameserver.entity.actor.templates.NpcTemplate; + +public class VillageMasterFighter extends VillageMaster +{ + /** + * Creates a village master. + * @param template the village master NPC template + */ + public VillageMasterFighter(NpcTemplate template) + { + super(template); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/VillageMasterMystic.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/VillageMasterMystic.java new file mode 100644 index 0000000..e702833 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/VillageMasterMystic.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.instance; + +import org.l2jmobius.gameserver.entity.actor.templates.NpcTemplate; + +public class VillageMasterMystic extends VillageMaster +{ + /** + * Creates a village master. + * @param template the village master NPC template + */ + public VillageMasterMystic(NpcTemplate template) + { + super(template); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/VillageMasterOrc.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/VillageMasterOrc.java new file mode 100644 index 0000000..722e94f --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/VillageMasterOrc.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.instance; + +import org.l2jmobius.gameserver.entity.actor.templates.NpcTemplate; + +public class VillageMasterOrc extends VillageMaster +{ + /** + * Creates a village master. + * @param template the village master NPC template + */ + public VillageMasterOrc(NpcTemplate template) + { + super(template); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/VillageMasterPriest.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/VillageMasterPriest.java new file mode 100644 index 0000000..754750b --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/VillageMasterPriest.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.instance; + +import org.l2jmobius.gameserver.entity.actor.templates.NpcTemplate; + +public class VillageMasterPriest extends VillageMaster +{ + /** + * Creates a village master. + * @param template the village master NPC template + */ + public VillageMasterPriest(NpcTemplate template) + { + super(template); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/Warehouse.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/Warehouse.java new file mode 100644 index 0000000..62452f8 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/instance/Warehouse.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.instance; + +import org.l2jmobius.gameserver.entity.actor.enums.creature.InstanceType; +import org.l2jmobius.gameserver.entity.actor.templates.NpcTemplate; + +public class Warehouse extends Folk +{ + /** + * Creates a warehouse NPC. + * @param template the warehouse NPC template + */ + public Warehouse(NpcTemplate template) + { + super(template); + setInstanceType(InstanceType.Warehouse); + } + + @Override + public boolean isWarehouse() + { + return true; + } + + @Override + public String getHtmlPath(int npcId, int value) + { + String pom = ""; + if (value == 0) + { + pom = Integer.toString(npcId); + } + else + { + pom = npcId + "-" + value; + } + + return "data/html/warehouse/" + pom + ".htm"; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/stat/CreatureStat.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/stat/CreatureStat.java new file mode 100644 index 0000000..41fe54a --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/stat/CreatureStat.java @@ -0,0 +1,858 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.stat; + +import java.util.Arrays; + +import org.l2jmobius.gameserver.config.NpcConfig; +import org.l2jmobius.gameserver.config.PlayerConfig; +import org.l2jmobius.gameserver.config.custom.ChampionMonstersConfig; +import org.l2jmobius.gameserver.config.custom.ClassBalanceConfig; +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.zone.ZoneId; +import org.l2jmobius.gameserver.mechanics.skill.Skill; +import org.l2jmobius.gameserver.mechanics.stats.Calculator; +import org.l2jmobius.gameserver.mechanics.stats.MoveType; +import org.l2jmobius.gameserver.mechanics.stats.Stat; +import org.l2jmobius.gameserver.mechanics.stats.TraitType; + +public class CreatureStat +{ + private static final int DIVINE_INSPIRATION = 1405; + + // Elementals + private static final byte FIRE = 0; + private static final byte WATER = 1; + private static final byte WIND = 2; + private static final byte EARTH = 3; + private static final byte HOLY = 4; + private static final byte DARK = 5; + + private final Creature _creature; + private long _exp = 0; + private long _sp = 0; + private byte _level = 1; + private final float[] _attackTraits = new float[TraitType.values().length]; + private final int[] _attackTraitsCount = new int[TraitType.values().length]; + private final float[] _defenceTraits = new float[TraitType.values().length]; + private final int[] _defenceTraitsCount = new int[TraitType.values().length]; + private final int[] _traitsInvul = new int[TraitType.values().length]; + + /** Creature's maximum buff count. */ + private int _maxBuffCount = PlayerConfig.BUFFS_MAX_AMOUNT; + + /** Speed multiplier set by admin gmspeed command */ + private double _gmSpeedMultiplier = 1; + + public CreatureStat(Creature creature) + { + _creature = creature; + Arrays.fill(_attackTraits, 1.0f); + Arrays.fill(_defenceTraits, 1.0f); + } + + public double calcStat(Stat stat, double init) + { + return calcStat(stat, init, null, null); + } + + /** + * Calculate the new value of the state with modifiers that will be applied on the targeted Creature.
    + *
    + * Concept:
    + * A Creature owns a table of Calculators called _calculators. Each Calculator (a calculator per state) own a table of Func object. A Func object is a mathematic function that permit to calculate the modifier of a state (ex : REGENERATE_HP_RATE...) :
    + * FuncAtkAccuracy -> Math.sqrt(_player.getDEX())*6+_player.getLevel()
    + * When the calc method of a calculator is launched, each mathematical function is called according to its priority _order.
    + * Indeed, Func with lowest priority order is executed firsta and Funcs with the same order are executed in unspecified order.
    + * The result of the calculation is stored in the value property of an Env class instance. + * @param stat The stat to calculate the new value with modifiers + * @param initVal The initial value of the stat before applying modifiers + * @param target The Creature whose properties will be used in the calculation (ex : CON, INT...) + * @param skill The Skill whose properties will be used in the calculation (ex : Level...) + * @return + */ + public double calcStat(Stat stat, double initVal, Creature target, Skill skill) + { + double value = initVal; + if (stat == null) + { + return value; + } + + final int id = stat.ordinal(); + final Calculator calc = _creature.getCalculators()[id]; + + // If no Func object found, no modifier is applied. + if ((calc == null) || (calc.size() == 0)) + { + return value; + } + + // Launch the calculation + value = calc.calc(_creature, target, skill, value); + + // avoid some troubles with negative stats (some stats should never be negative) + if (value <= 0) + { + switch (stat) + { + case MAX_HP: + case MAX_MP: + case MAX_CP: + case MAGIC_DEFENCE: + case POWER_DEFENCE: + case POWER_ATTACK: + case MAGIC_ATTACK: + case POWER_ATTACK_SPEED: + case MAGIC_ATTACK_SPEED: + case SHIELD_DEFENCE: + case STAT_CON: + case STAT_DEX: + case STAT_INT: + case STAT_MEN: + case STAT_STR: + case STAT_WIT: + { + value = 1.0; + break; + } + } + } + + return value; + } + + /** + * @return the Accuracy (base+modifier) of the Creature in function of the Weapon Expertise Penalty. + */ + public int getAccuracy() + { + return (int) Math.round(calcStat(Stat.ACCURACY_COMBAT, 0, null, null)); + } + + public Creature getActiveChar() + { + return _creature; + } + + /** + * @return the Attack Speed multiplier (base+modifier) of the Creature to get proper animations. + */ + public float getAttackSpeedMultiplier() + { + return (float) ((1.1 * getPAtkSpd()) / _creature.getTemplate().getBasePAtkSpd()); + } + + /** + * @param target + * @param init + * @return the Critical Damage rate (base+modifier) of the Creature. + */ + public double getCriticalDmg(Creature target, double init) + { + return calcStat(Stat.CRITICAL_DAMAGE, init, target, null); + } + + /** + * @param target + * @param skill + * @return the Critical Hit rate (base+modifier) of the Creature. + */ + public int getCriticalHit(Creature target, Skill skill) + { + double val = calcStat(Stat.CRITICAL_RATE, _creature.getTemplate().getBaseCritRate(), target, skill); + + // if (!_creature.isGM()) + // { + val = Math.min(val, PlayerConfig.MAX_PCRIT_RATE); + // } + + return (int) (val + .5); + } + + /** + * @param base + * @return the Critical Hit Pos rate of the Creature + */ + public int getCriticalHitPos(int base) + { + return (int) calcStat(Stat.CRITICAL_RATE_POS, base); + } + + /** + * @param target + * @return the Attack Evasion rate (base+modifier) of the Creature. + */ + public int getEvasionRate(Creature target) + { + int val = (int) Math.round(calcStat(Stat.EVASION_RATE, 0, target, null)); + + // if (!_creature.isGM()) + // { + val = Math.min(val, PlayerConfig.MAX_EVASION); + // } + + return val; + } + + public long getExp() + { + return _exp; + } + + public void setExp(long value) + { + _exp = value; + } + + public byte getLevel() + { + return _level; + } + + public void setLevel(byte value) + { + _level = value; + } + + /** + * @param skill + * @return the Magical Attack range (base+modifier) of the Creature. + */ + public int getMagicalAttackRange(Skill skill) + { + if (skill != null) + { + return (int) calcStat(Stat.MAGIC_ATTACK_RANGE, skill.getCastRange(), null, skill); + } + + return _creature.getTemplate().getBaseAttackRange(); + } + + public int getMaxCp() + { + return (int) calcStat(Stat.MAX_CP, _creature.getTemplate().getBaseCpMax()); + } + + public int getMaxRecoverableCp() + { + return (int) calcStat(Stat.MAX_RECOVERABLE_CP, getMaxCp()); + } + + public int getMaxHp() + { + return (int) calcStat(Stat.MAX_HP, _creature.getTemplate().getBaseHpMax()); + } + + public int getMaxRecoverableHp() + { + return (int) calcStat(Stat.MAX_RECOVERABLE_HP, getMaxHp()); + } + + public int getMaxMp() + { + return (int) calcStat(Stat.MAX_MP, _creature.getTemplate().getBaseMpMax()); + } + + public int getMaxRecoverableMp() + { + return (int) calcStat(Stat.MAX_RECOVERABLE_MP, getMaxMp()); + } + + /** + * Return the MAtk (base+modifier) of the Creature.
    + *
    + * Example of use: Calculate Magic damage + * @param target The Creature targeted by the skill + * @param skill The Skill used against the target + * @return + */ + public double getMAtk(Creature target, Skill skill) + { + float bonusAtk = 1; + if (ChampionMonstersConfig.CHAMPION_ENABLE && _creature.isChampion()) + { + bonusAtk = ChampionMonstersConfig.CHAMPION_ATK; + } + + if (_creature.isRaid()) + { + bonusAtk *= NpcConfig.RAID_MATTACK_MULTIPLIER; + } + + // Calculate modifiers Magic Attack. + return Math.min(calcStat(Stat.MAGIC_ATTACK, _creature.getTemplate().getBaseMAtk() * bonusAtk, target, skill), PlayerConfig.MAX_MATK); + } + + /** + * @return the MAtk Speed (base+modifier) of the Creature in function of the Armour Expertise Penalty. + */ + public int getMAtkSpd() + { + float bonusSpdAtk = 1; + if (ChampionMonstersConfig.CHAMPION_ENABLE && _creature.isChampion()) + { + bonusSpdAtk = ChampionMonstersConfig.CHAMPION_SPD_ATK; + } + + double val = calcStat(Stat.MAGIC_ATTACK_SPEED, _creature.getTemplate().getBaseMAtkSpd() * bonusSpdAtk); + + // if (!_creature.isGM()) + // { + val = Math.min(val, PlayerConfig.MAX_MATK_SPEED); + // } + + return (int) val; + } + + /** + * @param target + * @param skill + * @return the Magic Critical Hit rate (base+modifier) of the Creature. + */ + public int getMCriticalHit(Creature target, Skill skill) + { + int val = (int) calcStat(Stat.MCRITICAL_RATE, 1, target, skill) * 10; + + if ((skill != null) && (target != null) && _creature.isPlayable()) + { + if (target.isPlayable()) + { + val *= ClassBalanceConfig.PVP_MAGICAL_SKILL_CRITICAL_CHANCE_MULTIPLIERS[_creature.asPlayer().getPlayerClass().getId()]; + } + else + { + val *= ClassBalanceConfig.PVE_MAGICAL_SKILL_CRITICAL_CHANCE_MULTIPLIERS[_creature.asPlayer().getPlayerClass().getId()]; + } + } + + // if (!_creature.isGM()) + // { + val = Math.min(val, PlayerConfig.MAX_MCRIT_RATE); + // } + + return val; + } + + /** + * Example of use : Calculate Magic damage. + * @param target The Creature targeted by the skill + * @param skill The Skill used against the target + * @return the MDef (base+modifier) of the Creature against a skill in function of abnormal effects in progress. + */ + public double getMDef(Creature target, Skill skill) + { + // Get the base MDef of the Creature. + double defence = _creature.getTemplate().getBaseMDef(); + + // Calculate modifier for Raid Bosses. + if (_creature.isRaid()) + { + defence *= NpcConfig.RAID_MDEFENCE_MULTIPLIER; + } + + if ((skill != null) && (target != null) && target.isPlayable()) + { + if (skill.isMagic()) + { + defence *= ClassBalanceConfig.PVE_MAGICAL_SKILL_DEFENCE_MULTIPLIERS[target.asPlayer().getPlayerClass().getId()]; + } + else + { + defence *= ClassBalanceConfig.PVE_PHYSICAL_SKILL_DEFENCE_MULTIPLIERS[target.asPlayer().getPlayerClass().getId()]; + } + } + + // Calculate modifiers Magic Attack. + return calcStat(Stat.MAGIC_DEFENCE, defence, target, skill); + } + + /** + * @return the CON of the Creature (base+modifier). + */ + public int getCON() + { + return (int) calcStat(Stat.STAT_CON, _creature.getTemplate().getBaseCON()); + } + + /** + * @return the DEX of the Creature (base+modifier). + */ + public int getDEX() + { + return (int) calcStat(Stat.STAT_DEX, _creature.getTemplate().getBaseDEX()); + } + + /** + * @return the INT of the Creature (base+modifier). + */ + public int getINT() + { + return (int) calcStat(Stat.STAT_INT, _creature.getTemplate().getBaseINT()); + } + + /** + * @return the MEN of the Creature (base+modifier). + */ + public int getMEN() + { + return (int) calcStat(Stat.STAT_MEN, _creature.getTemplate().getBaseMEN()); + } + + /** + * @return the STR of the Creature (base+modifier). + */ + public int getSTR() + { + return (int) calcStat(Stat.STAT_STR, _creature.getTemplate().getBaseSTR()); + } + + /** + * @return the WIT of the Creature (base+modifier). + */ + public int getWIT() + { + return (int) calcStat(Stat.STAT_WIT, _creature.getTemplate().getBaseWIT()); + } + + public double getMovementSpeedMultiplier() + { + double baseSpeed; + if (_creature.isInsideZone(ZoneId.WATER)) + { + baseSpeed = getBaseMoveSpeed(_creature.isRunning() ? MoveType.FAST_SWIM : MoveType.SLOW_SWIM); + } + else + { + baseSpeed = getBaseMoveSpeed(_creature.isRunning() ? MoveType.RUN : MoveType.WALK); + } + + return getMoveSpeed() * (1. / baseSpeed); + } + + public void setGmSpeedMultiplier(double multipier) + { + _gmSpeedMultiplier = multipier; + } + + /** + * @return the RunSpeed (base+modifier) of the Creature in function of the Armour Expertise Penalty. + */ + public double getRunSpeed() + { + final double baseRunSpd = _creature.isInsideZone(ZoneId.WATER) ? getSwimRunSpeed() : getBaseMoveSpeed(MoveType.RUN); + if (baseRunSpd <= 0) + { + return 0; + } + + return calcStat(Stat.MOVE_SPEED, baseRunSpd * _gmSpeedMultiplier, null, null); + } + + /** + * @return the WalkSpeed (base+modifier) of the Creature. + */ + public double getWalkSpeed() + { + final double baseWalkSpd = _creature.isInsideZone(ZoneId.WATER) ? getSwimWalkSpeed() : getBaseMoveSpeed(MoveType.WALK); + if (baseWalkSpd <= 0) + { + return 0; + } + + return calcStat(Stat.MOVE_SPEED, baseWalkSpd * _gmSpeedMultiplier); + } + + /** + * @return the SwimRunSpeed (base+modifier) of the Creature. + */ + public double getSwimRunSpeed() + { + final double baseRunSpd = getBaseMoveSpeed(MoveType.FAST_SWIM); + if (baseRunSpd <= 0) + { + return 0; + } + + return calcStat(Stat.MOVE_SPEED, baseRunSpd * _gmSpeedMultiplier, null, null); + } + + /** + * @return the SwimWalkSpeed (base+modifier) of the Creature. + */ + public double getSwimWalkSpeed() + { + final double baseWalkSpd = getBaseMoveSpeed(MoveType.SLOW_SWIM); + if (baseWalkSpd <= 0) + { + return 0; + } + + return calcStat(Stat.MOVE_SPEED, baseWalkSpd * _gmSpeedMultiplier); + } + + /** + * @param type movement type + * @return the base move speed of given movement type. + */ + public double getBaseMoveSpeed(MoveType type) + { + return _creature.getTemplate().getBaseMoveSpeed(type); + } + + /** + * @return the RunSpeed (base+modifier) or WalkSpeed (base+modifier) of the Creature in function of the movement type. + */ + public double getMoveSpeed() + { + if (_creature.isInsideZone(ZoneId.WATER)) + { + return _creature.isRunning() ? getSwimRunSpeed() : getSwimWalkSpeed(); + } + + return _creature.isRunning() ? getRunSpeed() : getWalkSpeed(); + } + + /** + * @param skill + * @return the MReuse rate (base+modifier) of the Creature. + */ + public double getMReuseRate(Skill skill) + { + return calcStat(Stat.MAGIC_REUSE_RATE, 1, null, skill); + } + + /** + * @param target + * @return the PAtk (base+modifier) of the Creature. + */ + public double getPAtk(Creature target) + { + float bonusAtk = 1; + if (ChampionMonstersConfig.CHAMPION_ENABLE && _creature.isChampion()) + { + bonusAtk = ChampionMonstersConfig.CHAMPION_ATK; + } + + if (_creature.isRaid()) + { + bonusAtk *= NpcConfig.RAID_PATTACK_MULTIPLIER; + } + + return Math.min(calcStat(Stat.POWER_ATTACK, _creature.getTemplate().getBasePAtk() * bonusAtk, target, null), PlayerConfig.MAX_PATK); + } + + /** + * @return the PAtk Speed (base+modifier) of the Creature in function of the Armour Expertise Penalty. + */ + public double getPAtkSpd() + { + float bonusAtk = 1; + if (ChampionMonstersConfig.CHAMPION_ENABLE && _creature.isChampion()) + { + bonusAtk = ChampionMonstersConfig.CHAMPION_SPD_ATK; + } + + return Math.round(calcStat(Stat.POWER_ATTACK_SPEED, _creature.getTemplate().getBasePAtkSpd() * bonusAtk, null, null)); + } + + /** + * @param target + * @return the PDef (base+modifier) of the Creature. + */ + public double getPDef(Creature target) + { + if (_creature.isPlayable() && (target != null)) + { + if (target.isPlayable()) + { + return ClassBalanceConfig.PVP_PHYSICAL_ATTACK_DEFENCE_MULTIPLIERS[_creature.asPlayer().getPlayerClass().getId()] * calcStat(Stat.POWER_DEFENCE, _creature.isRaid() ? _creature.getTemplate().getBasePDef() * NpcConfig.RAID_PDEFENCE_MULTIPLIER : _creature.getTemplate().getBasePDef(), target, null); + } + + return ClassBalanceConfig.PVE_PHYSICAL_ATTACK_DEFENCE_MULTIPLIERS[_creature.asPlayer().getPlayerClass().getId()] * calcStat(Stat.POWER_DEFENCE, _creature.isRaid() ? _creature.getTemplate().getBasePDef() * NpcConfig.RAID_PDEFENCE_MULTIPLIER : _creature.getTemplate().getBasePDef(), target, null); + } + + return calcStat(Stat.POWER_DEFENCE, _creature.isRaid() ? _creature.getTemplate().getBasePDef() * NpcConfig.RAID_PDEFENCE_MULTIPLIER : _creature.getTemplate().getBasePDef(), target, null); + } + + /** + * @return the Physical Attack range (base+modifier) of the Creature. + */ + public int getPhysicalAttackRange() + { + int baseAttackRange; + if (_creature.getActiveWeaponItem() == null) + { + baseAttackRange = _creature.getTemplate().getBaseAttackRange(); + } + else // Use weapon pAtkRange stat. + { + baseAttackRange = 0; + } + + return (int) calcStat(Stat.POWER_ATTACK_RANGE, baseAttackRange, null, null); + } + + public int getPhysicalAttackAngle() + { + return 0; + } + + /** + * @param target + * @return the weapon reuse modifier. + */ + public double getWeaponReuseModifier(Creature target) + { + return calcStat(Stat.ATK_REUSE, 1, target, null); + } + + /** + * @return the ShieldDef rate (base+modifier) of the Creature. + */ + public int getShldDef() + { + return (int) calcStat(Stat.SHIELD_DEFENCE, 0); + } + + public long getSp() + { + return _sp; + } + + public void setSp(long value) + { + _sp = value; + } + + /** + * @param skill + * @return the mpConsume. + */ + public int getMpConsume(Skill skill) + { + if (skill == null) + { + return 1; + } + + double mpConsume = skill.getMpConsume(); + final double nextDanceMpCost = Math.ceil(skill.getMpConsume() / 2.); + if (skill.isDance() && PlayerConfig.DANCE_CONSUME_ADDITIONAL_MP && (_creature != null) && (_creature.getDanceCount() > 0)) + { + mpConsume += _creature.getDanceCount() * nextDanceMpCost; + } + + mpConsume = calcStat(Stat.MP_CONSUME, mpConsume, null, skill); + if (skill.isDance()) + { + return (int) calcStat(Stat.DANCE_MP_CONSUME_RATE, mpConsume); + } + + if (skill.isMagic()) + { + return (int) calcStat(Stat.MAGICAL_MP_CONSUME_RATE, mpConsume); + } + + return (int) calcStat(Stat.PHYSICAL_MP_CONSUME_RATE, mpConsume); + } + + /** + * @param skill + * @return the mpInitialConsume. + */ + public int getMpInitialConsume(Skill skill) + { + return skill == null ? 1 : (int) calcStat(Stat.MP_CONSUME, skill.getMpInitialConsume(), null, skill); + } + + public byte getAttackElement() + { + // temp fix starts + int tempVal = 0; + final int[] stats = + { + 0, + 0, + 0, + 0, + 0, + 0 + }; + + byte returnVal = -2; + stats[0] = (int) calcStat(Stat.FIRE_POWER, _creature.getTemplate().getBaseFire()); + stats[1] = (int) calcStat(Stat.WATER_POWER, _creature.getTemplate().getBaseWater()); + stats[2] = (int) calcStat(Stat.WIND_POWER, _creature.getTemplate().getBaseWind()); + stats[3] = (int) calcStat(Stat.EARTH_POWER, _creature.getTemplate().getBaseEarth()); + stats[4] = (int) calcStat(Stat.HOLY_POWER, _creature.getTemplate().getBaseHoly()); + stats[5] = (int) calcStat(Stat.DARK_POWER, _creature.getTemplate().getBaseDark()); + for (byte x = 0; x < 6; x++) + { + if (stats[x] > tempVal) + { + returnVal = x; + tempVal = stats[x]; + } + } + + return returnVal; + // temp fix ends + } + + public int getAttackElementValue(byte attackAttribute) + { + switch (attackAttribute) + { + case FIRE: + { + return (int) calcStat(Stat.FIRE_POWER, _creature.getTemplate().getBaseFire()); + } + case WATER: + { + return (int) calcStat(Stat.WATER_POWER, _creature.getTemplate().getBaseWater()); + } + case WIND: + { + return (int) calcStat(Stat.WIND_POWER, _creature.getTemplate().getBaseWind()); + } + case EARTH: + { + return (int) calcStat(Stat.EARTH_POWER, _creature.getTemplate().getBaseEarth()); + } + case HOLY: + { + return (int) calcStat(Stat.HOLY_POWER, _creature.getTemplate().getBaseHoly()); + } + case DARK: + { + return (int) calcStat(Stat.DARK_POWER, _creature.getTemplate().getBaseDark()); + } + default: + { + return 0; + } + } + } + + public int getDefenseElementValue(byte defenseAttribute) + { + switch (defenseAttribute) + { + case FIRE: + { + return (int) calcStat(Stat.FIRE_RES, _creature.getTemplate().getBaseFireRes()); + } + case WATER: + { + return (int) calcStat(Stat.WATER_RES, _creature.getTemplate().getBaseWaterRes()); + } + case WIND: + { + return (int) calcStat(Stat.WIND_RES, _creature.getTemplate().getBaseWindRes()); + } + case EARTH: + { + return (int) calcStat(Stat.EARTH_RES, _creature.getTemplate().getBaseEarthRes()); + } + case HOLY: + { + return (int) calcStat(Stat.HOLY_RES, _creature.getTemplate().getBaseHolyRes()); + } + case DARK: + { + return (int) calcStat(Stat.DARK_RES, _creature.getTemplate().getBaseDarkRes()); + } + default: + { + return (int) _creature.getTemplate().getBaseElementRes(); + } + } + } + + public float getAttackTrait(TraitType traitType) + { + return _attackTraits[traitType.ordinal()]; + } + + public float[] getAttackTraits() + { + return _attackTraits; + } + + public boolean hasAttackTrait(TraitType traitType) + { + return _attackTraitsCount[traitType.ordinal()] > 0; + } + + public int[] getAttackTraitsCount() + { + return _attackTraitsCount; + } + + public float getDefenceTrait(TraitType traitType) + { + return _defenceTraits[traitType.ordinal()]; + } + + public float[] getDefenceTraits() + { + return _defenceTraits; + } + + public boolean hasDefenceTrait(TraitType traitType) + { + return _defenceTraitsCount[traitType.ordinal()] > 0; + } + + public int[] getDefenceTraitsCount() + { + return _defenceTraitsCount; + } + + public boolean isTraitInvul(TraitType traitType) + { + return _traitsInvul[traitType.ordinal()] > 0; + } + + public int[] getTraitsInvul() + { + return _traitsInvul; + } + + /** + * Gets the maximum buff count. + * @return the maximum buff count + */ + public int getMaxBuffCount() + { + return _maxBuffCount + _creature.getSkillLevel(DIVINE_INSPIRATION); + } + + /** + * Sets the maximum buff count. + * @param buffCount the buff count + */ + public void setMaxBuffCount(int buffCount) + { + _maxBuffCount = buffCount; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/stat/DoorStat.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/stat/DoorStat.java new file mode 100644 index 0000000..a02f475 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/stat/DoorStat.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.stat; + +import org.l2jmobius.gameserver.entity.actor.instance.Door; + +public class DoorStat extends CreatureStat +{ + private int _upgradeHpRatio = 1; + + public DoorStat(Door activeChar) + { + super(activeChar); + } + + @Override + public Door getActiveChar() + { + return super.getActiveChar().asDoor(); + } + + @Override + public int getMaxHp() + { + return super.getMaxHp() * _upgradeHpRatio; + } + + public void setUpgradeHpRatio(int ratio) + { + _upgradeHpRatio = ratio; + } + + public int getUpgradeHpRatio() + { + return _upgradeHpRatio; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/stat/NpcStat.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/stat/NpcStat.java new file mode 100644 index 0000000..0139554 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/stat/NpcStat.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.stat; + +import org.l2jmobius.gameserver.entity.actor.Npc; + +public class NpcStat extends CreatureStat +{ + public NpcStat(Npc activeChar) + { + super(activeChar); + } + + @Override + public byte getLevel() + { + return getActiveChar().getTemplate().getLevel(); + } + + @Override + public Npc getActiveChar() + { + return super.getActiveChar().asNpc(); + } + + @Override + public int getPhysicalAttackAngle() + { + return getActiveChar().getTemplate().getBaseAttackAngle(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/stat/PetStat.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/stat/PetStat.java new file mode 100644 index 0000000..8040fc5 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/stat/PetStat.java @@ -0,0 +1,233 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.stat; + +import org.l2jmobius.gameserver.config.custom.ClassBalanceConfig; +import org.l2jmobius.gameserver.data.xml.ExperienceData; +import org.l2jmobius.gameserver.data.xml.PetDataTable; +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.instance.Pet; +import org.l2jmobius.gameserver.mechanics.skill.Skill; +import org.l2jmobius.gameserver.mechanics.stats.Stat; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.serverpackets.SocialAction; +import org.l2jmobius.gameserver.network.serverpackets.StatusUpdate; +import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; + +public class PetStat extends SummonStat +{ + public PetStat(Pet activeChar) + { + super(activeChar); + } + + public boolean addExp(int value) + { + final Pet pet = getActiveChar(); + if (pet.isUncontrollable() || !super.addExp(value)) + { + return false; + } + + pet.updateAndBroadcastStatus(1); + + // The PetInfo packet wipes the PartySpelled (list of active spells' icons). Re-add them. + pet.updateEffectIcons(true); + + return true; + } + + public boolean addExpAndSp(double addToExp, double addToSp) + { + final long finalExp = Math.round(addToExp); + final Pet pet = getActiveChar(); + if (pet.isUncontrollable() || !addExp(finalExp)) + { + return false; + } + + final SystemMessage sm = new SystemMessage(SystemMessageId.YOUR_PET_GAINED_S1_EXPERIENCE_POINTS); + sm.addLong(finalExp); + pet.updateAndBroadcastStatus(1); + pet.sendPacket(sm); + return true; + } + + @Override + public boolean addLevel(byte value) + { + if ((getLevel() + value) > (getMaxLevel() - 1)) + { + return false; + } + + final boolean levelIncreased = super.addLevel(value); + + // Sync up exp with current level. + // if (getExp() > getExpForLevel(getLevel() + 1) || getExp() < getExpForLevel(getLevel())) setExp(Experience.LEVEL[getLevel()]); + + final Pet pet = getActiveChar(); + final StatusUpdate su = new StatusUpdate(pet); + su.addAttribute(StatusUpdate.LEVEL, getLevel()); + su.addAttribute(StatusUpdate.MAX_HP, getMaxHp()); + su.addAttribute(StatusUpdate.MAX_MP, getMaxMp()); + pet.broadcastPacket(su); + if (levelIncreased) + { + pet.broadcastPacket(new SocialAction(pet.getObjectId(), SocialAction.LEVEL_UP)); + } + + // Send a Server->Client packet PetInfo to the Player. + pet.updateAndBroadcastStatus(1); + + if (pet.getControlItem() != null) + { + pet.getControlItem().setEnchantLevel(getLevel()); + } + + return levelIncreased; + } + + @Override + public long getExpForLevel(int level) + { + final Pet pet = getActiveChar(); + try + { + return PetDataTable.getInstance().getPetLevelData(pet.getId(), level).getPetMaxExp(); + } + catch (NullPointerException e) + { + if (pet != null) + { + LOGGER.warning("Pet objectId:" + pet.getObjectId() + ", NpcId:" + pet.getId() + ", level:" + level + " is missing data from pets_stats table!"); + } + + throw e; + } + } + + @Override + public Pet getActiveChar() + { + return super.getActiveChar().asPet(); + } + + public int getFeedBattle() + { + return getActiveChar().getPetLevelData().getPetFeedBattle(); + } + + public int getFeedNormal() + { + return getActiveChar().getPetLevelData().getPetFeedNormal(); + } + + @Override + public void setLevel(byte value) + { + final Pet pet = getActiveChar(); + pet.setPetData(PetDataTable.getInstance().getPetLevelData(pet.getTemplate().getId(), value)); + if (pet.getPetLevelData() == null) + { + throw new IllegalArgumentException("No pet data for npc: " + pet.getTemplate().getId() + " level: " + value); + } + + pet.stopFeed(); + super.setLevel(value); + + pet.startFeed(); + + if (pet.getControlItem() != null) + { + pet.getControlItem().setEnchantLevel(getLevel()); + } + } + + public int getMaxFeed() + { + return getActiveChar().getPetLevelData().getPetMaxFeed(); + } + + @Override + public int getMaxHp() + { + return (int) calcStat(Stat.MAX_HP, getActiveChar().getPetLevelData().getPetMaxHP(), null, null); + } + + @Override + public int getMaxMp() + { + return (int) calcStat(Stat.MAX_MP, getActiveChar().getPetLevelData().getPetMaxMP(), null, null); + } + + @Override + public double getMAtk(Creature target, Skill skill) + { + return calcStat(Stat.MAGIC_ATTACK, getActiveChar().getPetLevelData().getPetMAtk(), target, skill); + } + + @Override + public double getMDef(Creature target, Skill skill) + { + return calcStat(Stat.MAGIC_DEFENCE, getActiveChar().getPetLevelData().getPetMDef(), target, skill); + } + + @Override + public double getPAtk(Creature target) + { + return calcStat(Stat.POWER_ATTACK, getActiveChar().getPetLevelData().getPetPAtk(), target, null); + } + + @Override + public double getPDef(Creature target) + { + if (target != null) + { + if (target.isPlayable()) + { + return ClassBalanceConfig.PVP_PHYSICAL_ATTACK_DEFENCE_MULTIPLIERS[getActiveChar().asPlayer().getPlayerClass().getId()] * calcStat(Stat.POWER_DEFENCE, getActiveChar().getPetLevelData().getPetPDef(), target, null); + } + + return ClassBalanceConfig.PVE_PHYSICAL_ATTACK_DEFENCE_MULTIPLIERS[getActiveChar().asPlayer().getPlayerClass().getId()] * calcStat(Stat.POWER_DEFENCE, getActiveChar().getPetLevelData().getPetPDef(), target, null); + } + + return calcStat(Stat.POWER_DEFENCE, getActiveChar().getPetLevelData().getPetPDef(), target, null); + } + + @Override + public double getPAtkSpd() + { + return getActiveChar().isHungry() ? super.getPAtkSpd() / 2 : super.getPAtkSpd(); + } + + @Override + public int getMAtkSpd() + { + return getActiveChar().isHungry() ? super.getMAtkSpd() / 2 : super.getMAtkSpd(); + } + + @Override + public int getMaxLevel() + { + return ExperienceData.getInstance().getMaxPetLevel(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/stat/PlayableStat.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/stat/PlayableStat.java new file mode 100644 index 0000000..c64c51e --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/stat/PlayableStat.java @@ -0,0 +1,285 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.stat; + +import java.util.logging.Logger; + +import org.l2jmobius.gameserver.config.PlayerConfig; +import org.l2jmobius.gameserver.data.xml.ExperienceData; +import org.l2jmobius.gameserver.data.xml.PetDataTable; +import org.l2jmobius.gameserver.entity.actor.Playable; +import org.l2jmobius.gameserver.entity.item.Weapon; +import org.l2jmobius.gameserver.entity.zone.ZoneId; +import org.l2jmobius.gameserver.entity.zone.type.SwampZone; +import org.l2jmobius.gameserver.managers.ZoneManager; +import org.l2jmobius.gameserver.mechanics.events.EventDispatcher; +import org.l2jmobius.gameserver.mechanics.events.EventType; +import org.l2jmobius.gameserver.mechanics.events.holders.actor.playable.OnPlayableExpChanged; +import org.l2jmobius.gameserver.mechanics.events.returns.TerminateReturn; + +public class PlayableStat extends CreatureStat +{ + protected static final Logger LOGGER = Logger.getLogger(PlayableStat.class.getName()); + + public PlayableStat(Playable activeChar) + { + super(activeChar); + } + + public boolean addExp(long amount) + { + final Playable playable = getActiveChar(); + if (EventDispatcher.getInstance().hasListener(EventType.ON_PLAYABLE_EXP_CHANGED, playable)) + { + final TerminateReturn term = EventDispatcher.getInstance().notifyEvent(new OnPlayableExpChanged(playable, getExp(), getExp() + amount), playable, TerminateReturn.class); + if ((term != null) && term.terminate()) + { + return false; + } + } + + if (((getExp() + amount) < 0) || ((amount > 0) && (getExp() == (getExpForLevel(getMaxLevel()) - 1)))) + { + return true; + } + + long value = amount; + if ((getExp() + value) >= getExpForLevel(getMaxLevel())) + { + value = getExpForLevel(getMaxLevel()) - 1 - getExp(); + } + + setExp(getExp() + value); + byte minimumLevel = 1; + if (playable.isPet()) + { + // Get minimum level from NpcTemplate. + minimumLevel = (byte) PetDataTable.getInstance().getPetMinLevel(playable.asPet().getTemplate().getId()); + } + + byte level = minimumLevel; // minimum level + for (byte tmp = level; tmp <= getMaxLevel(); tmp++) + { + if (getExp() >= getExpForLevel(tmp)) + { + continue; + } + + level = --tmp; + break; + } + + if ((level != getLevel()) && (level >= minimumLevel)) + { + addLevel((byte) (level - getLevel())); + } + + return true; + } + + public boolean removeExp(long amount) + { + long value = amount; + if ((getExp() - value) < 0) + { + value = getExp() - 1; + } + + setExp(getExp() - value); + byte minimumLevel = 1; + final Playable playable = getActiveChar(); + if (playable.isPet()) + { + // Get minimum level from NpcTemplate. + minimumLevel = (byte) PetDataTable.getInstance().getPetMinLevel(playable.asPet().getTemplate().getId()); + } + + byte level = minimumLevel; + for (byte tmp = level; tmp <= getMaxLevel(); tmp++) + { + if (getExp() >= getExpForLevel(tmp)) + { + continue; + } + + level = --tmp; + break; + } + + if ((level != getLevel()) && (level >= minimumLevel)) + { + addLevel((byte) (level - getLevel())); + } + + return true; + } + + public boolean removeExpAndSp(long removeExp, long removeSp) + { + boolean expRemoved = false; + boolean spRemoved = false; + if (removeExp > 0) + { + expRemoved = removeExp(removeExp); + } + + if (removeSp > 0) + { + spRemoved = removeSp(removeSp); + } + + return expRemoved || spRemoved; + } + + public boolean addLevel(byte amount) + { + byte value = amount; + if ((getLevel() + value) > (getMaxLevel() - 1)) + { + if (getLevel() < (getMaxLevel() - 1)) + { + value = (byte) (getMaxLevel() - 1 - getLevel()); + } + else + { + return false; + } + } + + final boolean levelIncreased = (getLevel() + value) > getLevel(); + value += getLevel(); + setLevel(value); + + // Sync up exp with current level. + if ((getExp() >= getExpForLevel(getLevel() + 1)) || (getExpForLevel(getLevel()) > getExp())) + { + setExp(getExpForLevel(getLevel())); + } + + final Playable playable = getActiveChar(); + if (!levelIncreased && playable.isPlayer() && !playable.isGM() && PlayerConfig.DECREASE_SKILL_LEVEL) + { + playable.asPlayer().checkPlayerSkills(); + } + + if (!levelIncreased) + { + return false; + } + + playable.getStatus().setCurrentHp(playable.getStat().getMaxHp()); + playable.getStatus().setCurrentMp(playable.getStat().getMaxMp()); + + return true; + } + + public boolean addSp(long amount) + { + if (amount < 0) + { + LOGGER.warning("wrong usage"); + return false; + } + + final long currentSp = getSp(); + if (currentSp >= PlayerConfig.MAX_SP) + { + return false; + } + + long value = amount; + if (currentSp > (PlayerConfig.MAX_SP - value)) + { + value = PlayerConfig.MAX_SP - currentSp; + } + + setSp(currentSp + value); + return true; + } + + public boolean removeSp(long amount) + { + final long currentSp = getSp(); + if (currentSp < amount) + { + setSp(getSp() - currentSp); + return true; + } + + setSp(getSp() - amount); + return true; + } + + public long getExpForLevel(int level) + { + return ExperienceData.getInstance().getExpForLevel(level); + } + + @Override + public double getRunSpeed() + { + final Playable playable = getActiveChar(); + if (playable.isInsideZone(ZoneId.SWAMP)) + { + final SwampZone zone = ZoneManager.getInstance().getZone(playable, SwampZone.class); + if (zone != null) + { + return super.getRunSpeed() * zone.getMoveBonus(); + } + } + + return super.getRunSpeed(); + } + + @Override + public double getWalkSpeed() + { + final Playable playable = getActiveChar(); + if (playable.isInsideZone(ZoneId.SWAMP)) + { + final SwampZone zone = ZoneManager.getInstance().getZone(playable, SwampZone.class); + if (zone != null) + { + return super.getWalkSpeed() * zone.getMoveBonus(); + } + } + + return super.getWalkSpeed(); + } + + @Override + public Playable getActiveChar() + { + return super.getActiveChar().asPlayable(); + } + + public int getMaxLevel() + { + return ExperienceData.getInstance().getMaxLevel(); + } + + @Override + public int getPhysicalAttackAngle() + { + final Weapon weapon = getActiveChar().getActiveWeaponItem(); + return weapon != null ? weapon.getBaseAttackAngle() : super.getPhysicalAttackAngle(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/stat/PlayerStat.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/stat/PlayerStat.java new file mode 100644 index 0000000..adde1b5 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/stat/PlayerStat.java @@ -0,0 +1,861 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.stat; + +import org.l2jmobius.gameserver.config.PlayerConfig; +import org.l2jmobius.gameserver.config.RatesConfig; +import org.l2jmobius.gameserver.data.holders.PetLevelData; +import org.l2jmobius.gameserver.data.xml.ExperienceData; +import org.l2jmobius.gameserver.data.xml.PetDataTable; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.holders.player.SubClassHolder; +import org.l2jmobius.gameserver.entity.actor.instance.Pet; +import org.l2jmobius.gameserver.entity.actor.status.PlayerStatus; +import org.l2jmobius.gameserver.entity.clan.Clan; +import org.l2jmobius.gameserver.entity.zone.ZoneId; +import org.l2jmobius.gameserver.mechanics.events.EventDispatcher; +import org.l2jmobius.gameserver.mechanics.events.EventType; +import org.l2jmobius.gameserver.mechanics.events.holders.actor.player.OnPlayerLevelChanged; +import org.l2jmobius.gameserver.mechanics.script.QuestState; +import org.l2jmobius.gameserver.mechanics.stats.Formulas; +import org.l2jmobius.gameserver.mechanics.stats.MoveType; +import org.l2jmobius.gameserver.mechanics.stats.Stat; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.serverpackets.PledgeShowMemberListUpdate; +import org.l2jmobius.gameserver.network.serverpackets.SocialAction; +import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; + +public class PlayerStat extends PlayableStat +{ + private int _oldMaxHp; // stats watch + private int _oldMaxMp; // stats watch + private int _oldMaxCp; // stats watch + private float _vitalityPoints = 1; + private byte _vitalityLevel = 0; + /** Player's maximum cubic count. */ + private int _maxCubicCount = 1; + private boolean _cloakSlot = false; + + public static final int[] VITALITY_LEVELS = + { + 240, + 2000, + 13000, + 17000, + 20000 + }; + + public static final int MAX_VITALITY_POINTS = VITALITY_LEVELS[4]; + public static final int MIN_VITALITY_POINTS = 1; + + public PlayerStat(Player player) + { + super(player); + } + + @Override + public boolean addExp(long value) + { + final Player player = getActiveChar(); + + // Allowed to gain exp? + if (!player.getAccessLevel().canGainExp()) + { + return false; + } + + if (!super.addExp(value)) + { + return false; + } + + // Set new karma + if (!player.isCursedWeaponEquipped() && (player.getKarma() > 0) && !player.isInsideZone(ZoneId.PVP)) + { + final int karmaLost = Formulas.calculateKarmaLost(player, value); + if (karmaLost > 0) + { + player.setKarma(player.getKarma() - karmaLost); + final SystemMessage msg = new SystemMessage(SystemMessageId.YOUR_KARMA_HAS_BEEN_CHANGED_TO_S1); + msg.addInt(player.getKarma()); + player.sendPacket(msg); + } + } + + return true; + } + + public boolean addExpAndSp(double addToExpValue, double addToSpValue, boolean useBonuses) + { + final Player player = getActiveChar(); + + // Allowed to gain exp/sp? + if (!player.getAccessLevel().canGainExp()) + { + return false; + } + + double addToExp = addToExpValue; + double addToSp = addToSpValue; + + double bonusExp = 1; + double bonusSp = 1; + if (useBonuses) + { + bonusExp = getExpBonusMultiplier(); + bonusSp = getSpBonusMultiplier(); + } + + addToExp *= bonusExp; + addToSp *= bonusSp; + double ratioTakenByPlayer = 0; + + // if this player has a pet and it is in his range he takes from the owner's Exp, give the pet Exp now + if (player.hasPet() && (player.calculateDistance3D(player.getSummon()) < PlayerConfig.ALT_PARTY_RANGE)) + { + final Pet pet = player.getSummon().asPet(); + ratioTakenByPlayer = pet.getPetLevelData().getOwnerExpTaken() / 100f; + + // only give exp/sp to the pet by taking from the owner if the pet has a non-zero, positive ratio + // allow possible customizations that would have the pet earning more than 100% of the owner's exp/sp + if (ratioTakenByPlayer > 1) + { + ratioTakenByPlayer = 1; + } + + if (!pet.isDead()) + { + pet.addExpAndSp(addToExp * (1 - ratioTakenByPlayer), addToSp * (1 - ratioTakenByPlayer)); + } + + // now adjust the max ratio to avoid the owner earning negative exp/sp + addToExp *= ratioTakenByPlayer; + addToSp *= ratioTakenByPlayer; + } + + final long finalExp = Math.round(addToExp); + final long finalSp = Math.round(addToSp); + final boolean expAdded = addExp(finalExp); + final boolean spAdded = addSp(finalSp); + if (!expAdded && spAdded) + { + final SystemMessage sm = new SystemMessage(SystemMessageId.YOU_HAVE_ACQUIRED_S1_SP); + sm.addLong(finalSp); + player.sendPacket(sm); + } + else if (expAdded && !spAdded) + { + final SystemMessage sm = new SystemMessage(SystemMessageId.YOU_HAVE_EARNED_S1_EXPERIENCE); + sm.addLong(finalExp); + player.sendPacket(sm); + } + else if ((finalExp > 0) || (finalSp > 0)) + { + final SystemMessage sm = new SystemMessage(SystemMessageId.YOU_HAVE_EARNED_S1_EXPERIENCE_AND_S2_SP); + sm.addLong((long) addToExp); + sm.addLong((long) addToSp); + player.sendPacket(sm); + } + + player.updateUserInfo(); + + return true; + } + + @Override + public boolean removeExpAndSp(long addToExp, long addToSp) + { + return removeExpAndSp(addToExp, addToSp, true); + } + + public boolean removeExpAndSp(long addToExp, long addToSp, boolean sendMessage) + { + final int level = getLevel(); + if (!super.removeExpAndSp(addToExp, addToSp)) + { + return false; + } + + final Player player = getActiveChar(); + + if (sendMessage) + { + // Send a Server->Client System Message to the Player. + SystemMessage sm = new SystemMessage(SystemMessageId.YOUR_EXPERIENCE_HAS_DECREASED_BY_S1); + sm.addLong(addToExp); + player.sendPacket(sm); + sm = new SystemMessage(SystemMessageId.YOUR_SP_HAS_DECREASED_BY_S1); + sm.addLong(addToSp); + player.sendPacket(sm); + if (getLevel() < level) + { + player.broadcastStatusUpdate(); + } + } + + player.updateUserInfo(); + + return true; + } + + @Override + public boolean addLevel(byte value) + { + if ((getLevel() + value) > (ExperienceData.getInstance().getMaxLevel() - 1)) + { + return false; + } + + // Notify to scripts + final Player player = getActiveChar(); + if (EventDispatcher.getInstance().hasListener(EventType.ON_PLAYER_LEVEL_CHANGED, player)) + { + EventDispatcher.getInstance().notifyEventAsync(new OnPlayerLevelChanged(player, getLevel(), getLevel() + value), player); + } + + final boolean levelIncreased = super.addLevel(value); + if (levelIncreased) + { + if (!PlayerConfig.DISABLE_TUTORIAL) + { + final QuestState qs = player.getQuestState("Q00255_Tutorial"); + if (qs != null) + { + qs.getQuest().notifyEvent("CE40", null, player); + } + } + + player.setCurrentCp(getMaxCp()); + player.broadcastPacket(new SocialAction(player.getObjectId(), SocialAction.LEVEL_UP)); + player.sendPacket(SystemMessageId.YOUR_LEVEL_HAS_INCREASED); + } + + // Give AutoGet skills and all normal skills if Auto-Learn is activated. + player.rewardSkills(); + + final Clan clan = player.getClan(); + if (clan != null) + { + clan.updateClanMember(player); + clan.broadcastToOnlineMembers(new PledgeShowMemberListUpdate(player)); + } + + if (player.isInParty()) + { + player.getParty().recalculatePartyLevel(); // Recalculate the party level. + } + + // Synchronize level with pet if possible. + if (player.hasPet()) + { + final Pet pet = player.getSummon().asPet(); + if (pet.getPetData().isSynchLevel() && (pet.getLevel() != getLevel())) + { + final byte availableLevel = (byte) Math.min(pet.getPetData().getMaxLevel(), getLevel()); + pet.getStat().setLevel(availableLevel); + pet.getStat().getExpForLevel(availableLevel); + pet.setCurrentHp(pet.getMaxHp()); + pet.setCurrentMp(pet.getMaxMp()); + pet.broadcastPacket(new SocialAction(player.getObjectId(), SocialAction.LEVEL_UP)); + pet.updateAndBroadcastStatus(1); + } + } + + // Update the overloaded status of the Player. + player.refreshOverloaded(); + + // Update the expertise status of the Player. + player.refreshExpertisePenalty(); + + // Send a Server->Client packet UserInfo to the Player. + player.updateUserInfo(); + return levelIncreased; + } + + @Override + public long getExpForLevel(int level) + { + return ExperienceData.getInstance().getExpForLevel(level); + } + + @Override + public Player getActiveChar() + { + return super.getActiveChar().asPlayer(); + } + + @Override + public long getExp() + { + final Player player = getActiveChar(); + if (player.isSubClassActive()) + { + return player.getSubClasses().get(player.getClassIndex()).getExp(); + } + + return super.getExp(); + } + + public long getBaseExp() + { + return super.getExp(); + } + + @Override + public void setExp(long value) + { + final Player player = getActiveChar(); + if (player.isSubClassActive()) + { + player.getSubClasses().get(player.getClassIndex()).setExp(value); + } + else + { + super.setExp(value); + } + } + + /** + * Gets the maximum cubic count. + * @return the maximum cubic count + */ + public int getMaxCubicCount() + { + return _maxCubicCount; + } + + /** + * Sets the maximum cubic count. + * @param cubicCount the maximum cubic count + */ + public void setMaxCubicCount(int cubicCount) + { + _maxCubicCount = cubicCount; + } + + public boolean canEquipCloak() + { + if (!getActiveChar().hasEnteredWorld()) + { + return true; + } + + return _cloakSlot; + } + + public void setCloakSlotStatus(boolean cloakSlot) + { + _cloakSlot = cloakSlot; + } + + @Override + public byte getLevel() + { + final Player player = getActiveChar(); + if (player.isSubClassActive()) + { + final SubClassHolder holder = player.getSubClasses().get(player.getClassIndex()); + if (holder != null) + { + return holder.getLevel(); + } + } + + return super.getLevel(); + } + + public byte getBaseLevel() + { + return super.getLevel(); + } + + @Override + public void setLevel(byte value) + { + byte level = value; + if (level > (ExperienceData.getInstance().getMaxLevel() - 1)) + { + level = (byte) (ExperienceData.getInstance().getMaxLevel() - 1); + } + + final Player player = getActiveChar(); + if (player.isSubClassActive()) + { + player.getSubClasses().get(player.getClassIndex()).setLevel(value); + } + else + { + super.setLevel(level); + } + } + + @Override + public int getMaxCp() + { + // Get the Max CP (base+modifier) of the Player. + final Player player = getActiveChar(); + final int val = (player == null) ? 1 : (int) calcStat(Stat.MAX_CP, player.getTemplate().getBaseCpMax(player.getLevel())); + if (val != _oldMaxCp) + { + _oldMaxCp = val; + + // Launch a regen task if the new Max CP is higher than the old one. + if (player != null) + { + final PlayerStatus status = player.getStatus(); + if (status.getCurrentCp() != val) + { + status.setCurrentCp(status.getCurrentCp()); // trigger start of regeneration + } + } + } + + return val; + } + + @Override + public int getMaxHp() + { + // Get the Max HP (base+modifier) of the Player. + final Player player = getActiveChar(); + final int val = (player == null) ? 1 : (int) calcStat(Stat.MAX_HP, player.getTemplate().getBaseHpMax(player.getLevel())); + if (val != _oldMaxHp) + { + _oldMaxHp = val; + + // Launch a regen task if the new Max HP is higher than the old one. + if (player != null) + { + final PlayerStatus status = player.getStatus(); + if (status.getCurrentHp() != val) + { + status.setCurrentHp(status.getCurrentHp()); // trigger start of regeneration + } + } + } + + return val; + } + + @Override + public int getMaxMp() + { + // Get the Max MP (base+modifier) of the Player. + final Player player = getActiveChar(); + final int val = (player == null) ? 1 : (int) calcStat(Stat.MAX_MP, player.getTemplate().getBaseMpMax(player.getLevel())); + if (val != _oldMaxMp) + { + _oldMaxMp = val; + + // Launch a regen task if the new Max MP is higher than the old one. + if (player != null) + { + final PlayerStatus status = player.getStatus(); + if (status.getCurrentMp() != val) + { + status.setCurrentMp(status.getCurrentMp()); // trigger start of regeneration + } + } + } + + return val; + } + + @Override + public long getSp() + { + final Player player = getActiveChar(); + if (player.isSubClassActive()) + { + return player.getSubClasses().get(player.getClassIndex()).getSp(); + } + + return super.getSp(); + } + + public long getBaseSp() + { + return super.getSp(); + } + + @Override + public void setSp(long value) + { + final Player player = getActiveChar(); + if (player.isSubClassActive()) + { + player.getSubClasses().get(player.getClassIndex()).setSp(value); + } + else + { + super.setSp(value); + } + } + + /** + * @param type movement type + * @return the base move speed of given movement type. + */ + @Override + public double getBaseMoveSpeed(MoveType type) + { + final Player player = getActiveChar(); + if (player.isMounted()) + { + final PetLevelData data = PetDataTable.getInstance().getPetLevelData(player.getMountNpcId(), player.getMountLevel()); + if (data != null) + { + return data.getSpeedOnRide(type); + } + } + + return super.getBaseMoveSpeed(type); + } + + @Override + public double getRunSpeed() + { + double val = super.getRunSpeed() + PlayerConfig.RUN_SPD_BOOST; + + // Apply max run speed cap. + final Player player = getActiveChar(); + if ((val > PlayerConfig.MAX_RUN_SPEED) && !player.isGM()) + { + return PlayerConfig.MAX_RUN_SPEED; + } + + // Check for mount penalties. + if (player.isMounted()) + { + // if level diff with mount >= 10, it decreases move speed by 50% + if ((player.getMountLevel() - player.getLevel()) >= 10) + { + val /= 2; + } + + // if mount is hungry, it decreases move speed by 50% + if (player.isHungry()) + { + val /= 2; + } + } + + return val; + } + + @Override + public double getWalkSpeed() + { + double val = super.getWalkSpeed() + PlayerConfig.RUN_SPD_BOOST; + + // Apply max run speed cap. + final Player player = getActiveChar(); + if ((val > PlayerConfig.MAX_RUN_SPEED) && !player.isGM()) + { + return PlayerConfig.MAX_RUN_SPEED; + } + + if (player.isMounted()) + { + // if level diff with mount >= 10, it decreases move speed by 50% + if ((player.getMountLevel() - player.getLevel()) >= 10) + { + val /= 2; + } + + // if mount is hungry, it decreases move speed by 50% + if (player.isHungry()) + { + val /= 2; + } + } + + return val; + } + + @Override + public double getPAtkSpd() + { + final double val = super.getPAtkSpd(); + if ((val > PlayerConfig.MAX_PATK_SPEED) && !getActiveChar().isGM()) + { + return PlayerConfig.MAX_PATK_SPEED; + } + + return val; + } + + private void updateVitalityLevel(boolean quiet) + { + final byte level; + if (_vitalityPoints <= VITALITY_LEVELS[0]) + { + level = 0; + } + else if (_vitalityPoints <= VITALITY_LEVELS[1]) + { + level = 1; + } + else if (_vitalityPoints <= VITALITY_LEVELS[2]) + { + level = 2; + } + else if (_vitalityPoints <= VITALITY_LEVELS[3]) + { + level = 3; + } + else + { + level = 4; + } + + if (!quiet && (level != _vitalityLevel)) + { + final Player player = getActiveChar(); + if (level < _vitalityLevel) + { + player.sendMessage("Your Vitality has decreased."); + } + else + { + player.sendMessage("Your Vitality has increased."); + } + + if (level == 0) + { + player.sendMessage("Your Vitality is fully exhausted."); + } + else if (level == 4) + { + player.sendMessage("Your Vitality is at maximum."); + } + } + + _vitalityLevel = level; + } + + /* + * Return current vitality points in integer format + */ + public int getVitalityPoints() + { + return (int) _vitalityPoints; + } + + /* + * Set current vitality points to this value if quiet = true - does not send system messages + */ + public void setVitalityPoints(int value, boolean quiet) + { + final int points = Math.clamp(value, MIN_VITALITY_POINTS, MAX_VITALITY_POINTS); + if (points == _vitalityPoints) + { + return; + } + + _vitalityPoints = points; + updateVitalityLevel(quiet); + // getActiveChar().sendPacket(new ExVitalityPointInfo(getVitalityPoints())); + } + + public synchronized void updateVitalityPoints(float value, boolean useRates, boolean quiet) + { + if ((value == 0) || !PlayerConfig.ENABLE_VITALITY) + { + return; + } + + float points = value; + if (useRates) + { + final Player player = getActiveChar(); + if (player.isLucky()) + { + return; + } + + if (points < 0) // vitality consumed + { + final int stat = (int) calcStat(Stat.VITALITY_CONSUME_RATE, 1, player, null); + if (stat == 0) + { + return; + } + + if (stat < 0) + { + points = -points; + } + } + + if (points > 0) + { + // vitality increased + points *= RatesConfig.RATE_VITALITY_GAIN; + } + else + { + // vitality decreased + points *= RatesConfig.RATE_VITALITY_LOST; + } + } + + if (points > 0) + { + points = Math.min(_vitalityPoints + points, MAX_VITALITY_POINTS); + } + else + { + points = Math.max(_vitalityPoints + points, MIN_VITALITY_POINTS); + } + + if (Math.abs(points - _vitalityPoints) <= 1e-6) + { + return; + } + + _vitalityPoints = points; + updateVitalityLevel(quiet); + } + + public double getVitalityMultiplier() + { + double vitality = 1.0; + if (PlayerConfig.ENABLE_VITALITY) + { + switch (getVitalityLevel()) + { + case 1: + { + vitality = RatesConfig.RATE_VITALITY_LEVEL_1; + break; + } + case 2: + { + vitality = RatesConfig.RATE_VITALITY_LEVEL_2; + break; + } + case 3: + { + vitality = RatesConfig.RATE_VITALITY_LEVEL_3; + break; + } + case 4: + { + vitality = RatesConfig.RATE_VITALITY_LEVEL_4; + break; + } + } + } + + return vitality; + } + + /** + * @return the _vitalityLevel + */ + public byte getVitalityLevel() + { + return _vitalityLevel; + } + + public double getExpBonusMultiplier() + { + double bonus = 1.0; + double vitality = 1.0; + double bonusExp = 1.0; + + // Bonus from Vitality System. + vitality = getVitalityMultiplier(); + + // Bonus exp from skills. + bonusExp = 1 + (calcStat(Stat.BONUS_EXP, 0, null, null) / 100); + if (vitality > 1.0) + { + bonus += (vitality - 1); + } + + if (bonusExp > 1) + { + bonus += (bonusExp - 1); + } + + // Check for abnormal bonuses. + bonus = Math.max(bonus, 1); + if (PlayerConfig.MAX_BONUS_EXP > 0) + { + bonus = Math.min(bonus, PlayerConfig.MAX_BONUS_EXP); + } + + return bonus; + } + + public double getSpBonusMultiplier() + { + double bonus = 1.0; + double vitality = 1.0; + double bonusSp = 1.0; + + // Bonus from Vitality System. + vitality = getVitalityMultiplier(); + + // Bonus sp from skills. + bonusSp = 1 + (calcStat(Stat.BONUS_SP, 0, null, null) / 100); + if (vitality > 1.0) + { + bonus += (vitality - 1); + } + + if (bonusSp > 1) + { + bonus += (bonusSp - 1); + } + + // Check for abnormal bonuses. + bonus = Math.max(bonus, 1); + if (PlayerConfig.MAX_BONUS_SP > 0) + { + bonus = Math.min(bonus, PlayerConfig.MAX_BONUS_SP); + } + + return bonus; + } + + public double getBonusDropAdenaMultiplier() + { + return 1 + (calcStat(Stat.BONUS_DROP_ADENA, 0, null, null) / 100); + } + + public double getBonusDropAmountMultiplier() + { + return 1 + (calcStat(Stat.BONUS_DROP_AMOUNT, 0, null, null) / 100); + } + + public double getBonusDropRateMultiplier() + { + return 1 + (calcStat(Stat.BONUS_DROP_RATE, 0, null, null) / 100); + } + + public double getBonusSpoilRateMultiplier() + { + return 1 + (calcStat(Stat.BONUS_SPOIL_RATE, 0, null, null) / 100); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/stat/StaticObjectStat.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/stat/StaticObjectStat.java new file mode 100644 index 0000000..4aa1d09 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/stat/StaticObjectStat.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.stat; + +import org.l2jmobius.gameserver.entity.actor.instance.StaticObject; + +public class StaticObjectStat extends CreatureStat +{ + public StaticObjectStat(StaticObject activeChar) + { + super(activeChar); + } + + @Override + public StaticObject getActiveChar() + { + return (StaticObject) super.getActiveChar(); + } + + @Override + public byte getLevel() + { + return (byte) getActiveChar().getLevel(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/stat/SummonStat.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/stat/SummonStat.java new file mode 100644 index 0000000..5fd0094 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/stat/SummonStat.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.stat; + +import org.l2jmobius.gameserver.entity.actor.Summon; + +public class SummonStat extends PlayableStat +{ + public SummonStat(Summon activeChar) + { + super(activeChar); + } + + @Override + public Summon getActiveChar() + { + return super.getActiveChar().asSummon(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/stat/VehicleStat.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/stat/VehicleStat.java new file mode 100644 index 0000000..94c4f24 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/stat/VehicleStat.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.stat; + +import org.l2jmobius.gameserver.entity.actor.Vehicle; + +public class VehicleStat extends CreatureStat +{ + private float _moveSpeed = 0; + private int _rotationSpeed = 0; + + public VehicleStat(Vehicle activeChar) + { + super(activeChar); + } + + @Override + public double getMoveSpeed() + { + return _moveSpeed; + } + + public void setMoveSpeed(float speed) + { + _moveSpeed = speed; + } + + public double getRotationSpeed() + { + return _rotationSpeed; + } + + public void setRotationSpeed(int speed) + { + _rotationSpeed = speed; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/status/AttackableStatus.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/status/AttackableStatus.java new file mode 100644 index 0000000..8412b57 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/status/AttackableStatus.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.status; + +import org.l2jmobius.gameserver.entity.actor.Attackable; +import org.l2jmobius.gameserver.entity.actor.Creature; + +public class AttackableStatus extends NpcStatus +{ + public AttackableStatus(Attackable activeChar) + { + super(activeChar); + } + + @Override + public void reduceHp(double value, Creature attacker) + { + reduceHp(value, attacker, true, false, false); + } + + @Override + public void reduceHp(double value, Creature attacker, boolean awake, boolean isDOT, boolean isHpConsumption) + { + final Attackable attackable = getActiveChar(); + if (attackable.isDead()) + { + return; + } + + if (value > 0) + { + if (attackable.isOverhit()) + { + attackable.setOverhitValues(attacker, value); + } + else + { + attackable.overhitEnabled(false); + } + } + else + { + attackable.overhitEnabled(false); + } + + super.reduceHp(value, attacker, awake, isDOT, isHpConsumption); + + if (!attackable.isDead()) + { + // And the attacker's hit didn't kill the mob, clear the over-hit flag. + attackable.overhitEnabled(false); + } + } + + @Override + public Attackable getActiveChar() + { + return super.getActiveChar().asAttackable(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/status/CreatureStatus.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/status/CreatureStatus.java new file mode 100644 index 0000000..a2be566 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/status/CreatureStatus.java @@ -0,0 +1,412 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.status; + +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Future; +import java.util.logging.Logger; + +import org.l2jmobius.commons.threads.ThreadPool; +import org.l2jmobius.commons.util.Rnd; +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.mechanics.stats.Formulas; + +public class CreatureStatus +{ + protected static final Logger LOGGER = Logger.getLogger(CreatureStatus.class.getName()); + + private final Creature _creature; + + private double _currentHp = 0; // Current HP of the Creature + private double _currentMp = 0; // Current MP of the Creature + + /** Array containing all clients that need to be notified about hp/mp updates of the Creature */ + private Set _statusListener; + + private Future _regTask; + + protected byte _flagsRegenActive = 0; + + protected static final byte REGEN_FLAG_CP = 4; + private static final byte REGEN_FLAG_HP = 1; + private static final byte REGEN_FLAG_MP = 2; + + public CreatureStatus(Creature creature) + { + _creature = creature; + } + + /** + * Add the object to the list of Creature that must be informed of HP/MP updates of this Creature.
    + *
    + * Concept:
    + *
    + * Each Creature owns a list called _statusListener that contains all Player to inform of HP/MP updates.
    + * Players who must be informed are players that target this Creature.
    + * When a RegenTask is in progress sever just need to go through this list to send Server->Client packet StatusUpdate.
    + *
    + * Example of use: + *
      + *
    • Target a PC or NPC
    • + *
        + * @param object Creature to add to the listener + */ + public void addStatusListener(Creature object) + { + if (object == _creature) + { + return; + } + + getStatusListener().add(object); + } + + /** + * Remove the object from the list of Creature that must be informed of HP/MP updates of this Creature.
        + *
        + * Concept:
        + *
        + * Each Creature owns a list called _statusListener that contains all Player to inform of HP/MP updates.
        + * Players who must be informed are players that target this Creature.
        + * When a RegenTask is in progress sever just need to go through this list to send Server->Client packet StatusUpdate.
        + *
        + * Example of use : + *
          + *
        • Untarget a PC or NPC
        • + *
        + * @param object Creature to add to the listener + */ + public void removeStatusListener(Creature object) + { + getStatusListener().remove(object); + } + + /** + * Return the list of Creature that must be informed of HP/MP updates of this Creature.
        + *
        + * Concept:
        + *
        + * Each Creature owns a list called _statusListener that contains all Player to inform of HP/MP updates.
        + * Players who must be informed are players that target this Creature.
        + * When a RegenTask is in progress sever just need to go through this list to send Server->Client packet StatusUpdate. + * @return The list of Creature to inform or null if empty + */ + public Set getStatusListener() + { + if (_statusListener == null) + { + _statusListener = ConcurrentHashMap.newKeySet(); + } + + return _statusListener; + } + + // place holder, only PcStatus has CP + public void reduceCp(int value) + { + } + + /** + * Reduce the current HP of the Creature and launch the doDie Task if necessary. + * @param value + * @param attacker + */ + public void reduceHp(double value, Creature attacker) + { + reduceHp(value, attacker, true, false, false); + } + + public void reduceHp(double value, Creature attacker, boolean isHpConsumption) + { + reduceHp(value, attacker, true, false, isHpConsumption); + } + + public void reduceHp(double value, Creature attacker, boolean awake, boolean isDOT, boolean isHPConsumption) + { + if (_creature.isDead()) + { + return; + } + + // invul handling + if (_creature.isInvul() && !(isDOT || isHPConsumption)) + { + return; + } + + if (attacker != null) + { + final Player attackerPlayer = attacker.asPlayer(); + if ((attackerPlayer != null) && attackerPlayer.isGM() && !attackerPlayer.getAccessLevel().canGiveDamage()) + { + return; + } + } + + if (!isDOT && !isHPConsumption) + { + _creature.stopEffectsOnDamage(awake); + if (_creature.isStunned() && (Rnd.get(10) == 0)) + { + _creature.stopStunning(true); + } + } + + if (value > 0) + { + setCurrentHp(Math.max(_currentHp - value, 0)); + } + + if ((_creature.getCurrentHp() < 0.5) && _creature.isMortal()) // Die + { + _creature.abortAttack(); + _creature.abortCast(); + + _creature.doDie(attacker); + } + } + + public void reduceMp(double value) + { + setCurrentMp(Math.max(_currentMp - value, 0)); + } + + /** + * Start the HP/MP/CP Regeneration task.
        + *
        + * Actions: + *
          + *
        • Calculate the regen task period
        • + *
        • Launch the HP/MP/CP Regeneration task with Medium priority
        • + *
        + */ + public synchronized void startHpMpRegeneration() + { + if ((_regTask == null) && !_creature.isDead()) + { + // Get the Regeneration period. + final int period = Formulas.getRegeneratePeriod(_creature); + + // Create the HP/MP/CP Regeneration task. + _regTask = ThreadPool.scheduleAtFixedRate(this::doRegeneration, period, period); + } + } + + /** + * Stop the HP/MP/CP Regeneration task.
        + *
        + * Actions: + *
          + *
        • Set the RegenActive flag to False
        • + *
        • Stop the HP/MP/CP Regeneration task
        • + *
        + */ + public synchronized void stopHpMpRegeneration() + { + if (_regTask != null) + { + // Stop the HP/MP/CP Regeneration task. + _regTask.cancel(false); + _regTask = null; + + // Set the RegenActive flag to false. + _flagsRegenActive = 0; + } + } + + // place holder, only PcStatus has CP + public double getCurrentCp() + { + return 0; + } + + // place holder, only PcStatus has CP + public void setCurrentCp(double newCp) + { + } + + public double getCurrentHp() + { + return _currentHp; + } + + public void setCurrentHp(double newHp) + { + setCurrentHp(newHp, true); + } + + /** + * Sets the current hp of this character. + * @param newHp the new hp + * @param broadcastPacket if true StatusUpdate packet will be broadcasted. + * @return @{code true} if hp was changed, @{code false} otherwise. + */ + public boolean setCurrentHp(double newHp, boolean broadcastPacket) + { + // Get the Max HP of the Creature. + final int currentHp = (int) _currentHp; + final double maxHp = _creature.getMaxHp(); + + // Tempfix for updating UserInfo when skills like Final Frenzy and Final Fortress conditions activate. + final double thirtyPercent = maxHp * 0.3; + final boolean updateUserInfo = _creature.isPlayer() && (((currentHp < thirtyPercent) && (newHp > thirtyPercent)) || ((currentHp > thirtyPercent) && (newHp < thirtyPercent))); + + synchronized (this) + { + if (_creature.isDead()) + { + return false; + } + + if (newHp >= maxHp) + { + // Set the RegenActive flag to false. + _currentHp = maxHp; + _flagsRegenActive &= ~REGEN_FLAG_HP; + + // Stop the HP/MP/CP Regeneration task. + if (_flagsRegenActive == 0) + { + stopHpMpRegeneration(); + } + } + else + { + // Set the RegenActive flag to true. + _currentHp = newHp; + _flagsRegenActive |= REGEN_FLAG_HP; + + // Start the HP/MP/CP Regeneration task with Medium priority. + startHpMpRegeneration(); + } + } + + final boolean hpWasChanged = currentHp != _currentHp; + + // Send the Server->Client packet StatusUpdate with current HP and MP to all other Player to inform. + if (hpWasChanged && broadcastPacket) + { + _creature.broadcastStatusUpdate(); + } + + if (updateUserInfo) + { + _creature.asPlayer().updateUserInfo(); + } + + return hpWasChanged; + } + + public void setCurrentHpMp(double newHp, double newMp) + { + final boolean hpChanged = setCurrentHp(newHp, false); + final boolean mpChanged = setCurrentMp(newMp, false); + if (hpChanged || mpChanged) + { + _creature.broadcastStatusUpdate(); + } + } + + public double getCurrentMp() + { + return _currentMp; + } + + public void setCurrentMp(double newMp) + { + setCurrentMp(newMp, true); + } + + /** + * Sets the current mp of this character. + * @param newMp the new mp + * @param broadcastPacket if true StatusUpdate packet will be broadcasted. + * @return @{code true} if mp was changed, @{code false} otherwise. + */ + public boolean setCurrentMp(double newMp, boolean broadcastPacket) + { + // Get the Max MP of the Creature. + final int currentMp = (int) _currentMp; + final int maxMp = _creature.getMaxMp(); + + synchronized (this) + { + if (_creature.isDead()) + { + return false; + } + + if (newMp >= maxMp) + { + // Set the RegenActive flag to false. + _currentMp = maxMp; + _flagsRegenActive &= ~REGEN_FLAG_MP; + + // Stop the HP/MP/CP Regeneration task. + if (_flagsRegenActive == 0) + { + stopHpMpRegeneration(); + } + } + else + { + // Set the RegenActive flag to true. + _currentMp = newMp; + _flagsRegenActive |= REGEN_FLAG_MP; + + // Start the HP/MP/CP Regeneration task with Medium priority. + startHpMpRegeneration(); + } + } + + final boolean mpWasChanged = currentMp != _currentMp; + + // Send the Server->Client packet StatusUpdate with current HP and MP to all other Player to inform. + if (mpWasChanged && broadcastPacket) + { + _creature.broadcastStatusUpdate(); + } + + return mpWasChanged; + } + + protected void doRegeneration() + { + // Modify the current HP/MP of the Creature and broadcast Server->Client packet StatusUpdate. + if (!_creature.isDead() && ((_currentHp < _creature.getMaxRecoverableHp()) || (_currentMp < _creature.getMaxRecoverableMp()))) + { + final double newHp = _currentHp + Formulas.calcHpRegen(_creature); + final double newMp = _currentMp + Formulas.calcMpRegen(_creature); + setCurrentHpMp(newHp, newMp); + } + else + { + stopHpMpRegeneration(); + } + } + + public Creature getActiveChar() + { + return _creature; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/status/DoorStatus.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/status/DoorStatus.java new file mode 100644 index 0000000..ce2f41d --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/status/DoorStatus.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.status; + +import org.l2jmobius.gameserver.entity.actor.instance.Door; + +public class DoorStatus extends CreatureStatus +{ + public DoorStatus(Door activeChar) + { + super(activeChar); + } + + @Override + public Door getActiveChar() + { + return super.getActiveChar().asDoor(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/status/FolkStatus.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/status/FolkStatus.java new file mode 100644 index 0000000..1c37181 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/status/FolkStatus.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.status; + +import org.l2jmobius.gameserver.config.FeatureConfig; +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.Npc; +import org.l2jmobius.gameserver.entity.actor.instance.ClanHallManager; +import org.l2jmobius.gameserver.entity.actor.instance.Folk; + +public class FolkStatus extends NpcStatus +{ + public FolkStatus(Npc activeChar) + { + super(activeChar); + } + + @Override + public void reduceHp(double value, Creature attacker) + { + reduceHp(value, attacker, true, false, false); + } + + @Override + public void reduceHp(double value, Creature attacker, boolean awake, boolean isDOT, boolean isHpConsumption) + { + } + + @Override + public void reduceMp(double value) + { + // If Clan Hall buff are free and it's a Clan Hall Manager MP won't get reduced. + if (FeatureConfig.CH_BUFF_FREE && (getActiveChar() instanceof ClanHallManager)) + { + return; + } + + super.reduceMp(value); + } + + @Override + public Folk getActiveChar() + { + return (Folk) super.getActiveChar(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/status/NpcStatus.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/status/NpcStatus.java new file mode 100644 index 0000000..52e7049 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/status/NpcStatus.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.status; + +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.Npc; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.holders.player.Duel; + +public class NpcStatus extends CreatureStatus +{ + public NpcStatus(Npc activeChar) + { + super(activeChar); + } + + @Override + public void reduceHp(double value, Creature attacker) + { + reduceHp(value, attacker, true, false, false); + } + + @Override + public void reduceHp(double value, Creature attacker, boolean awake, boolean isDOT, boolean isHpConsumption) + { + final Npc npc = getActiveChar(); + if (npc.isDead()) + { + return; + } + + if (attacker != null) + { + final Player attackerPlayer = attacker.asPlayer(); + if ((attackerPlayer != null) && attackerPlayer.isInDuel()) + { + attackerPlayer.setDuelState(Duel.DUELSTATE_INTERRUPTED); + } + + // Add attackers to npc's attacker list. + npc.addAttackerToAttackByList(attacker); + } + + super.reduceHp(value, attacker, awake, isDOT, isHpConsumption); + } + + @Override + public Npc getActiveChar() + { + return super.getActiveChar().asNpc(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/status/PetStatus.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/status/PetStatus.java new file mode 100644 index 0000000..6bee3f8 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/status/PetStatus.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.status; + +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.instance.Pet; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; + +public class PetStatus extends SummonStatus +{ + private int _currentFed = 0; // Current Fed of the Pet + + public PetStatus(Pet activeChar) + { + super(activeChar); + } + + @Override + public void reduceHp(double value, Creature attacker) + { + reduceHp(value, attacker, true, false, false); + } + + @Override + public void reduceHp(double value, Creature attacker, boolean awake, boolean isDOT, boolean isHpConsumption) + { + final Pet pet = getActiveChar(); + if (pet.isDead()) + { + return; + } + + super.reduceHp(value, attacker, awake, isDOT, isHpConsumption); + + if (attacker == null) + { + return; + } + + if (!isDOT && (pet.getOwner() != null)) + { + final SystemMessage sm = new SystemMessage(SystemMessageId.YOUR_PET_RECEIVED_S2_DAMAGE_CAUSED_BY_S1); + sm.addString(attacker.getName()); + sm.addInt((int) value); + pet.sendPacket(sm); + } + + pet.getAI().notifyActionAttacked(attacker); + } + + public int getCurrentFed() + { + return _currentFed; + } + + public void setCurrentFed(int value) + { + _currentFed = value; + } + + @Override + public Pet getActiveChar() + { + return super.getActiveChar().asPet(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/status/PlayableStatus.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/status/PlayableStatus.java new file mode 100644 index 0000000..ddf1e13 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/status/PlayableStatus.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.status; + +import org.l2jmobius.gameserver.entity.actor.Playable; + +public class PlayableStatus extends CreatureStatus +{ + public PlayableStatus(Playable activeChar) + { + super(activeChar); + } + + @Override + public Playable getActiveChar() + { + return super.getActiveChar().asPlayable(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/status/PlayerStatus.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/status/PlayerStatus.java new file mode 100644 index 0000000..083a8a1 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/status/PlayerStatus.java @@ -0,0 +1,427 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.status; + +import org.l2jmobius.commons.util.Rnd; +import org.l2jmobius.gameserver.config.PlayerConfig; +import org.l2jmobius.gameserver.config.custom.MultilingualSupportConfig; +import org.l2jmobius.gameserver.config.custom.OfflineTradeConfig; +import org.l2jmobius.gameserver.data.xml.NpcNameLocalisationData; +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.Summon; +import org.l2jmobius.gameserver.entity.actor.enums.player.PrivateStoreType; +import org.l2jmobius.gameserver.entity.actor.holders.player.Duel; +import org.l2jmobius.gameserver.entity.actor.stat.PlayerStat; +import org.l2jmobius.gameserver.managers.DuelManager; +import org.l2jmobius.gameserver.mechanics.script.QuestState; +import org.l2jmobius.gameserver.mechanics.skill.enums.SkillFinishType; +import org.l2jmobius.gameserver.mechanics.stats.Formulas; +import org.l2jmobius.gameserver.mechanics.stats.Stat; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.serverpackets.ActionFailed; +import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; +import org.l2jmobius.gameserver.util.LocationUtil; + +public class PlayerStatus extends PlayableStatus +{ + private double _currentCp = 0; // Current CP of the Player + + public PlayerStatus(Player player) + { + super(player); + } + + @Override + public void reduceCp(int value) + { + setCurrentCp(_currentCp > value ? _currentCp - value : 0); + } + + @Override + public void reduceHp(double value, Creature attacker) + { + reduceHp(value, attacker, true, false, false, false); + } + + @Override + public void reduceHp(double value, Creature attacker, boolean awake, boolean isDOT, boolean isHPConsumption) + { + reduceHp(value, attacker, awake, isDOT, isHPConsumption, false); + } + + public void reduceHp(double value, Creature attacker, boolean awake, boolean isDOT, boolean isHPConsumption, boolean ignoreCP) + { + final Player player = getActiveChar(); + if (player.isDead()) + { + return; + } + + // If OFFLINE_MODE_NO_DAMAGE is enabled and player is offline and he is in store/craft mode, no damage is taken. + if (OfflineTradeConfig.OFFLINE_MODE_NO_DAMAGE && (player.getClient() != null) && player.getClient().isDetached() && ((OfflineTradeConfig.OFFLINE_TRADE_ENABLE && ((player.getPrivateStoreType() == PrivateStoreType.SELL) || (player.getPrivateStoreType() == PrivateStoreType.BUY))) || (OfflineTradeConfig.OFFLINE_CRAFT_ENABLE && (player.isCrafting() || (player.getPrivateStoreType() == PrivateStoreType.MANUFACTURE))))) + { + return; + } + + if (player.isInvul() && !(isDOT || isHPConsumption)) + { + return; + } + + if (!isHPConsumption) + { + player.stopEffectsOnDamage(awake); + + // Attacked players in craft/shops stand up. + if (player.isCrafting() || player.isInStoreMode()) + { + player.setPrivateStoreType(PrivateStoreType.NONE); + player.standUp(); + player.broadcastUserInfo(); + } + else if (player.isSitting()) + { + player.standUp(); + } + + if (!isDOT && player.isStunned() && (Rnd.get(10) == 0)) + { + player.stopStunning(true); + } + } + + double amount = value; + int fullValue = (int) amount; + int tDmg = 0; + int mpDam = 0; + if ((attacker != null) && (attacker != player)) + { + final Player attackerPlayer = attacker.asPlayer(); + if (attackerPlayer != null) + { + if (attackerPlayer.isGM() && !attackerPlayer.getAccessLevel().canGiveDamage()) + { + return; + } + + if (player.isInDuel()) + { + if (player.getDuelState() == Duel.DUELSTATE_DEAD) + { + return; + } + else if (player.getDuelState() == Duel.DUELSTATE_WINNER) + { + return; + } + + // Cancel duel if player got hit by another player, that is not part of the duel. + if (attackerPlayer.getDuelId() != player.getDuelId()) + { + player.setDuelState(Duel.DUELSTATE_INTERRUPTED); + } + } + } + + // Check and calculate transfered damage. + final PlayerStat stat = player.getStat(); + final Summon summon = player.getSummon(); + if (player.hasServitor() && LocationUtil.checkIfInRange(1000, player, summon, true)) + { + tDmg = ((int) amount * (int) stat.calcStat(Stat.TRANSFER_DAMAGE_PERCENT, 0, null, null)) / 100; + + // Only transfer dmg up to current HP, it should not be killed. + tDmg = Math.min((int) summon.getCurrentHp() - 1, tDmg); + if (tDmg > 0) + { + summon.reduceCurrentHp(tDmg, attacker, null); + amount -= tDmg; + fullValue = (int) amount; // Reduce the announced value here as player will get a message about summon damage. + } + } + + mpDam = ((int) amount * (int) stat.calcStat(Stat.MANA_SHIELD_PERCENT, 0, null, null)) / 100; + if (mpDam > 0) + { + mpDam = (int) (amount - mpDam); + if (mpDam > player.getCurrentMp()) + { + player.sendMessage("MP became 0 and the Arcane Shield is disappearing."); + player.stopSkillEffects(SkillFinishType.REMOVED, 1556); + amount = mpDam - player.getCurrentMp(); + player.setCurrentMp(0); + } + else + { + player.reduceCurrentMp(mpDam); + player.sendMessage("Arcane Shield decreased your MP by " + mpDam + " instead of HP."); + return; + } + } + + final Player caster = player.getTransferingDamageTo(); + if ((caster != null) && (player.getParty() != null) && LocationUtil.checkIfInRange(1000, player, caster, true) && !caster.isDead() && (player != caster) && player.getParty().getMembers().contains(caster)) + { + int transferDmg = Math.min((int) caster.getCurrentHp() - 1, ((int) amount * (int) stat.calcStat(Stat.TRANSFER_DAMAGE_TO_PLAYER, 0, null, null)) / 100); + if (transferDmg > 0) + { + int membersInRange = 0; + for (Player member : caster.getParty().getMembers()) + { + if (LocationUtil.checkIfInRange(1000, member, caster, false) && (member != caster)) + { + membersInRange++; + } + } + + if ((attacker.isPlayable() || attacker.isFakePlayer()) && (caster.getCurrentCp() > 0)) + { + if (caster.getCurrentCp() > transferDmg) + { + caster.getStatus().reduceCp(transferDmg); + } + else + { + transferDmg = (int) (transferDmg - caster.getCurrentCp()); + caster.getStatus().reduceCp((int) caster.getCurrentCp()); + } + } + + if (membersInRange > 0) + { + caster.reduceCurrentHp(transferDmg / membersInRange, attacker, null); + amount -= transferDmg; + fullValue = (int) amount; + } + } + } + + if (!ignoreCP && (attacker.isPlayable() || attacker.isFakePlayer())) + { + if (_currentCp >= amount) + { + setCurrentCp(_currentCp - amount); // Set Cp to diff of Cp vs value. + amount = 0; // No need to subtract anything from Hp. + } + else + { + amount -= _currentCp; // Get diff from value vs Cp; will apply diff to Hp. + setCurrentCp(0, false); // Set Cp to 0 + } + } + + if ((fullValue > 0) && !isDOT) + { + // Send a System Message to the Player. + SystemMessage smsg = new SystemMessage(SystemMessageId.S1_HIT_YOU_FOR_S2_DAMAGE); + + // Localisation related. + String targetName = attacker.getName(); + if (MultilingualSupportConfig.MULTILANG_ENABLE && attacker.isNpc()) + { + final String[] localisation = NpcNameLocalisationData.getInstance().getLocalisation(player.getLang(), attacker.getId()); + if (localisation != null) + { + targetName = localisation[0]; + } + } + + smsg.addString(targetName); + smsg.addInt(fullValue); + player.sendPacket(smsg); + + if ((tDmg > 0) && (attackerPlayer != null)) + { + smsg = new SystemMessage(SystemMessageId.YOU_HAVE_GIVEN_S1_DAMAGE_TO_YOUR_TARGET_AND_S2_DAMAGE_TO_THE_SERVITOR); + smsg.addInt(fullValue); + smsg.addInt(tDmg); + attackerPlayer.sendPacket(smsg); + } + } + } + + if (amount > 0) + { + amount = getCurrentHp() - amount; + if (amount <= 0) + { + if (player.isInDuel()) + { + player.disableAllSkills(); + stopHpMpRegeneration(); + if (attacker != null) + { + attacker.getAI().setIntentionActive(); + attacker.sendPacket(ActionFailed.STATIC_PACKET); + attacker.setTarget(null); + attacker.abortAttack(); + } + + // let the DuelManager know of his defeat + DuelManager.getInstance().onPlayerDefeat(player); + amount = 1; + } + else + { + amount = 0; + } + } + + setCurrentHp(amount); + } + + if ((player.getCurrentHp() < 0.5) && !isHPConsumption) + { + player.abortAttack(); + player.abortCast(); + + if (player.isInOlympiadMode()) + { + stopHpMpRegeneration(); + player.setDead(true); + player.setIsPendingRevive(true); + if (player.hasSummon()) + { + player.getSummon().getAI().setIntentionIdle(); + } + return; + } + + player.doDie(attacker); + if (!PlayerConfig.DISABLE_TUTORIAL) + { + final QuestState qs = player.getQuestState("Q00255_Tutorial"); + if (qs != null) + { + qs.getQuest().notifyEvent("CE30", null, player); + } + } + } + } + + @Override + public boolean setCurrentHp(double newHp, boolean broadcastPacket) + { + final boolean result = super.setCurrentHp(newHp, broadcastPacket); + final Player player = getActiveChar(); + if (!PlayerConfig.DISABLE_TUTORIAL && (getCurrentHp() <= (player.getStat().getMaxHp() * .3))) + { + final QuestState qs = player.getQuestState("Q00255_Tutorial"); + if (qs != null) + { + qs.getQuest().notifyEvent("CE45", null, player); + } + } + + return result; + } + + @Override + public double getCurrentCp() + { + return _currentCp; + } + + @Override + public void setCurrentCp(double newCp) + { + setCurrentCp(newCp, true); + } + + public void setCurrentCp(double value, boolean broadcastPacket) + { + // Get the Max CP of the Creature. + final int currentCp = (int) _currentCp; + final Player player = getActiveChar(); + final int maxCp = player.getStat().getMaxCp(); + + synchronized (this) + { + if (player.isDead()) + { + return; + } + + final double newCp = Math.max(0, value); + if (newCp >= maxCp) + { + // Set the RegenActive flag to false. + _currentCp = maxCp; + _flagsRegenActive &= ~REGEN_FLAG_CP; + + // Stop the HP/MP/CP Regeneration task. + if (_flagsRegenActive == 0) + { + stopHpMpRegeneration(); + } + } + else + { + // Set the RegenActive flag to true. + _currentCp = newCp; + _flagsRegenActive |= REGEN_FLAG_CP; + + // Start the HP/MP/CP Regeneration task with Medium priority. + startHpMpRegeneration(); + } + } + + // Send the Server->Client packet StatusUpdate with current HP and MP to all other Player to inform. + if ((currentCp != _currentCp) && broadcastPacket) + { + player.broadcastStatusUpdate(); + } + } + + @Override + protected void doRegeneration() + { + final Player player = getActiveChar(); + final PlayerStat stat = player.getStat(); + + // Modify the current CP of the Creature and broadcast Server->Client packet StatusUpdate. + if (_currentCp < stat.getMaxRecoverableCp()) + { + setCurrentCp(_currentCp + Formulas.calcCpRegen(player), false); + } + + // Modify the current HP of the Creature and broadcast Server->Client packet StatusUpdate. + if (getCurrentHp() < stat.getMaxRecoverableHp()) + { + setCurrentHp(getCurrentHp() + Formulas.calcHpRegen(player), false); + } + + // Modify the current MP of the Creature and broadcast Server->Client packet StatusUpdate. + if (getCurrentMp() < stat.getMaxRecoverableMp()) + { + setCurrentMp(getCurrentMp() + Formulas.calcMpRegen(player), false); + } + + player.broadcastStatusUpdate(); // Send the StatusUpdate packet. + } + + @Override + public Player getActiveChar() + { + return super.getActiveChar().asPlayer(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/status/SiegeFlagStatus.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/status/SiegeFlagStatus.java new file mode 100644 index 0000000..5a52a9f --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/status/SiegeFlagStatus.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.status; + +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.instance.SiegeFlag; + +public class SiegeFlagStatus extends NpcStatus +{ + public SiegeFlagStatus(SiegeFlag activeChar) + { + super(activeChar); + } + + @Override + public void reduceHp(double value, Creature attacker) + { + reduceHp(value, attacker, true, false, false); + } + + @Override + public void reduceHp(double value, Creature attacker, boolean awake, boolean isDOT, boolean isHpConsumption) + { + if (getActiveChar().isAdvancedHeadquarter()) + { + super.reduceHp(value / 2, attacker, awake, isDOT, isHpConsumption); + } + + super.reduceHp(value, attacker, awake, isDOT, isHpConsumption); + } + + @Override + public SiegeFlag getActiveChar() + { + return (SiegeFlag) super.getActiveChar(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/status/StaticObjectStatus.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/status/StaticObjectStatus.java new file mode 100644 index 0000000..3c6044f --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/status/StaticObjectStatus.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.status; + +import org.l2jmobius.gameserver.entity.actor.instance.StaticObject; + +public class StaticObjectStatus extends CreatureStatus +{ + public StaticObjectStatus(StaticObject activeChar) + { + super(activeChar); + } + + @Override + public StaticObject getActiveChar() + { + return (StaticObject) super.getActiveChar(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/status/SummonStatus.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/status/SummonStatus.java new file mode 100644 index 0000000..7d64bc0 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/status/SummonStatus.java @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.status; + +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.Summon; +import org.l2jmobius.gameserver.entity.actor.holders.player.Duel; +import org.l2jmobius.gameserver.mechanics.stats.Stat; +import org.l2jmobius.gameserver.util.LocationUtil; + +public class SummonStatus extends PlayableStatus +{ + public SummonStatus(Summon activeChar) + { + super(activeChar); + } + + @Override + public void reduceHp(double value, Creature attacker) + { + reduceHp(value, attacker, true, false, false); + } + + @Override + public void reduceHp(double amount, Creature attacker, boolean awake, boolean isDOT, boolean isHPConsumption) + { + final Summon summon = getActiveChar(); + if ((attacker == null) || summon.isDead()) + { + return; + } + + final Player attackerPlayer = attacker.asPlayer(); + if ((attackerPlayer != null) && ((summon.getOwner() == null) || (summon.getOwner().getDuelId() != attackerPlayer.getDuelId()))) + { + attackerPlayer.setDuelState(Duel.DUELSTATE_INTERRUPTED); + } + + double value = amount; + final Player caster = summon.getTransferingDamageTo(); + if (summon.getOwner().getParty() != null) + { + if ((caster != null) && LocationUtil.checkIfInRange(1000, summon, caster, true) && !caster.isDead() && summon.getParty().getMembers().contains(caster)) + { + int transferDmg = Math.min((int) caster.getCurrentHp() - 1, ((int) value * (int) summon.getStat().calcStat(Stat.TRANSFER_DAMAGE_TO_PLAYER, 0, null, null)) / 100); + if (transferDmg > 0) + { + int membersInRange = 0; + for (Player member : caster.getParty().getMembers()) + { + if (LocationUtil.checkIfInRange(1000, member, caster, false) && (member != caster)) + { + membersInRange++; + } + } + + if (attacker.isPlayable() && (caster.getCurrentCp() > 0)) + { + if (caster.getCurrentCp() > transferDmg) + { + caster.getStatus().reduceCp(transferDmg); + } + else + { + transferDmg = (int) (transferDmg - caster.getCurrentCp()); + caster.getStatus().reduceCp((int) caster.getCurrentCp()); + } + } + + if (membersInRange > 0) + { + caster.reduceCurrentHp(transferDmg / membersInRange, attacker, null); + value -= transferDmg; + } + } + } + } + else if ((caster != null) && (caster == summon.getOwner()) && LocationUtil.checkIfInRange(1000, summon, caster, true) && !caster.isDead()) // When no party, transfer only to owner (caster). + { + int transferDmg = Math.min((int) caster.getCurrentHp() - 1, ((int) value * (int) summon.getStat().calcStat(Stat.TRANSFER_DAMAGE_TO_PLAYER, 0, null, null)) / 100); + if (transferDmg > 0) + { + if (attacker.isPlayable() && (caster.getCurrentCp() > 0)) + { + if (caster.getCurrentCp() > transferDmg) + { + caster.getStatus().reduceCp(transferDmg); + } + else + { + transferDmg = (int) (transferDmg - caster.getCurrentCp()); + caster.getStatus().reduceCp((int) caster.getCurrentCp()); + } + } + + caster.reduceCurrentHp(transferDmg, attacker, null); + value -= transferDmg; + } + } + + super.reduceHp(value, attacker, awake, isDOT, isHPConsumption); + } + + @Override + public Summon getActiveChar() + { + return super.getActiveChar().asSummon(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/attackable/CommandChannelTimer.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/attackable/CommandChannelTimer.java new file mode 100644 index 0000000..7bf4cac --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/attackable/CommandChannelTimer.java @@ -0,0 +1,54 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.actor.tasks.attackable; + +import org.l2jmobius.commons.threads.ThreadPool; +import org.l2jmobius.gameserver.config.PlayerConfig; +import org.l2jmobius.gameserver.entity.actor.Attackable; + +/** + * @author xban1x + */ +public class CommandChannelTimer implements Runnable +{ + private final Attackable _attackable; + + public CommandChannelTimer(Attackable attackable) + { + _attackable = attackable; + } + + @Override + public void run() + { + if (_attackable == null) + { + return; + } + + if ((System.currentTimeMillis() - _attackable.getCommandChannelLastAttack()) > PlayerConfig.LOOT_RAIDS_PRIVILEGE_INTERVAL) + { + _attackable.setCommandChannelTimer(null); + _attackable.setFirstCommandChannelAttacked(null); + _attackable.setCommandChannelLastAttack(0); + } + else + { + ThreadPool.schedule(this, 10000); // 10sec + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/attackable/OnKillNotifyTask.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/attackable/OnKillNotifyTask.java new file mode 100644 index 0000000..29418c1 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/attackable/OnKillNotifyTask.java @@ -0,0 +1,49 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.actor.tasks.attackable; + +import org.l2jmobius.gameserver.entity.actor.Attackable; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.mechanics.script.Quest; + +/** + * @author xban1x + */ +public class OnKillNotifyTask implements Runnable +{ + private final Attackable _attackable; + private final Quest _quest; + private final Player _killer; + private final boolean _isSummon; + + public OnKillNotifyTask(Attackable attackable, Quest quest, Player killer, boolean isSummon) + { + _attackable = attackable; + _quest = quest; + _killer = killer; + _isSummon = isSummon; + } + + @Override + public void run() + { + if ((_quest != null) && (_attackable != null) && (_killer != null)) + { + _quest.onKill(_attackable, _killer, _isSummon); + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/creature/HitTask.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/creature/HitTask.java new file mode 100644 index 0000000..8a98b2f --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/creature/HitTask.java @@ -0,0 +1,63 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.actor.tasks.creature; + +import org.l2jmobius.gameserver.entity.actor.Creature; + +/** + * Task launching the function onHitTimer().
        + * Actions: + *
          + *
        • If the attacker/target is dead or use fake death, notify the AI with CANCEL and send a Server->Client packet ActionFailed (if attacker is a Player)
        • + *
        • If attack isn't aborted, send a message system (critical hit, missed...) to attacker/target if they are Player
        • + *
        • If attack isn't aborted and hit isn't missed, reduce HP of the target and calculate reflection damage to reduce HP of attacker if necessary
        • + *
        • if attack isn't aborted and hit isn't missed, manage attack or cast break of the target (calculating rate, sending message...)
        • + *
        + * @author xban1x + */ +public class HitTask implements Runnable +{ + private final Creature _creature; + private final Creature _hitTarget; + private final int _damage; + private final boolean _crit; + private final boolean _miss; + private final byte _shld; + private final boolean _soulshot; + private final boolean _rechargeShots; + + public HitTask(Creature creature, Creature target, int damage, boolean crit, boolean miss, byte shld, boolean soulshot, boolean rechargeShots) + { + _creature = creature; + _hitTarget = target; + _damage = damage; + _crit = crit; + _shld = shld; + _miss = miss; + _soulshot = soulshot; + _rechargeShots = rechargeShots; + } + + @Override + public void run() + { + if (_creature != null) + { + _creature.onHitTimer(_hitTarget, _damage, _crit, _miss, _shld, _soulshot, _rechargeShots); + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/creature/MagicUseTask.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/creature/MagicUseTask.java new file mode 100644 index 0000000..758335c --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/creature/MagicUseTask.java @@ -0,0 +1,127 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.actor.tasks.creature; + +import java.util.List; + +import org.l2jmobius.gameserver.entity.WorldObject; +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.mechanics.skill.Skill; + +/** + * Task dedicated to magic use of character + * @author xban1x + */ +public class MagicUseTask implements Runnable +{ + private final Creature _creature; + private List _targets; + private final Skill _skill; + private int _count; + private int _skillTime; + private int _phase; + private final boolean _simultaneously; + + public MagicUseTask(Creature creature, List targets, Skill s, int hit, boolean simultaneous) + { + _creature = creature; + _targets = targets; + _skill = s; + _count = 0; + _phase = 1; + _skillTime = hit; + _simultaneously = simultaneous; + } + + @Override + public void run() + { + if (_creature == null) + { + return; + } + + switch (_phase) + { + case 1: + { + _creature.onMagicLaunchedTimer(this); + break; + } + case 2: + { + _creature.onMagicHitTimer(this); + break; + } + case 3: + { + _creature.onMagicFinalizer(this); + break; + } + } + } + + public int getCount() + { + return _count; + } + + public int getPhase() + { + return _phase; + } + + public Skill getSkill() + { + return _skill; + } + + public int getSkillTime() + { + return _skillTime; + } + + public List getTargets() + { + return _targets; + } + + public boolean isSimultaneous() + { + return _simultaneously; + } + + public void setCount(int count) + { + _count = count; + } + + public void setPhase(int phase) + { + _phase = phase; + } + + public void setSkillTime(int skillTime) + { + _skillTime = skillTime; + } + + public void setTargets(List targets) + { + _targets = targets; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/creature/QueuedMagicUseTask.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/creature/QueuedMagicUseTask.java new file mode 100644 index 0000000..d581b1a --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/creature/QueuedMagicUseTask.java @@ -0,0 +1,49 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.actor.tasks.creature; + +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.mechanics.skill.Skill; + +/** + * Task dedicated to qued magic use of character + * @author xban1x + */ +public class QueuedMagicUseTask implements Runnable +{ + private final Player _currPlayer; + private final Skill _queuedSkill; + private final boolean _isCtrlPressed; + private final boolean _isShiftPressed; + + public QueuedMagicUseTask(Player currPlayer, Skill queuedSkill, boolean isCtrlPressed, boolean isShiftPressed) + { + _currPlayer = currPlayer; + _queuedSkill = queuedSkill; + _isCtrlPressed = isCtrlPressed; + _isShiftPressed = isShiftPressed; + } + + @Override + public void run() + { + if (_currPlayer != null) + { + _currPlayer.useMagic(_queuedSkill, _isCtrlPressed, _isShiftPressed); + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/creature/UsePotionTask.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/creature/UsePotionTask.java new file mode 100644 index 0000000..1c8fae6 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/creature/UsePotionTask.java @@ -0,0 +1,45 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.actor.tasks.creature; + +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.mechanics.skill.Skill; + +/** + * Task dedicated to use potion of character + * @author xban1x + */ +public class UsePotionTask implements Runnable +{ + private final Creature _creature; + private final Skill _skill; + + public UsePotionTask(Creature creature, Skill skill) + { + _creature = creature; + _skill = skill; + } + + @Override + public void run() + { + if (_creature != null) + { + _creature.doSimultaneousCast(_skill); + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/cubics/CubicAction.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/cubics/CubicAction.java new file mode 100644 index 0000000..9afb9a0 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/cubics/CubicAction.java @@ -0,0 +1,238 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.tasks.cubics; + +import java.util.Collections; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.commons.util.Rnd; +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.instance.Cubic; +import org.l2jmobius.gameserver.entity.cubic.CubicSkill; +import org.l2jmobius.gameserver.entity.cubic.CubicTargetType; +import org.l2jmobius.gameserver.mechanics.effects.EffectType; +import org.l2jmobius.gameserver.mechanics.skill.Skill; +import org.l2jmobius.gameserver.network.serverpackets.MagicSkillUse; +import org.l2jmobius.gameserver.taskmanagers.AttackStanceTaskManager; + +/** + * Executes offensive and mixed cubic actions.
        + * Supports template-level and skill-level target routing. + * @author UnAfraid, Mobius + */ +public class CubicAction implements Runnable +{ + private static final Logger LOGGER = Logger.getLogger(CubicAction.class.getName()); + + private final Cubic _cubic; + private final AtomicInteger _currentCount = new AtomicInteger(); + private final int _chance; + + public CubicAction(Cubic cubic, int chance) + { + _cubic = cubic; + _chance = chance; + } + + @Override + public void run() + { + if (_cubic == null) + { + return; + } + + if (_cubic.getOwner().isDead() || !_cubic.getOwner().isOnline()) + { + _cubic.getOwner().removeCubic(_cubic.getId()); + _cubic.getOwner().broadcastUserInfo(); + return; + } + + try + { + if (!AttackStanceTaskManager.getInstance().hasAttackStanceTask(_cubic.getOwner())) + { + if (_cubic.getOwner().hasSummon()) + { + if (!AttackStanceTaskManager.getInstance().hasAttackStanceTask(_cubic.getOwner().getSummon())) + { + _cubic.stopAction(); + return; + } + } + else + { + _cubic.stopAction(); + return; + } + } + + if ((_cubic.getCubicMaxCount() > -1) && (_currentCount.get() >= _cubic.getCubicMaxCount())) + { + _cubic.stopAction(); + if ((_cubic.getTemplate() != null) && (_cubic.getTemplate().getUseUp() > 0)) + { + _cubic.getOwner().removeCubic(_cubic.getId()); + _cubic.getOwner().broadcastUserInfo(); + } + return; + } + + if (tryUsePrioritySkill()) + { + _currentCount.incrementAndGet(); + return; + } + + if (Rnd.get(100) >= _chance) + { + return; + } + + if (tryUseSkill(_cubic.getCubicSkill())) + { + _currentCount.incrementAndGet(); + } + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "Cubic action failed. cubicId: " + _cubic.getId() + " ownerId: " + _cubic.getOwner().getObjectId(), e); + } + } + + private boolean tryUsePrioritySkill() + { + for (CubicSkill cubicSkill : _cubic.getCubicSkills()) + { + if (cubicSkill.isPriority() && tryUseSkill(cubicSkill)) + { + return true; + } + } + + return false; + } + + private boolean tryUseSkill(CubicSkill cubicSkill) + { + if (cubicSkill == null) + { + return false; + } + + final Skill skill = cubicSkill.getSkill(); + if (skill == null) + { + return false; + } + + final Creature target = resolveTarget(cubicSkill); + if ((target == null) || target.isDead()) + { + return false; + } + + if ((_cubic.getTemplate() != null) && !_cubic.getTemplate().validateConditions(_cubic, _cubic.getOwner(), target)) + { + return false; + } + + if (!cubicSkill.validateConditions(_cubic, _cubic.getOwner(), target)) + { + return false; + } + + activateSkill(skill, target); + return true; + } + + private Creature resolveTarget(CubicSkill cubicSkill) + { + CubicTargetType targetType = _cubic.getTemplate() != null ? _cubic.getTemplate().getTargetType() : CubicTargetType.TARGET; + if (targetType == CubicTargetType.BY_SKILL) + { + targetType = cubicSkill.getTargetType(); + } + + switch (targetType) + { + case HEAL: + { + _cubic.cubicTargetForHeal(); + return _cubic.getTarget(); + } + case MASTER: + { + _cubic.setTarget(_cubic.getOwner()); + return _cubic.getOwner(); + } + case TARGET: + default: + { + _cubic.getCubicTarget(); + if (!Cubic.isInCubicRange(_cubic.getOwner(), _cubic.getTarget())) + { + _cubic.setTarget(null); + } + return _cubic.getTarget(); + } + } + } + + private void activateSkill(Skill skill, Creature target) + { + _cubic.getOwner().broadcastSkillPacket(new MagicSkillUse(_cubic.getOwner(), target, skill.getId(), skill.getLevel(), 0, 0), target); + if (skill.isContinuous()) + { + _cubic.useCubicContinuous(skill, Collections.singletonList(target)); + } + else + { + if (skill.hasEffectType(EffectType.MAGICAL_ATTACK)) + { + _cubic.useCubicMdam(skill, Collections.singletonList(target)); + } + else if (skill.hasEffectType(EffectType.HP_DRAIN)) + { + _cubic.useCubicDrain(skill, Collections.singletonList(target)); + } + else if (skill.hasEffectType(EffectType.STUN, EffectType.ROOT, EffectType.PARALYZE)) + { + _cubic.useCubicDisabler(skill, Collections.singletonList(target)); + } + else if (skill.hasEffectType(EffectType.DMG_OVER_TIME, EffectType.DMG_OVER_TIME_PERCENT)) + { + _cubic.useCubicContinuous(skill, Collections.singletonList(target)); + } + else if (skill.hasEffectType(EffectType.AGGRESSION)) + { + _cubic.useCubicDisabler(skill, Collections.singletonList(target)); + } + else + { + skill.activateSkill(_cubic, Collections.singletonList(target)); + } + } + } +} \ No newline at end of file diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/cubics/CubicDisappear.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/cubics/CubicDisappear.java new file mode 100644 index 0000000..ff3ab7c --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/cubics/CubicDisappear.java @@ -0,0 +1,43 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.actor.tasks.cubics; + +import org.l2jmobius.gameserver.entity.actor.instance.Cubic; + +/** + * Cubic disappear task. + * @author Zoey76 + */ +public class CubicDisappear implements Runnable +{ + private final Cubic _cubic; + + public CubicDisappear(Cubic cubic) + { + _cubic = cubic; + } + + @Override + public void run() + { + if (_cubic != null) + { + _cubic.getOwner().removeCubic(_cubic.getId()); + _cubic.getOwner().broadcastUserInfo(); + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/cubics/CubicHeal.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/cubics/CubicHeal.java new file mode 100644 index 0000000..44a33c0 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/cubics/CubicHeal.java @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.tasks.cubics; + +import java.util.Collections; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.instance.Cubic; +import org.l2jmobius.gameserver.entity.cubic.CubicSkill; +import org.l2jmobius.gameserver.mechanics.skill.Skill; +import org.l2jmobius.gameserver.network.serverpackets.MagicSkillUse; + +/** + * Executes heal cubic actions.
        + * Tracks successful heal usage so consumable heal cubics respect template limits. + * @author UnAfraid, Mobius + */ +public class CubicHeal implements Runnable +{ + private static final Logger LOGGER = Logger.getLogger(CubicHeal.class.getName()); + + private final Cubic _cubic; + private final AtomicInteger _currentCount = new AtomicInteger(); + + public CubicHeal(Cubic cubic) + { + _cubic = cubic; + } + + @Override + public void run() + { + if (_cubic == null) + { + return; + } + + if (_cubic.getOwner().isDead() || !_cubic.getOwner().isOnline()) + { + _cubic.getOwner().removeCubic(_cubic.getId()); + _cubic.getOwner().broadcastUserInfo(); + return; + } + + try + { + if ((_cubic.getCubicMaxCount() > -1) && (_currentCount.get() >= _cubic.getCubicMaxCount())) + { + _cubic.stopAction(); + if ((_cubic.getTemplate() != null) && (_cubic.getTemplate().getUseUp() > 0)) + { + _cubic.getOwner().removeCubic(_cubic.getId()); + _cubic.getOwner().broadcastUserInfo(); + } + return; + } + + _cubic.cubicTargetForHeal(); + final Creature target = _cubic.getTarget(); + if ((target == null) || target.isDead()) + { + return; + } + + final CubicSkill cubicSkill = getHealSkill(target); + if (cubicSkill == null) + { + return; + } + + final Skill skill = cubicSkill.getSkill(); + if ((skill == null) || ((target.getMaxHp() - target.getCurrentHp()) <= skill.getPower())) + { + return; + } + + skill.activateSkill(_cubic, Collections.singletonList(target)); + _cubic.getOwner().broadcastPacket(new MagicSkillUse(_cubic.getOwner(), target, skill.getId(), skill.getLevel(), 0, 0)); + _currentCount.incrementAndGet(); + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "Cubic heal failed. cubicId: " + _cubic.getId() + " ownerId: " + _cubic.getOwner().getObjectId(), e); + } + } + + private CubicSkill getHealSkill(Creature target) + { + if ((_cubic.getTemplate() != null) && !_cubic.getTemplate().validateConditions(_cubic, _cubic.getOwner(), target)) + { + return null; + } + + for (CubicSkill cubicSkill : _cubic.getCubicSkills()) + { + final Skill skill = cubicSkill.getSkill(); + if ((skill != null) && (skill.getId() == Cubic.SKILL_CUBIC_HEAL) && cubicSkill.validateConditions(_cubic, _cubic.getOwner(), target)) + { + return cubicSkill; + } + } + + return null; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/npc/trap/TrapTask.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/npc/trap/TrapTask.java new file mode 100644 index 0000000..6ba990d --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/npc/trap/TrapTask.java @@ -0,0 +1,88 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.actor.tasks.npc.trap; + +import java.util.logging.Logger; + +import org.l2jmobius.gameserver.entity.World; +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.instance.Trap; +import org.l2jmobius.gameserver.network.serverpackets.SocialAction; + +/** + * Trap task. + * @author Zoey76 + */ +public class TrapTask implements Runnable +{ + private static final Logger LOGGER = Logger.getLogger(TrapTask.class.getName()); + private static final int TICK = 1000; // 1s + private final Trap _trap; + + public TrapTask(Trap trap) + { + _trap = trap; + } + + @Override + public void run() + { + try + { + if (!_trap.isTriggered()) + { + if (_trap.hasLifeTime()) + { + _trap.setRemainingTime(_trap.getRemainingTime() - TICK); + if (_trap.getRemainingTime() < (_trap.getLifeTime() - 15000)) + { + _trap.broadcastPacket(new SocialAction(_trap.getObjectId(), 2)); + } + + if (_trap.getRemainingTime() <= 0) + { + switch (_trap.getSkill().getTargetType()) + { + case AURA: + case FRONT_AURA: + case BEHIND_AURA: + { + _trap.triggerTrap(_trap); + break; + } + default: + { + _trap.unSummon(); + } + } + return; + } + } + + World.forFirstVisibleObject(_trap, Creature.class, target -> _trap.checkTarget(target), target -> + { + _trap.triggerTrap(target); + }); + } + } + catch (Exception e) + { + LOGGER.severe(TrapTask.class.getSimpleName() + ": " + e.getMessage()); + _trap.unSummon(); + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/npc/trap/TrapTriggerTask.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/npc/trap/TrapTriggerTask.java new file mode 100644 index 0000000..b5099f4 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/npc/trap/TrapTriggerTask.java @@ -0,0 +1,48 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.actor.tasks.npc.trap; + +import org.l2jmobius.commons.threads.ThreadPool; +import org.l2jmobius.gameserver.entity.actor.instance.Trap; + +/** + * Trap trigger task. + * @author Zoey76 + */ +public class TrapTriggerTask implements Runnable +{ + private final Trap _trap; + + public TrapTriggerTask(Trap trap) + { + _trap = trap; + } + + @Override + public void run() + { + try + { + _trap.doCast(_trap.getSkill()); + ThreadPool.schedule(new TrapUnsummonTask(_trap), _trap.getSkill().getHitTime() + 300); + } + catch (Exception e) + { + _trap.unSummon(); + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/npc/trap/TrapUnsummonTask.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/npc/trap/TrapUnsummonTask.java new file mode 100644 index 0000000..5fd48bd --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/npc/trap/TrapUnsummonTask.java @@ -0,0 +1,39 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.actor.tasks.npc.trap; + +import org.l2jmobius.gameserver.entity.actor.instance.Trap; + +/** + * Trap unsummon task. + * @author Zoey76 + */ +public class TrapUnsummonTask implements Runnable +{ + private final Trap _trap; + + public TrapUnsummonTask(Trap trap) + { + _trap = trap; + } + + @Override + public void run() + { + _trap.unSummon(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/npc/walker/ArrivedTask.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/npc/walker/ArrivedTask.java new file mode 100644 index 0000000..12d83ad --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/npc/walker/ArrivedTask.java @@ -0,0 +1,44 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.actor.tasks.npc.walker; + +import org.l2jmobius.gameserver.entity.actor.Npc; +import org.l2jmobius.gameserver.entity.actor.holders.npc.WalkInfo; +import org.l2jmobius.gameserver.managers.WalkingManager; + +/** + * Walker arrive task. + * @author GKR + */ +public class ArrivedTask implements Runnable +{ + private final WalkInfo _walk; + private final Npc _npc; + + public ArrivedTask(Npc npc, WalkInfo walk) + { + _npc = npc; + _walk = walk; + } + + @Override + public void run() + { + _walk.setBlocked(false); + WalkingManager.getInstance().startMoving(_npc, _walk.getRoute().getName()); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/player/DismountTask.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/player/DismountTask.java new file mode 100644 index 0000000..6ea2bc2 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/player/DismountTask.java @@ -0,0 +1,42 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.actor.tasks.player; + +import org.l2jmobius.gameserver.entity.actor.Player; + +/** + * Task dedicated to dismount player from pet. + * @author UnAfraid + */ +public class DismountTask implements Runnable +{ + private final Player _player; + + public DismountTask(Player player) + { + _player = player; + } + + @Override + public void run() + { + if (_player != null) + { + _player.dismount(); + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/player/FameTask.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/player/FameTask.java new file mode 100644 index 0000000..1ee3a6f --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/player/FameTask.java @@ -0,0 +1,55 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.actor.tasks.player; + +import org.l2jmobius.gameserver.config.PlayerConfig; +import org.l2jmobius.gameserver.config.custom.OfflineTradeConfig; +import org.l2jmobius.gameserver.entity.actor.Player; + +/** + * Task dedicated to reward player with fame while standing on siege zone. + * @author UnAfraid + */ +public class FameTask implements Runnable +{ + private final Player _player; + private final int _value; + + public FameTask(Player player, int value) + { + _player = player; + _value = value; + } + + @Override + public void run() + { + if ((_player == null) || (_player.isDead() && !PlayerConfig.FAME_FOR_DEAD_PLAYERS)) + { + return; + } + + if (((_player.getClient() == null) || _player.getClient().isDetached()) && !OfflineTradeConfig.OFFLINE_FAME) + { + return; + } + + _player.setFame(_player.getFame() + _value); + _player.sendMessage("You have acquired " + _value + " reputation."); + _player.updateUserInfo(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/player/IllegalPlayerActionTask.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/player/IllegalPlayerActionTask.java new file mode 100644 index 0000000..976893d --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/player/IllegalPlayerActionTask.java @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.tasks.player; + +import java.util.logging.Logger; + +import org.l2jmobius.gameserver.config.GeneralConfig; +import org.l2jmobius.gameserver.data.xml.AdminData; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.enums.player.IllegalActionPunishmentType; +import org.l2jmobius.gameserver.managers.PunishmentManager; +import org.l2jmobius.gameserver.mechanics.punishment.PunishmentAffect; +import org.l2jmobius.gameserver.mechanics.punishment.PunishmentTask; +import org.l2jmobius.gameserver.mechanics.punishment.PunishmentType; +import org.l2jmobius.gameserver.network.Disconnection; +import org.l2jmobius.gameserver.network.serverpackets.LeaveWorld; + +/** + * Task that handles illegal player actions. + */ +public class IllegalPlayerActionTask implements Runnable +{ + private static final Logger AUDIT_LOGGER = Logger.getLogger("audit"); + + private final String _message; + private final IllegalActionPunishmentType _punishment; + private final Player _actor; + + public IllegalPlayerActionTask(Player actor, String message, IllegalActionPunishmentType punishment) + { + _message = message; + _punishment = punishment; + _actor = actor; + + switch (punishment) + { + case KICK: + { + _actor.sendMessage("You will be kicked for illegal action, GM informed."); + break; + } + case KICKBAN: + { + if (!_actor.isGM()) + { + _actor.setAccessLevel(-1); + _actor.setAccountAccesslevel(-1); + } + + _actor.sendMessage("You are banned for illegal action, GM informed."); + break; + } + case JAIL: + { + _actor.sendMessage("Illegal action performed!"); + _actor.sendMessage("You will be teleported to GM Consultation Service area and jailed."); + break; + } + } + } + + @Override + public void run() + { + final StringBuilder sb = new StringBuilder(); + sb.append("AUDIT, "); + sb.append(_message); + sb.append(", "); + sb.append(_actor); + sb.append(", "); + sb.append(_punishment); + AUDIT_LOGGER.info(sb.toString()); + + if (!_actor.isGM()) + { + switch (_punishment) + { + case BROADCAST: + { + AdminData.getInstance().broadcastMessageToGMs(_message); + return; + } + case KICK: + { + Disconnection.of(_actor).storeAndDeleteWith(LeaveWorld.STATIC_PACKET); + break; + } + case KICKBAN: + { + PunishmentManager.getInstance().startPunishment(new PunishmentTask(_actor.getObjectId(), PunishmentAffect.CHARACTER, PunishmentType.BAN, System.currentTimeMillis() + (GeneralConfig.DEFAULT_PUNISH_PARAM * 1000), _message, getClass().getSimpleName())); + break; + } + case JAIL: + { + PunishmentManager.getInstance().startPunishment(new PunishmentTask(_actor.getObjectId(), PunishmentAffect.CHARACTER, PunishmentType.JAIL, System.currentTimeMillis() + (GeneralConfig.DEFAULT_PUNISH_PARAM * 1000), _message, getClass().getSimpleName())); + break; + } + } + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/player/InventoryEnableTask.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/player/InventoryEnableTask.java new file mode 100644 index 0000000..0ee5921 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/player/InventoryEnableTask.java @@ -0,0 +1,42 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.actor.tasks.player; + +import org.l2jmobius.gameserver.entity.actor.Player; + +/** + * Task dedicated to enable player's inventory. + * @author UnAfraid + */ +public class InventoryEnableTask implements Runnable +{ + private final Player _player; + + public InventoryEnableTask(Player player) + { + _player = player; + } + + @Override + public void run() + { + if (_player != null) + { + _player.setInventoryBlockingStatus(false); + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/player/LookingForFishTask.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/player/LookingForFishTask.java new file mode 100644 index 0000000..612e78f --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/player/LookingForFishTask.java @@ -0,0 +1,69 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.actor.tasks.player; + +import org.l2jmobius.commons.util.Rnd; +import org.l2jmobius.gameserver.entity.actor.Player; + +/** + * Task dedicated for looking for fishes. + * @author UnAfraid + */ +public class LookingForFishTask implements Runnable +{ + private final Player _player; + private final boolean _isNoob; + private final boolean _isUpperGrade; + private final int _fishGroup; + private final double _fishGutsCheck; + private final long _endTaskTime; + + public LookingForFishTask(Player player, int startCombatTime, double fishGutsCheck, int fishGroup, boolean isNoob, boolean isUpperGrade) + { + _player = player; + _fishGutsCheck = fishGutsCheck; + _endTaskTime = System.currentTimeMillis() + (startCombatTime * 1000) + 10000; + _fishGroup = fishGroup; + _isNoob = isNoob; + _isUpperGrade = isUpperGrade; + } + + @Override + public void run() + { + if (_player != null) + { + if (System.currentTimeMillis() >= _endTaskTime) + { + _player.endFishing(false); + return; + } + + if (_fishGroup == -1) + { + return; + } + + final int check = Rnd.get(100); + if (_fishGutsCheck > check) + { + _player.stopLookingForFishTask(); + _player.startFishCombat(_isNoob, _isUpperGrade); + } + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/player/PetFeedTask.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/player/PetFeedTask.java new file mode 100644 index 0000000..ef88310 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/player/PetFeedTask.java @@ -0,0 +1,117 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.actor.tasks.player; + +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.gameserver.data.xml.PetDataTable; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.enums.player.MountType; +import org.l2jmobius.gameserver.entity.actor.enums.player.TeleportWhereType; +import org.l2jmobius.gameserver.entity.item.instance.Item; +import org.l2jmobius.gameserver.handler.IItemHandler; +import org.l2jmobius.gameserver.handler.ItemHandler; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; + +/** + * Task dedicated for feeding player's pet. + * @author UnAfraid + */ +public class PetFeedTask implements Runnable +{ + private static final Logger LOGGER = Logger.getLogger(PetFeedTask.class.getName()); + + private final Player _player; + + public PetFeedTask(Player player) + { + _player = player; + } + + @Override + public void run() + { + try + { + if (!_player.isMounted() || (_player.getMountNpcId() == 0) || (PetDataTable.getInstance().getPetData(_player.getMountNpcId()) == null)) + { + _player.stopFeed(); + return; + } + + if (_player.getCurrentFeed() > _player.getFeedConsume()) + { + // eat + _player.setCurrentFeed(_player.getCurrentFeed() - _player.getFeedConsume()); + } + else + { + _player.setCurrentFeed(0); + _player.stopFeed(); + + if (_player.isFlying() && (_player.getMountType() == MountType.WYVERN)) + { + _player.dismount(); + _player.teleToLocation(TeleportWhereType.TOWN); // Teleport to nearest village. + } + else + { + _player.dismount(); + } + + _player.sendPacket(SystemMessageId.YOU_ARE_OUT_OF_FEED_MOUNT_STATUS_CANCELED); + return; + } + + final Set foodIds = PetDataTable.getInstance().getPetData(_player.getMountNpcId()).getFood(); + if (foodIds.isEmpty()) + { + return; + } + + Item food = null; + for (int id : foodIds) + { + // TODO: possibly pet inv? + food = _player.getInventory().getItemByItemId(id); + if (food != null) + { + break; + } + } + + if ((food != null) && _player.isHungry()) + { + final IItemHandler handler = ItemHandler.getInstance().getHandler(food.getEtcItem()); + if (handler != null) + { + handler.onItemUse(_player, food, false); + final SystemMessage sm = new SystemMessage(SystemMessageId.YOUR_PET_WAS_HUNGRY_SO_IT_ATE_S1); + sm.addItemName(food.getId()); + _player.sendPacket(sm); + } + } + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "Mounted Pet [NpcId: " + _player.getMountNpcId() + "] a feed task error has occurred", e); + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/player/RentPetTask.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/player/RentPetTask.java new file mode 100644 index 0000000..3c8af20 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/player/RentPetTask.java @@ -0,0 +1,42 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.actor.tasks.player; + +import org.l2jmobius.gameserver.entity.actor.Player; + +/** + * Task dedicated to dismount player from rented pet. + * @author UnAfraid + */ +public class RentPetTask implements Runnable +{ + private final Player _player; + + public RentPetTask(Player player) + { + _player = player; + } + + @Override + public void run() + { + if (_player != null) + { + _player.stopRentPet(); + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/player/ResetChargesTask.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/player/ResetChargesTask.java new file mode 100644 index 0000000..862bafa --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/player/ResetChargesTask.java @@ -0,0 +1,42 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.actor.tasks.player; + +import org.l2jmobius.gameserver.entity.actor.Player; + +/** + * Task dedicated to reset player's current charges. + * @author UnAfraid + */ +public class ResetChargesTask implements Runnable +{ + private final Player _player; + + public ResetChargesTask(Player player) + { + _player = player; + } + + @Override + public void run() + { + if (_player != null) + { + _player.clearCharges(); + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/player/SitDownTask.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/player/SitDownTask.java new file mode 100644 index 0000000..45b922e --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/player/SitDownTask.java @@ -0,0 +1,43 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.actor.tasks.player; + +import org.l2jmobius.gameserver.entity.actor.Player; + +/** + * Task dedicated to put player to sit down. + * @author UnAfraid + */ +public class SitDownTask implements Runnable +{ + private final Player _player; + + public SitDownTask(Player player) + { + _player = player; + } + + @Override + public void run() + { + if (_player != null) + { + _player.setParalyzed(true); + _player.setSittingProgress(false); + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/player/StandUpTask.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/player/StandUpTask.java new file mode 100644 index 0000000..aaeae47 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/player/StandUpTask.java @@ -0,0 +1,47 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.actor.tasks.player; + +import org.l2jmobius.gameserver.entity.actor.Player; + +/** + * Task dedicated to put player to stand up. + * @author UnAfraid + */ +public class StandUpTask implements Runnable +{ + private final Player _player; + + public StandUpTask(Player player) + { + _player = player; + } + + @Override + public void run() + { + if (_player == null) + { + return; + } + + _player.setParalyzed(false); + _player.setSitting(false); + _player.setSittingProgress(false); + _player.getAI().setIntentionIdle(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/player/TeleportTask.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/player/TeleportTask.java new file mode 100644 index 0000000..5fd5d46 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/player/TeleportTask.java @@ -0,0 +1,44 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.actor.tasks.player; + +import org.l2jmobius.gameserver.entity.Location; +import org.l2jmobius.gameserver.entity.actor.Player; + +/** + * @author UnAfraid + */ +public class TeleportTask implements Runnable +{ + private final Player _player; + private final Location _loc; + + public TeleportTask(Player player, Location loc) + { + _player = player; + _loc = loc; + } + + @Override + public void run() + { + if ((_player != null) && _player.isOnline()) + { + _player.teleToLocation(_loc, true); + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/player/TeleportWatchdogTask.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/player/TeleportWatchdogTask.java new file mode 100644 index 0000000..36a5f7d --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/player/TeleportWatchdogTask.java @@ -0,0 +1,44 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.actor.tasks.player; + +import org.l2jmobius.gameserver.entity.actor.Player; + +/** + * Task dedicated watch for player teleportation. + * @author UnAfraid + */ +public class TeleportWatchdogTask implements Runnable +{ + private final Player _player; + + public TeleportWatchdogTask(Player player) + { + _player = player; + } + + @Override + public void run() + { + if ((_player == null) || !_player.isTeleporting()) + { + return; + } + + _player.onTeleported(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/player/VitalityTask.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/player/VitalityTask.java new file mode 100644 index 0000000..5bdcc5d --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/player/VitalityTask.java @@ -0,0 +1,53 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.actor.tasks.player; + +import org.l2jmobius.gameserver.config.RatesConfig; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.stat.PlayerStat; +import org.l2jmobius.gameserver.entity.zone.ZoneId; + +/** + * Task dedicated to reward player with vitality. + * @author UnAfraid + */ +public class VitalityTask implements Runnable +{ + private final Player _player; + + public VitalityTask(Player player) + { + _player = player; + } + + @Override + public void run() + { + if (!_player.isInsideZone(ZoneId.PEACE)) + { + return; + } + + if (_player.getVitalityPoints() >= PlayerStat.MAX_VITALITY_POINTS) + { + return; + } + + _player.updateVitalityPoints(RatesConfig.RATE_RECOVERY_VITALITY_PEACE_ZONE, false, false); + // _player.sendPacket(new ExVitalityPointInfo(_player.getVitalityPoints())); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/player/WarnUserTakeBreakTask.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/player/WarnUserTakeBreakTask.java new file mode 100644 index 0000000..d7f6270 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/player/WarnUserTakeBreakTask.java @@ -0,0 +1,50 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.actor.tasks.player; + +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.network.SystemMessageId; + +/** + * Task dedicated to warn user to take a break. + * @author UnAfraid + */ +public class WarnUserTakeBreakTask implements Runnable +{ + private final Player _player; + + public WarnUserTakeBreakTask(Player player) + { + _player = player; + } + + @Override + public void run() + { + if (_player != null) + { + if (_player.isOnline()) + { + _player.sendPacket(SystemMessageId.YOU_HAVE_BEEN_PLAYING_FOR_AN_EXTENDED_PERIOD_OF_TIME_PLEASE_CONSIDER_TAKING_A_BREAK); + } + else + { + _player.stopWarnUserTakeBreak(); + } + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/player/WaterTask.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/player/WaterTask.java new file mode 100644 index 0000000..53ac070 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/tasks/player/WaterTask.java @@ -0,0 +1,52 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.actor.tasks.player; + +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; + +/** + * Task dedicated to make damage to the player while drowning. + * @author UnAfraid + */ +public class WaterTask implements Runnable +{ + private final Player _player; + + public WaterTask(Player player) + { + _player = player; + } + + @Override + public void run() + { + if (_player == null) + { + return; + } + + final double reduceHp = (_player.getMaxHp() / 100.0) < 1 ? 1 : _player.getMaxHp() / 100.0; + _player.reduceCurrentHp(reduceHp, _player, false, false, null); + + // reduced hp, becouse not rest + final SystemMessage sm = new SystemMessage(SystemMessageId.YOU_HAVE_TAKEN_S1_DAMAGE_BECAUSE_YOU_WERE_UNABLE_TO_BREATHE); + sm.addInt((int) reduceHp); + _player.sendPacket(sm); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/templates/CreatureTemplate.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/templates/CreatureTemplate.java new file mode 100644 index 0000000..494dbef --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/templates/CreatureTemplate.java @@ -0,0 +1,648 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.actor.templates; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Map; + +import org.l2jmobius.gameserver.entity.actor.enums.creature.Race; +import org.l2jmobius.gameserver.entity.item.type.WeaponType; +import org.l2jmobius.gameserver.mechanics.events.ListenersContainer; +import org.l2jmobius.gameserver.mechanics.skill.Skill; +import org.l2jmobius.gameserver.mechanics.stats.MoveType; +import org.l2jmobius.gameserver.util.StatSet; + +/** + * Character template. + * @author Zoey76 + */ +public class CreatureTemplate extends ListenersContainer +{ + // BaseStats + private int _baseSTR; + private int _baseCON; + private int _baseDEX; + private int _baseINT; + private int _baseWIT; + private int _baseMEN; + protected float _baseHpMax; + private float _baseCpMax; + protected float _baseMpMax; + private float _baseHpReg; + private float _baseMpReg; + protected int _basePAtk; + protected int _baseMAtk; + protected int _basePDef; + protected int _baseMDef; + private int _basePAtkSpd; + private int _baseMAtkSpd; + private int _baseAttackRange; + private int _randomDamage; + private WeaponType _baseAttackType; + private int _baseShldDef; + private int _baseShldRate; + private int _baseCritRate; + private int _baseMCritRate; + + // SpecialStats + private int _baseBreath; + private int _baseFire; + private int _baseWind; + private int _baseWater; + private int _baseEarth; + private int _baseHoly; + private int _baseDark; + private double _baseFireRes; + private double _baseWindRes; + private double _baseWaterRes; + private double _baseEarthRes; + private double _baseHolyRes; + private double _baseDarkRes; + private double _baseElementRes; + /** For client info use {@link #_fCollisionRadius} */ + private int _collisionRadius; + /** For client info use {@link #_fCollisionHeight} */ + private int _collisionHeight; + + private float _fCollisionRadius; + private float _fCollisionHeight; + + private final double[] _moveType = new double[MoveType.values().length]; + /** The creature's race. */ + private Race _race; + + public CreatureTemplate(StatSet set) + { + set(set); + } + + public void set(StatSet set) + { + // Base stats + _baseSTR = set.getInt("baseSTR", 0); + _baseCON = set.getInt("baseCON", 0); + _baseDEX = set.getInt("baseDEX", 0); + _baseINT = set.getInt("baseINT", 0); + _baseWIT = set.getInt("baseWIT", 0); + _baseMEN = set.getInt("baseMEN", 0); + _baseHpMax = set.getFloat("baseHpMax", 0); + _baseCpMax = set.getFloat("baseCpMax", 0); + _baseMpMax = set.getFloat("baseMpMax", 0); + _baseHpReg = set.getFloat("baseHpReg", 0); + _baseMpReg = set.getFloat("baseMpReg", 0); + _basePAtk = set.getInt("basePAtk", 0); + _baseMAtk = set.getInt("baseMAtk", 0); + _basePDef = set.getInt("basePDef", 0); + _baseMDef = set.getInt("baseMDef", 0); + _basePAtkSpd = set.getInt("basePAtkSpd", 300); + _baseMAtkSpd = set.getInt("baseMAtkSpd", 333); + _baseShldDef = set.getInt("baseShldDef", 0); + _baseAttackRange = set.getInt("baseAtkRange", 40); + _randomDamage = set.getInt("baseRndDam", 0); + _baseAttackType = set.getEnum("baseAtkType", WeaponType.class, WeaponType.FIST); + _baseShldRate = set.getInt("baseShldRate", 0); + _baseCritRate = set.getInt("baseCritRate", 4); + _baseMCritRate = set.getInt("baseMCritRate", 5); + + // SpecialStats + _baseBreath = set.getInt("baseBreath", 100); + _baseFire = set.getInt("baseFire", 0); + _baseWind = set.getInt("baseWind", 0); + _baseWater = set.getInt("baseWater", 0); + _baseEarth = set.getInt("baseEarth", 0); + _baseHoly = set.getInt("baseHoly", 0); + _baseDark = set.getInt("baseDark", 0); + _baseFireRes = set.getInt("baseFireRes", 0); + _baseWindRes = set.getInt("baseWindRes", 0); + _baseWaterRes = set.getInt("baseWaterRes", 0); + _baseEarthRes = set.getInt("baseEarthRes", 0); + _baseHolyRes = set.getInt("baseHolyRes", 0); + _baseDarkRes = set.getInt("baseDarkRes", 0); + _baseElementRes = set.getInt("baseElementRes", 0); + + // Geometry + _fCollisionHeight = set.getFloat("collisionHeight", 0); + _fCollisionRadius = set.getFloat("collisionRadius", 0); + _collisionRadius = (int) _fCollisionRadius; + _collisionHeight = (int) _fCollisionHeight; + + // speed. + Arrays.fill(_moveType, 1); + setBaseMoveSpeed(MoveType.RUN, set.getDouble("baseRunSpd", 120)); + setBaseMoveSpeed(MoveType.WALK, set.getDouble("baseWalkSpd", 50)); + setBaseMoveSpeed(MoveType.FAST_SWIM, set.getDouble("baseSwimRunSpd", getBaseMoveSpeed(MoveType.RUN))); + setBaseMoveSpeed(MoveType.SLOW_SWIM, set.getDouble("baseSwimWalkSpd", getBaseMoveSpeed(MoveType.WALK))); + setBaseMoveSpeed(MoveType.FAST_FLY, set.getDouble("baseFlyRunSpd", getBaseMoveSpeed(MoveType.RUN))); + setBaseMoveSpeed(MoveType.SLOW_FLY, set.getDouble("baseFlyWalkSpd", getBaseMoveSpeed(MoveType.WALK))); + } + + /** + * @return the baseHpMax + */ + public float getBaseHpMax() + { + return _baseHpMax; + } + + /** + * @return the _baseFire + */ + public int getBaseFire() + { + return _baseFire; + } + + /** + * @return the _baseWind + */ + public int getBaseWind() + { + return _baseWind; + } + + /** + * @return the _baseWater + */ + public int getBaseWater() + { + return _baseWater; + } + + /** + * @return the _baseEarth + */ + public int getBaseEarth() + { + return _baseEarth; + } + + /** + * @return the _baseHoly + */ + public int getBaseHoly() + { + return _baseHoly; + } + + /** + * @return the _baseDark + */ + public int getBaseDark() + { + return _baseDark; + } + + /** + * @return the _baseFireRes + */ + public double getBaseFireRes() + { + return _baseFireRes; + } + + /** + * @return the _baseWindRes + */ + public double getBaseWindRes() + { + return _baseWindRes; + } + + /** + * @return the _baseWaterRes + */ + public double getBaseWaterRes() + { + return _baseWaterRes; + } + + /** + * @return the _baseEarthRes + */ + public double getBaseEarthRes() + { + return _baseEarthRes; + } + + /** + * @return the _baseHolyRes + */ + public double getBaseHolyRes() + { + return _baseHolyRes; + } + + /** + * @return the _baseDarkRes + */ + public double getBaseDarkRes() + { + return _baseDarkRes; + } + + /** + * @return the _baseElementRes + */ + public double getBaseElementRes() + { + return _baseElementRes; + } + + /** + * @return the baseSTR + */ + public int getBaseSTR() + { + return _baseSTR; + } + + /** + * @return the baseCON + */ + public int getBaseCON() + { + return _baseCON; + } + + /** + * @return the baseDEX + */ + public int getBaseDEX() + { + return _baseDEX; + } + + /** + * @return the baseINT + */ + public int getBaseINT() + { + return _baseINT; + } + + /** + * @return the baseWIT + */ + public int getBaseWIT() + { + return _baseWIT; + } + + /** + * @return the baseMEN + */ + public int getBaseMEN() + { + return _baseMEN; + } + + /** + * @return the baseCpMax + */ + public float getBaseCpMax() + { + return _baseCpMax; + } + + /** + * @return the baseMpMax + */ + public float getBaseMpMax() + { + return _baseMpMax; + } + + /** + * @return the baseHpReg + */ + public float getBaseHpReg() + { + return _baseHpReg; + } + + /** + * @return the baseMpReg + */ + public float getBaseMpReg() + { + return _baseMpReg; + } + + /** + * @return the basePAtk + */ + public int getBasePAtk() + { + return _basePAtk; + } + + /** + * @return the baseMAtk + */ + public int getBaseMAtk() + { + return _baseMAtk; + } + + /** + * @return the basePDef + */ + public int getBasePDef() + { + return _basePDef; + } + + /** + * @return the baseMDef + */ + public int getBaseMDef() + { + return _baseMDef; + } + + /** + * @return the basePAtkSpd + */ + public int getBasePAtkSpd() + { + return _basePAtkSpd; + } + + /** + * @return the baseMAtkSpd + */ + public int getBaseMAtkSpd() + { + return _baseMAtkSpd; + } + + /** + * @return the random damage + */ + public int getRandomDamage() + { + return _randomDamage; + } + + /** + * @return the baseShldDef + */ + public int getBaseShldDef() + { + return _baseShldDef; + } + + /** + * @return the baseShldRate + */ + public int getBaseShldRate() + { + return _baseShldRate; + } + + /** + * @return the baseCritRate + */ + public int getBaseCritRate() + { + return _baseCritRate; + } + + /** + * @return the baseMCritRate + */ + public int getBaseMCritRate() + { + return _baseMCritRate; + } + + public void setBaseMoveSpeed(MoveType type, double value) + { + _moveType[type.ordinal()] = value; + } + + public double getBaseMoveSpeed(MoveType mt) + { + return _moveType[mt.ordinal()]; + } + + /** + * @return the baseBreath + */ + public int getBaseBreath() + { + return _baseBreath; + } + + /** + * @return the collisionRadius + */ + public int getCollisionRadius() + { + return _collisionRadius; + } + + /** + * @return the collisionHeight + */ + public int getCollisionHeight() + { + return _collisionHeight; + } + + /** + * @return the fCollisionRadius + */ + public float getFCollisionRadius() + { + return _fCollisionRadius; + } + + /** + * @return the fCollisionHeight + */ + public float getFCollisionHeight() + { + return _fCollisionHeight; + } + + /** + * @param baseFire the baseFire to set + */ + public void setBaseFire(int baseFire) + { + _baseFire = baseFire; + } + + /** + * @param baseWater the baseWater to set + */ + public void setBaseWater(int baseWater) + { + _baseWater = baseWater; + } + + /** + * @param baseEarth the baseEarth to set + */ + public void setBaseEarth(int baseEarth) + { + _baseEarth = baseEarth; + } + + /** + * @param baseWind the baseWind to set + */ + public void setBaseWind(int baseWind) + { + _baseWind = baseWind; + } + + /** + * @param baseHoly the baseHoly to set + */ + public void setBaseHoly(int baseHoly) + { + _baseHoly = baseHoly; + } + + /** + * @param baseDark the baseDark to set + */ + public void setBaseDark(int baseDark) + { + _baseDark = baseDark; + } + + /** + * @param baseFireRes the baseFireRes to set + */ + public void setBaseFireRes(double baseFireRes) + { + _baseFireRes = baseFireRes; + } + + /** + * @param baseWaterRes the baseWaterRes to set + */ + public void setBaseWaterRes(double baseWaterRes) + { + _baseWaterRes = baseWaterRes; + } + + /** + * @param baseEarthRes the baseEarthRes to set + */ + public void setBaseEarthRes(double baseEarthRes) + { + _baseEarthRes = baseEarthRes; + } + + /** + * @param baseWindRes the baseWindRes to set + */ + public void setBaseWindRes(double baseWindRes) + { + _baseWindRes = baseWindRes; + } + + /** + * @param baseHolyRes the baseHolyRes to set + */ + public void setBaseHolyRes(double baseHolyRes) + { + _baseHolyRes = baseHolyRes; + } + + /** + * @param baseDarkRes the baseDarkRes to set + */ + public void setBaseDarkRes(double baseDarkRes) + { + _baseDarkRes = baseDarkRes; + } + + /** + * @param baseElementRes + */ + public void setBaseElementRes(double baseElementRes) + { + _baseElementRes = baseElementRes; + } + + /** + * @return the base attack type (Sword, Fist, Blunt, etc..) + */ + public WeaponType getBaseAttackType() + { + return _baseAttackType; + } + + /** + * Sets base attack type. + * @param type + */ + public void setBaseAttackType(WeaponType type) + { + _baseAttackType = type; + } + + /** + * @return the baseAtkRange + */ + public int getBaseAttackRange() + { + return _baseAttackRange; + } + + /** + * Sets base attack range. + * @param value + */ + public void setBaseAttackRange(int value) + { + _baseAttackRange = value; + } + + /** + * Overridden in NpcTemplate + * @return the characters skills + */ + public Map getSkills() + { + return Collections.emptyMap(); + } + + /** + * Gets the craeture's race. + * @return the race + */ + public Race getRace() + { + return _race; + } + + /** + * Sets the creature's race. + * @param race the race + */ + public void setRace(Race race) + { + _race = race; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/templates/CubicTemplate.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/templates/CubicTemplate.java new file mode 100644 index 0000000..797a616 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/templates/CubicTemplate.java @@ -0,0 +1,164 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.actor.templates; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.l2jmobius.gameserver.entity.WorldObject; +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.instance.Cubic; +import org.l2jmobius.gameserver.entity.cubic.CubicSelectionMode; +import org.l2jmobius.gameserver.entity.cubic.CubicSkill; +import org.l2jmobius.gameserver.entity.cubic.CubicTargetType; +import org.l2jmobius.gameserver.entity.cubic.ICubicConditionHolder; +import org.l2jmobius.gameserver.entity.cubic.conditions.ICubicCondition; +import org.l2jmobius.gameserver.util.StatSet; + +/** + * Cubic template loaded from datapack XML.
        + * Keeps static cubic runtime data and condition lists. + *
          + *
        • Stores activation timing.
        • + *
        • Stores cubic skills.
        • + *
        • Validates template-level conditions.
        • + *
        + * @author UnAfraid + */ +public class CubicTemplate extends CreatureTemplate implements ICubicConditionHolder +{ + private final int _id; + private final int _level; + private final int _slot; + private final int _duration; + private final int _delay; + private final int _maxCount; + private final int _useUp; + private final double _power; + private final CubicTargetType _targetType; + private final CubicSelectionMode _selectionMode; + private final List _conditions = new ArrayList<>(); + private final List _skills = new ArrayList<>(); + + public CubicTemplate(StatSet set) + { + super(set); + _id = set.getInt("id"); + _level = set.getInt("level"); + _slot = set.getInt("slot"); + _duration = set.getInt("duration"); + _delay = set.getInt("delay"); + _maxCount = set.getInt("maxCount"); + _useUp = set.getInt("useUp"); + _power = set.getDouble("power", 0); + _targetType = set.getEnum("targetType", CubicTargetType.class, CubicTargetType.TARGET); + _selectionMode = set.getEnum("selectionMode", CubicSelectionMode.class, CubicSelectionMode.RANDOM); + } + + public int getId() + { + return _id; + } + + public int getLevel() + { + return _level; + } + + public int getSlot() + { + return _slot; + } + + public int getDuration() + { + return _duration; + } + + public int getDelay() + { + return _delay; + } + + public int getMaxCount() + { + return _maxCount; + } + + public int getUseUp() + { + return _useUp; + } + + public CubicTargetType getTargetType() + { + return _targetType; + } + + public CubicSelectionMode getSelectionMode() + { + return _selectionMode; + } + + public List getCubicSkills() + { + return Collections.unmodifiableList(_skills); + } + + public void addCubicSkill(CubicSkill skill) + { + _skills.add(skill); + } + + @Override + public int getBasePAtk() + { + return (int) _power; + } + + @Override + public int getBaseMAtk() + { + return (int) _power; + } + + @Override + public boolean validateConditions(Cubic cubic, Creature owner, WorldObject target) + { + if (_conditions.isEmpty()) + { + return true; + } + + for (ICubicCondition condition : _conditions) + { + if (!condition.test(cubic, owner, target)) + { + return false; + } + } + + return true; + } + + @Override + public void addCondition(ICubicCondition condition) + { + _conditions.add(condition); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/templates/DoorTemplate.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/templates/DoorTemplate.java new file mode 100644 index 0000000..40d6d8c --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/templates/DoorTemplate.java @@ -0,0 +1,250 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.actor.templates; + +import org.l2jmobius.gameserver.entity.actor.instance.Door; +import org.l2jmobius.gameserver.util.StatSet; + +/** + * Doors template. + * @author JIV + */ +public class DoorTemplate extends CreatureTemplate +{ + private final int _doorId; + private final int[] _nodeX; + private final int[] _nodeY; + private final int _nodeZ; + private final int _height; + private final int _posX; + private final int _posY; + private final int _posZ; + private final int _emmiter; + private final int _childDoorId; + private final String _name; + private final String _groupName; + private final boolean _showHp; + private final boolean _isWall; + + // -1 close, 0 nothing, 1 open + private final byte _masterDoorClose; + private final byte _masterDoorOpen; + + private final boolean _isTargetable; + private final boolean _default_status; + + private int _openTime; + private int _randomTime; + private final int _closeTime; + private final int _level; + private final int _openType; + private final boolean _checkCollision; + private final boolean _isAttackableDoor; + private final int _clanhallId; + private final boolean _stealth; + + public DoorTemplate(StatSet set) + { + super(set); + _doorId = set.getInt("id"); + _name = set.getString("name"); + + // position + final String[] pos = set.getString("pos").split(","); + _height = set.getInt("height"); + _nodeZ = set.getInt("nodeZ"); + _nodeX = new int[4]; // 4 * x + _nodeY = new int[4]; // 4 * y + for (int i = 0; i < 4; i++) + { + final String[] split = set.getString("node" + (i + 1)).split(","); + _nodeX[i] = Integer.parseInt(split[0]); + _nodeY[i] = Integer.parseInt(split[1]); + } + + _posX = Integer.parseInt(pos[0]); + _posY = Integer.parseInt(pos[1]); + _posZ = Math.min(Integer.parseInt(pos[2]), _nodeZ); + + // optional + _emmiter = set.getInt("emitter_id", 0); + _showHp = set.getBoolean("hp_showable", true); + _isWall = set.getBoolean("is_wall", false); + _groupName = set.getString("group", null); + _childDoorId = set.getInt("child_id_event", -1); + + // true if door is opening + String masterevent = set.getString("master_close_event", "act_nothing"); + _masterDoorClose = (byte) (masterevent.equals("act_open") ? 1 : masterevent.equals("act_close") ? -1 : 0); + masterevent = set.getString("master_open_event", "act_nothing"); + _masterDoorOpen = (byte) (masterevent.equals("act_open") ? 1 : masterevent.equals("act_close") ? -1 : 0); + _isTargetable = set.getBoolean("targetable", true); + _default_status = set.getString("default_status", "close").equals("open"); + _closeTime = set.getInt("close_time", -1); + _level = set.getInt("level", 0); + _openType = set.getInt("open_method", 0); + _checkCollision = set.getBoolean("check_collision", true); + if ((_openType & Door.OPEN_BY_TIME) == Door.OPEN_BY_TIME) + { + _openTime = set.getInt("open_time"); + _randomTime = set.getInt("random_time", -1); + } + + _isAttackableDoor = set.getBoolean("is_attackable", false); + _clanhallId = set.getInt("clanhall_id", 0); + _stealth = set.getBoolean("stealth", false); + } + + /** + * Gets the door ID. + * @return the door ID + */ + public int getId() + { + return _doorId; + } + + public String getName() + { + return _name; + } + + public int[] getNodeX() + { + return _nodeX; + } + + public int[] getNodeY() + { + return _nodeY; + } + + public int getNodeZ() + { + return _nodeZ; + } + + public int getHeight() + { + return _height; + } + + public int getX() + { + return _posX; + } + + public int getY() + { + return _posY; + } + + public int getZ() + { + return _posZ; + } + + public int getEmmiter() + { + return _emmiter; + } + + public int getChildDoorId() + { + return _childDoorId; + } + + public String getGroupName() + { + return _groupName; + } + + public boolean isShowHp() + { + return _showHp; + } + + public boolean isWall() + { + return _isWall; + } + + public byte getMasterDoorOpen() + { + return _masterDoorOpen; + } + + public byte getMasterDoorClose() + { + return _masterDoorClose; + } + + public boolean isTargetable() + { + return _isTargetable; + } + + public boolean isOpenByDefault() + { + return _default_status; + } + + public int getOpenTime() + { + return _openTime; + } + + public int getRandomTime() + { + return _randomTime; + } + + public int getCloseTime() + { + return _closeTime; + } + + public int getLevel() + { + return _level; + } + + public int getOpenType() + { + return _openType; + } + + public boolean isCheckCollision() + { + return _checkCollision; + } + + public boolean isAttackable() + { + return _isAttackableDoor; + } + + public int getClanHallId() + { + return _clanhallId; + } + + public boolean isStealth() + { + return _stealth; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/templates/NpcTemplate.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/templates/NpcTemplate.java new file mode 100644 index 0000000..df168c3 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/templates/NpcTemplate.java @@ -0,0 +1,1352 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.actor.templates; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.logging.Logger; + +import org.l2jmobius.commons.util.Rnd; +import org.l2jmobius.gameserver.config.NpcConfig; +import org.l2jmobius.gameserver.config.RatesConfig; +import org.l2jmobius.gameserver.config.custom.ChampionMonstersConfig; +import org.l2jmobius.gameserver.config.custom.NpcStatMultipliersConfig; +import org.l2jmobius.gameserver.config.custom.PremiumSystemConfig; +import org.l2jmobius.gameserver.data.sql.CharInfoTable; +import org.l2jmobius.gameserver.data.xml.ItemData; +import org.l2jmobius.gameserver.data.xml.NpcData; +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.enums.creature.Race; +import org.l2jmobius.gameserver.entity.actor.enums.npc.AISkillScope; +import org.l2jmobius.gameserver.entity.actor.enums.npc.AIType; +import org.l2jmobius.gameserver.entity.actor.enums.npc.DropType; +import org.l2jmobius.gameserver.entity.actor.enums.player.PlayerClass; +import org.l2jmobius.gameserver.entity.actor.enums.player.Sex; +import org.l2jmobius.gameserver.entity.actor.holders.npc.DropGroupHolder; +import org.l2jmobius.gameserver.entity.actor.holders.npc.DropHolder; +import org.l2jmobius.gameserver.entity.actor.holders.npc.FakePlayerHolder; +import org.l2jmobius.gameserver.entity.actor.stat.PlayerStat; +import org.l2jmobius.gameserver.entity.item.ItemTemplate; +import org.l2jmobius.gameserver.entity.item.holders.ItemHolder; +import org.l2jmobius.gameserver.entity.itemcontainer.Inventory; +import org.l2jmobius.gameserver.mechanics.skill.Skill; +import org.l2jmobius.gameserver.util.MathUtil; +import org.l2jmobius.gameserver.util.StatSet; + +/** + * @author NosBit, Nobius + */ +public class NpcTemplate extends CreatureTemplate +{ + private static final Logger LOGGER = Logger.getLogger(NpcTemplate.class.getName()); + + private int _id; + private int _displayId; + private byte _level; + private String _type; + private String _name; + private boolean _usingServerSideName; + private String _title; + private boolean _usingServerSideTitle; + private StatSet _parameters; + private Sex _sex; + private int _chestId; + private int _rhandId; + private int _lhandId; + private int _weaponEnchant; + private double _exp; + private double _sp; + private boolean _unique; + private boolean _attackable; + private boolean _targetable; + private boolean _talkable; + private boolean _isQuestMonster; + private boolean _undying; + private boolean _showName; + private boolean _randomWalk; + private boolean _randomAnimation; + private boolean _flying; + private boolean _fakePlayer; + private FakePlayerHolder _fakePlayerInfo; + private boolean _canMove; + private boolean _noSleepMode; + private boolean _passableDoor; + private boolean _hasSummoner; + private boolean _canBeSown; + private int _corpseTime; + private AIType _aiType; + private int _aggroRange; + private int _clanHelpRange; + private boolean _isChaos; + private boolean _isAggressive; + private int _soulShot; + private int _spiritShot; + private int _soulShotChance; + private int _spiritShotChance; + private int _minSkillChance; + private int _maxSkillChance; + private int _baseAttackAngle; + private Map _skills; + private Map> _aiSkillLists; + private Set _clans; + private Set _ignoreClanNpcIds; + private List _dropGroups; + private List _dropListDeath; + private List _dropListSpoil; + private double _collisionRadiusGrown; + private double _collisionHeightGrown; + + private final List _teachInfo = new ArrayList<>(); + + /** + * Constructor of Creature. + * @param set The StatSet object to transfer data to the method + */ + public NpcTemplate(StatSet set) + { + super(set); + } + + @Override + public void set(StatSet set) + { + super.set(set); + _id = set.getInt("id"); + _displayId = set.getInt("displayId", _id); + _level = set.getByte("level", (byte) 70); + _type = set.getString("type", "Folk"); + _name = set.getString("name", ""); + _usingServerSideName = set.getBoolean("usingServerSideName", false); + _title = set.getString("title", ""); + _usingServerSideTitle = set.getBoolean("usingServerSideTitle", false); + setRace(set.getEnum("race", Race.class, Race.NONE)); + _sex = set.getEnum("sex", Sex.class, Sex.ETC); + _chestId = set.getInt("chestId", 0); + _rhandId = set.getInt("rhandId", 0); + _lhandId = set.getInt("lhandId", 0); + _weaponEnchant = set.getInt("weaponEnchant", 0); + _exp = set.getDouble("exp", 0); + _sp = set.getDouble("sp", 0); + _unique = set.getBoolean("unique", false); + _attackable = set.getBoolean("attackable", true); + _targetable = set.getBoolean("targetable", true); + _talkable = set.getBoolean("talkable", true); + _isQuestMonster = _title.contains("Quest"); + _undying = set.getBoolean("undying", !_type.equals("Monster") && !_type.equals("RaidBoss") && !_type.equals("GrandBoss")); + _showName = set.getBoolean("showName", true); + _randomWalk = set.getBoolean("randomWalk", !_type.equals("Guard")); + _randomAnimation = set.getBoolean("randomAnimation", true); + _flying = set.getBoolean("flying", false); + _fakePlayer = set.getBoolean("fakePlayer", false); + if (_fakePlayer) + { + _fakePlayerInfo = new FakePlayerHolder(set); + + // Check if a character with the same name already exists. + if (CharInfoTable.getInstance().getIdByName(_name) > 0) + { + LOGGER.info(getClass().getSimpleName() + ": Fake player id [" + _id + "] conflict. A real player with name [" + _name + "] already exists."); + } + } + + _canMove = (set.getDouble("baseWalkSpd", 1d) <= 0.1) || set.getBoolean("canMove", true); + _noSleepMode = set.getBoolean("noSleepMode", false); + _passableDoor = set.getBoolean("passableDoor", false); + _hasSummoner = set.getBoolean("hasSummoner", false); + _canBeSown = set.getBoolean("canBeSown", false); + _corpseTime = set.getInt("corpseTime", NpcConfig.DEFAULT_CORPSE_TIME); + _aiType = set.getEnum("aiType", AIType.class, AIType.FIGHTER); + + // L2J aggro range tempfix + final int aggroValue = set.getInt("aggroRange", 0); + if (_type.equalsIgnoreCase("Monster") && (aggroValue > NpcConfig.MAX_AGGRO_RANGE)) + { + _aggroRange = NpcConfig.MAX_AGGRO_RANGE; + } + else + { + _aggroRange = aggroValue; + } + + _clanHelpRange = set.getInt("clanHelpRange", 0); + _isChaos = set.getBoolean("isChaos", false); + _isAggressive = set.getBoolean("isAggressive", true); + _soulShot = set.getInt("soulShot", 0); + _spiritShot = set.getInt("spiritShot", 0); + _soulShotChance = set.getInt("soulShotChance", 0); + _spiritShotChance = set.getInt("spiritShotChance", 0); + _minSkillChance = set.getInt("minSkillChance", 7); + _maxSkillChance = set.getInt("maxSkillChance", 15); + _baseAttackAngle = set.getInt("width", 120); + _collisionRadiusGrown = set.getDouble("collisionRadiusGrown", 0); + _collisionHeightGrown = set.getDouble("collisionHeightGrown", 0); + if (NpcStatMultipliersConfig.ENABLE_NPC_STAT_MULTIPLIERS) // Custom NPC Stat Multipliers + { + switch (_type) + { + case "Monster": + { + _baseHpMax *= NpcStatMultipliersConfig.MONSTER_HP_MULTIPLIER; + _baseMpMax *= NpcStatMultipliersConfig.MONSTER_MP_MULTIPLIER; + _basePAtk *= NpcStatMultipliersConfig.MONSTER_PATK_MULTIPLIER; + _baseMAtk *= NpcStatMultipliersConfig.MONSTER_MATK_MULTIPLIER; + _basePDef *= NpcStatMultipliersConfig.MONSTER_PDEF_MULTIPLIER; + _baseMDef *= NpcStatMultipliersConfig.MONSTER_MDEF_MULTIPLIER; + _aggroRange *= NpcStatMultipliersConfig.MONSTER_AGRRO_RANGE_MULTIPLIER; + _clanHelpRange *= NpcStatMultipliersConfig.MONSTER_CLAN_HELP_RANGE_MULTIPLIER; + break; + } + case "RaidBoss": + case "GrandBoss": + { + _baseHpMax *= NpcStatMultipliersConfig.RAIDBOSS_HP_MULTIPLIER; + _baseMpMax *= NpcStatMultipliersConfig.RAIDBOSS_MP_MULTIPLIER; + _basePAtk *= NpcStatMultipliersConfig.RAIDBOSS_PATK_MULTIPLIER; + _baseMAtk *= NpcStatMultipliersConfig.RAIDBOSS_MATK_MULTIPLIER; + _basePDef *= NpcStatMultipliersConfig.RAIDBOSS_PDEF_MULTIPLIER; + _baseMDef *= NpcStatMultipliersConfig.RAIDBOSS_MDEF_MULTIPLIER; + _aggroRange *= NpcStatMultipliersConfig.RAIDBOSS_AGRRO_RANGE_MULTIPLIER; + _clanHelpRange *= NpcStatMultipliersConfig.RAIDBOSS_CLAN_HELP_RANGE_MULTIPLIER; + break; + } + case "Guard": + { + _baseHpMax *= NpcStatMultipliersConfig.GUARD_HP_MULTIPLIER; + _baseMpMax *= NpcStatMultipliersConfig.GUARD_MP_MULTIPLIER; + _basePAtk *= NpcStatMultipliersConfig.GUARD_PATK_MULTIPLIER; + _baseMAtk *= NpcStatMultipliersConfig.GUARD_MATK_MULTIPLIER; + _basePDef *= NpcStatMultipliersConfig.GUARD_PDEF_MULTIPLIER; + _baseMDef *= NpcStatMultipliersConfig.GUARD_MDEF_MULTIPLIER; + _aggroRange *= NpcStatMultipliersConfig.GUARD_AGRRO_RANGE_MULTIPLIER; + _clanHelpRange *= NpcStatMultipliersConfig.GUARD_CLAN_HELP_RANGE_MULTIPLIER; + break; + } + case "Defender": + { + _baseHpMax *= NpcStatMultipliersConfig.DEFENDER_HP_MULTIPLIER; + _baseMpMax *= NpcStatMultipliersConfig.DEFENDER_MP_MULTIPLIER; + _basePAtk *= NpcStatMultipliersConfig.DEFENDER_PATK_MULTIPLIER; + _baseMAtk *= NpcStatMultipliersConfig.DEFENDER_MATK_MULTIPLIER; + _basePDef *= NpcStatMultipliersConfig.DEFENDER_PDEF_MULTIPLIER; + _baseMDef *= NpcStatMultipliersConfig.DEFENDER_MDEF_MULTIPLIER; + _aggroRange *= NpcStatMultipliersConfig.DEFENDER_AGRRO_RANGE_MULTIPLIER; + _clanHelpRange *= NpcStatMultipliersConfig.DEFENDER_CLAN_HELP_RANGE_MULTIPLIER; + break; + } + } + } + } + + public int getId() + { + return _id; + } + + public int getDisplayId() + { + return _displayId; + } + + public byte getLevel() + { + return _level; + } + + public String getType() + { + return _type; + } + + public boolean isType(String type) + { + return _type.equalsIgnoreCase(type); + } + + public String getName() + { + return _name; + } + + public boolean isUsingServerSideName() + { + return _usingServerSideName; + } + + public String getTitle() + { + return _title; + } + + public boolean isUsingServerSideTitle() + { + return _usingServerSideTitle; + } + + public StatSet getParameters() + { + return _parameters; + } + + public void setParameters(StatSet set) + { + _parameters = set; + } + + public Sex getSex() + { + return _sex; + } + + public int getChestId() + { + return _chestId; + } + + public int getRHandId() + { + return _rhandId; + } + + public int getLHandId() + { + return _lhandId; + } + + public int getWeaponEnchant() + { + return _weaponEnchant; + } + + public double getExp() + { + return _exp; + } + + public double getSP() + { + return _sp; + } + + public boolean isUnique() + { + return _unique; + } + + public boolean isAttackable() + { + return _attackable; + } + + public boolean isTargetable() + { + return _targetable; + } + + public boolean isTalkable() + { + return _talkable; + } + + public boolean isQuestMonster() + { + return _isQuestMonster; + } + + public boolean isUndying() + { + return _undying; + } + + public boolean isShowName() + { + return _showName; + } + + public boolean isRandomWalkEnabled() + { + return _randomWalk && (getParameters().getInt("MoveArounding", 1) != 0); + } + + public boolean isRandomAnimationEnabled() + { + return _randomAnimation; + } + + public boolean isFlying() + { + return _flying; + } + + public boolean isFakePlayer() + { + return _fakePlayer; + } + + public FakePlayerHolder getFakePlayerInfo() + { + return _fakePlayerInfo; + } + + public boolean canMove() + { + return _canMove; + } + + public boolean isNoSleepMode() + { + return _noSleepMode; + } + + public boolean isPassableDoor() + { + return _passableDoor; + } + + public boolean hasSummoner() + { + return _hasSummoner; + } + + public boolean canBeSown() + { + return _canBeSown; + } + + public int getCorpseTime() + { + return _corpseTime; + } + + public AIType getAIType() + { + return _aiType; + } + + public int getAggroRange() + { + return _aggroRange; + } + + public int getClanHelpRange() + { + return _clanHelpRange; + } + + public boolean isChaos() + { + return _isChaos; + } + + public boolean isAggressive() + { + return _isAggressive; + } + + public int getSoulShot() + { + return _soulShot; + } + + public int getSpiritShot() + { + return _spiritShot; + } + + public int getSoulShotChance() + { + return _soulShotChance; + } + + public int getSpiritShotChance() + { + return _spiritShotChance; + } + + public int getMinSkillChance() + { + return _minSkillChance; + } + + public int getMaxSkillChance() + { + return _maxSkillChance; + } + + public int getBaseAttackAngle() + { + return _baseAttackAngle; + } + + @Override + public Map getSkills() + { + return _skills; + } + + public void setSkills(Map skills) + { + _skills = skills != null ? Collections.unmodifiableMap(skills) : Collections.emptyMap(); + } + + public List getAISkills(AISkillScope aiSkillScope) + { + return _aiSkillLists.getOrDefault(aiSkillScope, Collections.emptyList()); + } + + public void setAISkillLists(Map> aiSkillLists) + { + _aiSkillLists = aiSkillLists != null ? Collections.unmodifiableMap(aiSkillLists) : Collections.emptyMap(); + } + + public Set getClans() + { + return _clans; + } + + /** + * @param clans A sorted array of clan ids + */ + public void setClans(Set clans) + { + _clans = clans != null ? Collections.unmodifiableSet(clans) : null; + } + + /** + * @param clanName clan name to check if it belongs to this NPC template clans. + * @param clanNames clan names to check if they belong to this NPC template clans. + * @return {@code true} if at least one of the clan names belong to this NPC template clans, {@code false} otherwise. + */ + public boolean isClan(String clanName, String... clanNames) + { + // Using local variable for the sake of reloading since it can be turned to null. + final Set clans = _clans; + if (clans == null) + { + return false; + } + + int clanId = NpcData.getInstance().getGenericClanId(); + if (clans.contains(clanId)) + { + return true; + } + + clanId = NpcData.getInstance().getClanId(clanName); + if (clans.contains(clanId)) + { + return true; + } + + for (String name : clanNames) + { + clanId = NpcData.getInstance().getClanId(name); + if (clans.contains(clanId)) + { + return true; + } + } + + return false; + } + + /** + * @param clans A set of clan names to check if they belong to this NPC template clans. + * @return {@code true} if at least one of the clan names belong to this NPC template clans, {@code false} otherwise. + */ + public boolean isClan(Set clans) + { + // Using local variable for the sake of reloading since it can be turned to null. + final Set clanSet = _clans; + if ((clanSet == null) || (clans == null)) + { + return false; + } + + final int clanId = NpcData.getInstance().getGenericClanId(); + if (clanSet.contains(clanId)) + { + return true; + } + + for (Integer id : clans) + { + if (clanSet.contains(id)) + { + return true; + } + } + + return false; + } + + public Set getIgnoreClanNpcIds() + { + return _ignoreClanNpcIds; + } + + public boolean hasIgnoreClanNpcIds() + { + return _ignoreClanNpcIds != null; + } + + /** + * @param ignoreClanNpcIds the ignore clan npc ids + */ + public void setIgnoreClanNpcIds(Set ignoreClanNpcIds) + { + _ignoreClanNpcIds = ignoreClanNpcIds != null ? Collections.unmodifiableSet(ignoreClanNpcIds) : null; + } + + public void removeDropGroups() + { + _dropGroups = null; + } + + public void removeDrops() + { + _dropListDeath = null; + _dropListSpoil = null; + } + + public void setDropGroups(List groups) + { + _dropGroups = groups; + } + + public void addDrop(DropHolder dropHolder) + { + if (_dropListDeath == null) + { + _dropListDeath = new ArrayList<>(1); + } + + _dropListDeath.add(dropHolder); + } + + public void addSpoil(DropHolder dropHolder) + { + if (_dropListSpoil == null) + { + _dropListSpoil = new ArrayList<>(1); + } + + _dropListSpoil.add(dropHolder); + } + + public List getDropGroups() + { + return _dropGroups; + } + + public List getDropList() + { + return _dropListDeath; + } + + public List getSpoilList() + { + return _dropListSpoil; + } + + public List calculateDrops(DropType dropType, Creature victim, Creature killer) + { + if (dropType == DropType.DROP) + { + // calculate group drops + List groupDrops = null; + if (_dropGroups != null) + { + groupDrops = calculateGroupDrops(victim, killer); + + if ((groupDrops != null) && victim.isMonster() && victim.asMonster().isSeeded()) + { + groupDrops.removeIf(i -> (i.getId() != 57 /* Adena */) && (i.getId() != 6361 /* Green Seal Stone */) && (i.getId() != 6362 /* Red Seal Stone */) && (i.getId() != 6360 /* Blue Seal Stone */)); + } + } + + // calculate ungrouped drops + List ungroupedDrops = null; + if (_dropListDeath != null) + { + ungroupedDrops = calculateUngroupedDrops(dropType, victim, killer); + + if ((ungroupedDrops != null) && victim.isMonster() && victim.asMonster().isSeeded()) + { + ungroupedDrops.removeIf(i -> (i.getId() != 57 /* Adena */) && (i.getId() != 6361 /* Green Seal Stone */) && (i.getId() != 6362 /* Red Seal Stone */) && (i.getId() != 6360 /* Blue Seal Stone */)); + } + } + + // return results + if ((groupDrops != null) && (ungroupedDrops != null)) + { + groupDrops.addAll(ungroupedDrops); + ungroupedDrops.clear(); + return groupDrops; + } + + if (groupDrops != null) + { + return groupDrops; + } + + if (ungroupedDrops != null) + { + return ungroupedDrops; + } + } + else if ((dropType == DropType.SPOIL) && (_dropListSpoil != null)) + { + return calculateUngroupedDrops(dropType, victim, killer); + } + + // no drops + return null; + } + + private List calculateGroupDrops(Creature victim, Creature killer) + { + // level difference calculations + final int levelDifference = victim.getLevel() - killer.getLevel(); + final double levelGapChanceToDropAdena = MathUtil.scaleToRange(levelDifference, -RatesConfig.DROP_ADENA_MAX_LEVEL_DIFFERENCE, -RatesConfig.DROP_ADENA_MIN_LEVEL_DIFFERENCE, RatesConfig.DROP_ADENA_MIN_LEVEL_GAP_CHANCE, 100d); + final double levelGapChanceToDrop = MathUtil.scaleToRange(levelDifference, -RatesConfig.DROP_ITEM_MAX_LEVEL_DIFFERENCE, -RatesConfig.DROP_ITEM_MIN_LEVEL_DIFFERENCE, RatesConfig.DROP_ITEM_MIN_LEVEL_GAP_CHANCE, 100d); + + List calculatedDrops = null; + int dropOccurrenceCounter = victim.isRaid() ? RatesConfig.DROP_MAX_OCCURRENCES_RAIDBOSS : RatesConfig.DROP_MAX_OCCURRENCES_NORMAL; + if (dropOccurrenceCounter > 0) + { + final Player player = killer.asPlayer(); + List randomDrops = null; + ItemHolder cachedItem = null; + double totalChance; // total group chance is 100 + for (DropGroupHolder group : _dropGroups) + { + totalChance = 0; + GROUP_DROP: for (DropHolder dropItem : group.getDropList()) + { + final int itemId = dropItem.getItemId(); + final ItemTemplate item = ItemData.getInstance().getTemplate(itemId); + final boolean champion = victim.isChampion(); + + // chance + double rateChance = 1; + if (RatesConfig.RATE_DROP_CHANCE_BY_ID.get(itemId) != null) + { + rateChance *= RatesConfig.RATE_DROP_CHANCE_BY_ID.get(itemId); + if (champion && (itemId == Inventory.ADENA_ID)) + { + rateChance *= ChampionMonstersConfig.CHAMPION_ADENAS_REWARDS_CHANCE; + } + + if ((itemId == Inventory.ADENA_ID) && (rateChance > 100)) + { + rateChance = 100; + } + } + else if (item.hasExImmediateEffect()) + { + rateChance *= RatesConfig.RATE_HERB_DROP_CHANCE_MULTIPLIER; + } + else if (victim.isRaid()) + { + rateChance *= RatesConfig.RATE_RAID_DROP_CHANCE_MULTIPLIER; + } + else + { + rateChance *= RatesConfig.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? ChampionMonstersConfig.CHAMPION_REWARDS_CHANCE : 1); + } + + // premium chance + if (player != null) + { + if (PremiumSystemConfig.PREMIUM_SYSTEM_ENABLED && player.hasPremiumStatus()) + { + if (PremiumSystemConfig.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null) + { + rateChance *= PremiumSystemConfig.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId); + } + else if (item.hasExImmediateEffect()) + { + // TODO: Premium herb chance? :) + } + else if (victim.isRaid()) + { + // TODO: Premium raid chance? :) + } + else + { + rateChance *= PremiumSystemConfig.PREMIUM_RATE_DROP_CHANCE; + } + } + + // bonus drop rate effect + rateChance *= player.getStat().getBonusDropRateMultiplier(); + } + + // only use total chance on x1, custom rates break this logic because total chance is more than 100% + if (rateChance == 1) + { + totalChance += dropItem.getChance(); + } + else + { + totalChance = dropItem.getChance(); + } + + final double groupItemChance = totalChance * (group.getChance() / 100) * rateChance; + + // check if maximum drop occurrences have been reached + // items that have 100% drop chance without server rate multipliers drop normally + if ((dropOccurrenceCounter == 0) && (groupItemChance < 100) && (randomDrops != null) && (calculatedDrops != null)) + { + if ((rateChance == 1) && !randomDrops.isEmpty()) // custom rates break this logic because total chance is more than 100% + { + // remove highest chance item (temporarily if no other item replaces it) + cachedItem = randomDrops.remove(0); + calculatedDrops.remove(cachedItem); + } + + dropOccurrenceCounter = 1; + } + + // check level gap that may prevent to drop item + if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop)) + { + continue GROUP_DROP; + } + + // calculate chances + final ItemHolder drop = calculateGroupDrop(group, dropItem, victim, killer, groupItemChance); + if (drop == null) + { + continue GROUP_DROP; + } + + // create lists + if (randomDrops == null) + { + randomDrops = new ArrayList<>(dropOccurrenceCounter); + } + + if (calculatedDrops == null) + { + calculatedDrops = new ArrayList<>(dropOccurrenceCounter); + } + + // finally + final Float itemChance = RatesConfig.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); + if (itemChance != null) + { + if ((groupItemChance * itemChance) < 100) + { + dropOccurrenceCounter--; + if (rateChance == 1) // custom rates break this logic because total chance is more than 100% + { + randomDrops.add(drop); + } + } + } + else if (groupItemChance < 100) + { + dropOccurrenceCounter--; + if (rateChance == 1) // custom rates break this logic because total chance is more than 100% + { + randomDrops.add(drop); + } + } + + calculatedDrops.add(drop); + + // no more drops from this group, only use on x1, custom rates break this logic because total chance is more than 100% + if (rateChance == 1) + { + break GROUP_DROP; + } + } + } + + // add temporarily removed item when not replaced + if ((dropOccurrenceCounter > 0) && (cachedItem != null) && (calculatedDrops != null)) + { + calculatedDrops.add(cachedItem); + } + + // clear random drops + if (randomDrops != null) + { + randomDrops.clear(); + randomDrops = null; + } + + // champion extra drop + if (victim.isChampion() && (Rnd.get(100) < (victim.getLevel() < killer.getLevel() ? ChampionMonstersConfig.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE : ChampionMonstersConfig.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE))) + { + // create list + if (calculatedDrops == null) + { + calculatedDrops = new ArrayList<>(); + } + + if (!calculatedDrops.containsAll(ChampionMonstersConfig.CHAMPION_REWARD_ITEMS)) + { + calculatedDrops.addAll(ChampionMonstersConfig.CHAMPION_REWARD_ITEMS); + } + } + } + + return calculatedDrops; + } + + private List calculateUngroupedDrops(DropType dropType, Creature victim, Creature killer) + { + final List dropList = dropType == DropType.SPOIL ? _dropListSpoil : _dropListDeath; + + // level difference calculations + final int levelDifference = victim.getLevel() - killer.getLevel(); + final double levelGapChanceToDropAdena = MathUtil.scaleToRange(levelDifference, -RatesConfig.DROP_ADENA_MAX_LEVEL_DIFFERENCE, -RatesConfig.DROP_ADENA_MIN_LEVEL_DIFFERENCE, RatesConfig.DROP_ADENA_MIN_LEVEL_GAP_CHANCE, 100d); + final double levelGapChanceToDrop = MathUtil.scaleToRange(levelDifference, -RatesConfig.DROP_ITEM_MAX_LEVEL_DIFFERENCE, -RatesConfig.DROP_ITEM_MIN_LEVEL_DIFFERENCE, RatesConfig.DROP_ITEM_MIN_LEVEL_GAP_CHANCE, 100d); + + int dropOccurrenceCounter = victim.isRaid() ? RatesConfig.DROP_MAX_OCCURRENCES_RAIDBOSS : RatesConfig.DROP_MAX_OCCURRENCES_NORMAL; + List calculatedDrops = null; + List randomDrops = null; + ItemHolder cachedItem = null; + if (dropOccurrenceCounter > 0) + { + for (DropHolder dropItem : dropList) + { + // check if maximum drop occurrences have been reached + // items that have 100% drop chance without server rate multipliers drop normally + if ((dropOccurrenceCounter == 0) && (dropItem.getChance() < 100) && (randomDrops != null) && (calculatedDrops != null)) + { + // remove highest chance item (temporarily if no other item replaces it) + cachedItem = randomDrops.remove(0); + calculatedDrops.remove(cachedItem); + dropOccurrenceCounter = 1; + } + + // check level gap that may prevent to drop item + if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop)) + { + continue; + } + + // calculate chances + final ItemHolder drop = calculateUngroupedDrop(dropItem, victim, killer); + if (drop == null) + { + continue; + } + + // create lists + if (randomDrops == null) + { + randomDrops = new ArrayList<>(dropOccurrenceCounter); + } + + if (calculatedDrops == null) + { + calculatedDrops = new ArrayList<>(dropOccurrenceCounter); + } + + // finally + final Float itemChance = RatesConfig.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); + if (itemChance != null) + { + if ((dropItem.getChance() * itemChance) < 100) + { + dropOccurrenceCounter--; + randomDrops.add(drop); + } + } + else if (dropItem.getChance() < 100) + { + dropOccurrenceCounter--; + randomDrops.add(drop); + } + + calculatedDrops.add(drop); + } + } + + // add temporarily removed item when not replaced + if ((dropOccurrenceCounter > 0) && (cachedItem != null) && (calculatedDrops != null)) + { + calculatedDrops.add(cachedItem); + } + + // clear random drops + if (randomDrops != null) + { + randomDrops.clear(); + randomDrops = null; + } + + // champion extra drop + if (victim.isChampion() && (Rnd.get(100) < (victim.getLevel() < killer.getLevel() ? ChampionMonstersConfig.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE : ChampionMonstersConfig.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE))) + { + // create list + if (calculatedDrops == null) + { + calculatedDrops = new ArrayList<>(); + } + + if (!calculatedDrops.containsAll(ChampionMonstersConfig.CHAMPION_REWARD_ITEMS)) + { + calculatedDrops.addAll(ChampionMonstersConfig.CHAMPION_REWARD_ITEMS); + } + } + + return calculatedDrops; + } + + /** + * @param group + * @param dropItem + * @param victim + * @param killer + * @param chance + * @return ItemHolder + */ + private ItemHolder calculateGroupDrop(DropGroupHolder group, DropHolder dropItem, Creature victim, Creature killer, double chance) + { + final int itemId = dropItem.getItemId(); + final ItemTemplate item = ItemData.getInstance().getTemplate(itemId); + final boolean champion = victim.isChampion(); + + // calculate if item will drop + if ((Rnd.nextDouble() * 100) < chance) + { + // amount is calculated after chance returned success + double rateAmount = 1; + if (RatesConfig.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) + { + rateAmount *= RatesConfig.RATE_DROP_AMOUNT_BY_ID.get(itemId); + if (champion && (itemId == Inventory.ADENA_ID)) + { + rateAmount *= ChampionMonstersConfig.CHAMPION_ADENAS_REWARDS_AMOUNT; + } + } + else if (item.hasExImmediateEffect()) + { + rateAmount *= RatesConfig.RATE_HERB_DROP_AMOUNT_MULTIPLIER; + } + else if (victim.isRaid()) + { + rateAmount *= RatesConfig.RATE_RAID_DROP_AMOUNT_MULTIPLIER; + } + else + { + rateAmount *= RatesConfig.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? ChampionMonstersConfig.CHAMPION_REWARDS_AMOUNT : 1); + } + + // premium amount + final Player player = killer.asPlayer(); + if (player != null) + { + if (PremiumSystemConfig.PREMIUM_SYSTEM_ENABLED && player.hasPremiumStatus()) + { + if (PremiumSystemConfig.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) + { + rateAmount *= PremiumSystemConfig.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId); + } + else if (item.hasExImmediateEffect()) + { + // TODO: Premium herb amount? :) + } + else if (victim.isRaid()) + { + // TODO: Premium raid amount? :) + } + else + { + rateAmount *= PremiumSystemConfig.PREMIUM_RATE_DROP_AMOUNT; + } + } + + // bonus drop amount effect + final PlayerStat stat = player.getStat(); + rateAmount *= stat.getBonusDropAmountMultiplier(); + if (itemId == Inventory.ADENA_ID) + { + rateAmount *= stat.getBonusDropAdenaMultiplier(); + } + } + + // finally + return new ItemHolder(itemId, (int) (Rnd.get(dropItem.getMin(), dropItem.getMax()) * rateAmount)); + } + + return null; + } + + /** + * @param dropItem + * @param victim + * @param killer + * @return ItemHolder + */ + private ItemHolder calculateUngroupedDrop(DropHolder dropItem, Creature victim, Creature killer) + { + switch (dropItem.getDropType()) + { + case DROP: + { + final int itemId = dropItem.getItemId(); + final ItemTemplate item = ItemData.getInstance().getTemplate(itemId); + final boolean champion = victim.isChampion(); + + // chance + double rateChance = 1; + if (RatesConfig.RATE_DROP_CHANCE_BY_ID.get(itemId) != null) + { + rateChance *= RatesConfig.RATE_DROP_CHANCE_BY_ID.get(itemId); + if (champion && (itemId == Inventory.ADENA_ID)) + { + rateChance *= ChampionMonstersConfig.CHAMPION_ADENAS_REWARDS_CHANCE; + } + + if ((itemId == Inventory.ADENA_ID) && (rateChance > 100)) + { + rateChance = 100; + } + } + else if (item.hasExImmediateEffect()) + { + rateChance *= RatesConfig.RATE_HERB_DROP_CHANCE_MULTIPLIER; + } + else if (victim.isRaid()) + { + rateChance *= RatesConfig.RATE_RAID_DROP_CHANCE_MULTIPLIER; + } + else + { + rateChance *= RatesConfig.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? ChampionMonstersConfig.CHAMPION_REWARDS_CHANCE : 1); + } + + // premium chance + final Player player = killer.asPlayer(); + if (player != null) + { + if (PremiumSystemConfig.PREMIUM_SYSTEM_ENABLED && player.hasPremiumStatus()) + { + if (PremiumSystemConfig.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null) + { + rateChance *= PremiumSystemConfig.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId); + } + else if (item.hasExImmediateEffect()) + { + // TODO: Premium herb chance? :) + } + else if (victim.isRaid()) + { + // TODO: Premium raid chance? :) + } + else + { + rateChance *= PremiumSystemConfig.PREMIUM_RATE_DROP_CHANCE; + } + } + + // bonus drop rate effect + rateChance *= player.getStat().getBonusDropRateMultiplier(); + } + + // calculate if item will drop + if ((Rnd.nextDouble() * 100) < (dropItem.getChance() * rateChance)) + { + // amount is calculated after chance returned success + double rateAmount = 1; + if (RatesConfig.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) + { + rateAmount *= RatesConfig.RATE_DROP_AMOUNT_BY_ID.get(itemId); + if (champion && (itemId == Inventory.ADENA_ID)) + { + rateAmount *= ChampionMonstersConfig.CHAMPION_ADENAS_REWARDS_AMOUNT; + } + } + else if (item.hasExImmediateEffect()) + { + rateAmount *= RatesConfig.RATE_HERB_DROP_AMOUNT_MULTIPLIER; + } + else if (victim.isRaid()) + { + rateAmount *= RatesConfig.RATE_RAID_DROP_AMOUNT_MULTIPLIER; + } + else + { + rateAmount *= RatesConfig.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? ChampionMonstersConfig.CHAMPION_REWARDS_AMOUNT : 1); + } + + // premium amount + if (player != null) + { + if (PremiumSystemConfig.PREMIUM_SYSTEM_ENABLED && player.hasPremiumStatus()) + { + if (PremiumSystemConfig.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) + { + rateAmount *= PremiumSystemConfig.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId); + } + else if (item.hasExImmediateEffect()) + { + // TODO: Premium herb amount? :) + } + else if (victim.isRaid()) + { + // TODO: Premium raid amount? :) + } + else + { + rateAmount *= PremiumSystemConfig.PREMIUM_RATE_DROP_AMOUNT; + } + } + + // bonus drop amount effect + final PlayerStat stat = player.getStat(); + rateAmount *= stat.getBonusDropAmountMultiplier(); + if (itemId == Inventory.ADENA_ID) + { + rateAmount *= stat.getBonusDropAdenaMultiplier(); + } + } + + // finally + return new ItemHolder(itemId, (int) (Rnd.get(dropItem.getMin(), dropItem.getMax()) * rateAmount)); + } + break; + } + case SPOIL: + { + // chance + double rateChance = RatesConfig.RATE_SPOIL_DROP_CHANCE_MULTIPLIER; + + // premium chance + final Player player = killer.asPlayer(); + if (player != null) + { + if (PremiumSystemConfig.PREMIUM_SYSTEM_ENABLED && player.hasPremiumStatus()) + { + rateChance *= PremiumSystemConfig.PREMIUM_RATE_SPOIL_CHANCE; + } + + // bonus spoil rate effect + rateChance *= player.getStat().getBonusSpoilRateMultiplier(); + } + + // calculate if item will be rewarded + if ((Rnd.nextDouble() * 100) < (dropItem.getChance() * rateChance)) + { + // amount is calculated after chance returned success + double rateAmount = RatesConfig.RATE_SPOIL_DROP_AMOUNT_MULTIPLIER; + + // premium amount + if (PremiumSystemConfig.PREMIUM_SYSTEM_ENABLED && (player != null) && player.hasPremiumStatus()) + { + rateAmount *= PremiumSystemConfig.PREMIUM_RATE_SPOIL_AMOUNT; + } + + // finally + return new ItemHolder(dropItem.getItemId(), (int) (Rnd.get(dropItem.getMin(), dropItem.getMax()) * rateAmount)); + } + break; + } + } + + return null; + } + + public double getCollisionRadiusGrown() + { + return _collisionRadiusGrown; + } + + public double getCollisionHeightGrown() + { + return _collisionHeightGrown; + } + + public static boolean isAssignableTo(Class subValue, Class clazz) + { + // If clazz represents an interface. + if (clazz.isInterface()) + { + // Check if obj implements the clazz interface. + for (Class interface1 : subValue.getInterfaces()) + { + if (clazz.getName().equals(interface1.getName())) + { + return true; + } + } + } + else + { + Class sub = subValue; + do + { + if (sub.getName().equals(clazz.getName())) + { + return true; + } + + sub = sub.getSuperclass(); + } + while (sub != null); + } + + return false; + } + + /** + * Checks if obj can be assigned to the Class represented by clazz.
        + * This is true if, and only if, obj is the same class represented by clazz, or a subclass of it or obj implements the interface represented by clazz. + * @param obj + * @param clazz + * @return {@code true} if the object can be assigned to the class, {@code false} otherwise + */ + public static boolean isAssignableTo(Object obj, Class clazz) + { + return isAssignableTo(obj.getClass(), clazz); + } + + public boolean canTeach(PlayerClass playerClass) + { + // If the player is on a third class, fetch the class teacher information for its parent class. + if (playerClass.level() == 3) + { + return _teachInfo.contains(playerClass.getParent()); + } + + return _teachInfo.contains(playerClass); + } + + public List getTeachInfo() + { + return _teachInfo; + } + + public void addTeachInfo(List teachInfo) + { + _teachInfo.addAll(teachInfo); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/templates/PlayerTemplate.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/templates/PlayerTemplate.java new file mode 100644 index 0000000..f97d404 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/actor/templates/PlayerTemplate.java @@ -0,0 +1,229 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.actor.templates; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.l2jmobius.commons.util.Rnd; +import org.l2jmobius.gameserver.data.xml.ExperienceData; +import org.l2jmobius.gameserver.entity.Location; +import org.l2jmobius.gameserver.entity.actor.enums.player.PlayerClass; +import org.l2jmobius.gameserver.entity.itemcontainer.Inventory; +import org.l2jmobius.gameserver.util.StatSet; + +/** + * @author mkizub, Zoey76 + */ +public class PlayerTemplate extends CreatureTemplate +{ + private final PlayerClass _playerClass; + + private final float[] _baseHp; + private final float[] _baseMp; + private final float[] _baseCp; + + private final double[] _baseHpReg; + private final double[] _baseMpReg; + private final double[] _baseCpReg; + + private final float _fCollisionHeightFemale; + private final float _fCollisionRadiusFemale; + + private final int _baseSafeFallHeight; + + private final List _creationPoints; + private final Map _baseSlotDef; + + public PlayerTemplate(StatSet set, List creationPoints) + { + super(set); + _playerClass = PlayerClass.getPlayerClass(set.getInt("classId")); + setRace(_playerClass.getRace()); + _baseHp = new float[ExperienceData.getInstance().getMaxLevel()]; + _baseMp = new float[ExperienceData.getInstance().getMaxLevel()]; + _baseCp = new float[ExperienceData.getInstance().getMaxLevel()]; + _baseHpReg = new double[ExperienceData.getInstance().getMaxLevel()]; + _baseMpReg = new double[ExperienceData.getInstance().getMaxLevel()]; + _baseCpReg = new double[ExperienceData.getInstance().getMaxLevel()]; + _baseSlotDef = new HashMap<>(12); + _baseSlotDef.put(Inventory.PAPERDOLL_CHEST, set.getInt("basePDefchest", 0)); + _baseSlotDef.put(Inventory.PAPERDOLL_LEGS, set.getInt("basePDeflegs", 0)); + _baseSlotDef.put(Inventory.PAPERDOLL_HEAD, set.getInt("basePDefhead", 0)); + _baseSlotDef.put(Inventory.PAPERDOLL_FEET, set.getInt("basePDeffeet", 0)); + _baseSlotDef.put(Inventory.PAPERDOLL_GLOVES, set.getInt("basePDefgloves", 0)); + _baseSlotDef.put(Inventory.PAPERDOLL_UNDER, set.getInt("basePDefunderwear", 0)); + _baseSlotDef.put(Inventory.PAPERDOLL_CLOAK, set.getInt("basePDefcloak", 0)); + _baseSlotDef.put(Inventory.PAPERDOLL_REAR, set.getInt("baseMDefrear", 0)); + _baseSlotDef.put(Inventory.PAPERDOLL_LEAR, set.getInt("baseMDeflear", 0)); + _baseSlotDef.put(Inventory.PAPERDOLL_RFINGER, set.getInt("baseMDefrfinger", 0)); + _baseSlotDef.put(Inventory.PAPERDOLL_LFINGER, set.getInt("baseMDefrfinger", 0)); + _baseSlotDef.put(Inventory.PAPERDOLL_NECK, set.getInt("baseMDefneck", 0)); + + _fCollisionRadiusFemale = set.getFloat("collisionFemaleradius"); + _fCollisionHeightFemale = set.getFloat("collisionFemaleheight"); + _baseSafeFallHeight = set.getInt("baseSafeFall", 333); + _creationPoints = creationPoints; + } + + /** + * @return the template PlayerClass. + */ + public PlayerClass getPlayerClass() + { + return _playerClass; + } + + /** + * @return random Location of created character spawn. + */ + public Location getCreationPoint() + { + return _creationPoints.get(Rnd.get(_creationPoints.size())); + } + + /** + * Sets the value of level upgain parameter. + * @param paramName name of parameter + * @param level corresponding character level + * @param value value of parameter + */ + public void setUpgainValue(String paramName, int level, double value) + { + switch (paramName) + { + case "hp": + { + _baseHp[level] = (float) value; + break; + } + case "mp": + { + _baseMp[level] = (float) value; + break; + } + case "cp": + { + _baseCp[level] = (float) value; + break; + } + case "hpRegen": + { + _baseHpReg[level] = value; + break; + } + case "mpRegen": + { + _baseMpReg[level] = value; + break; + } + case "cpRegen": + { + _baseCpReg[level] = value; + break; + } + } + } + + /** + * @param level character level to return value + * @return the baseHpMax for given character level + */ + public float getBaseHpMax(int level) + { + return _baseHp[level]; + } + + /** + * @param level character level to return value + * @return the baseMpMax for given character level + */ + public float getBaseMpMax(int level) + { + return _baseMp[level]; + } + + /** + * @param level character level to return value + * @return the baseCpMax for given character level + */ + public float getBaseCpMax(int level) + { + return _baseCp[level]; + } + + /** + * @param level character level to return value + * @return the base HP Regeneration for given character level + */ + public double getBaseHpRegen(int level) + { + return _baseHpReg[level]; + } + + /** + * @param level character level to return value + * @return the base MP Regeneration for given character level + */ + public double getBaseMpRegen(int level) + { + return _baseMpReg[level]; + } + + /** + * @param level character level to return value + * @return the base HP Regeneration for given character level + */ + public double getBaseCpRegen(int level) + { + return _baseCpReg[level]; + } + + /** + * @param slotId id of inventory slot to return value + * @return defense value of character for EMPTY given slot + */ + public int getBaseDefBySlot(int slotId) + { + return _baseSlotDef.getOrDefault(slotId, 0); + } + + /** + * @return the template collision height for female characters. + */ + public float getFCollisionHeightFemale() + { + return _fCollisionHeightFemale; + } + + /** + * @return the template collision radius for female characters. + */ + public float getFCollisionRadiusFemale() + { + return _fCollisionRadiusFemale; + } + + /** + * @return the safe fall height. + */ + public int getSafeFallHeight() + { + return _baseSafeFallHeight; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/clan/Clan.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/clan/Clan.java new file mode 100644 index 0000000..a5b2169 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/clan/Clan.java @@ -0,0 +1,2953 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.clan; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.commons.database.DatabaseFactory; +import org.l2jmobius.commons.util.StringUtil; +import org.l2jmobius.gameserver.communitybbs.BB.Forum; +import org.l2jmobius.gameserver.communitybbs.Manager.ForumsBBSManager; +import org.l2jmobius.gameserver.config.FeatureConfig; +import org.l2jmobius.gameserver.config.GeneralConfig; +import org.l2jmobius.gameserver.config.PlayerConfig; +import org.l2jmobius.gameserver.data.sql.CharInfoTable; +import org.l2jmobius.gameserver.data.sql.ClanTable; +import org.l2jmobius.gameserver.data.sql.CrestTable; +import org.l2jmobius.gameserver.data.xml.SkillData; +import org.l2jmobius.gameserver.data.xml.SkillTreeData; +import org.l2jmobius.gameserver.entity.World; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.holders.player.BlockList; +import org.l2jmobius.gameserver.entity.item.enums.ItemProcessType; +import org.l2jmobius.gameserver.entity.itemcontainer.ClanWarehouse; +import org.l2jmobius.gameserver.entity.itemcontainer.ItemContainer; +import org.l2jmobius.gameserver.entity.zone.ZoneId; +import org.l2jmobius.gameserver.managers.CastleManager; +import org.l2jmobius.gameserver.managers.SiegeManager; +import org.l2jmobius.gameserver.mechanics.events.EventDispatcher; +import org.l2jmobius.gameserver.mechanics.events.EventType; +import org.l2jmobius.gameserver.mechanics.events.holders.actor.player.clan.OnPlayerClanJoin; +import org.l2jmobius.gameserver.mechanics.events.holders.actor.player.clan.OnPlayerClanLeaderChange; +import org.l2jmobius.gameserver.mechanics.events.holders.actor.player.clan.OnPlayerClanLeft; +import org.l2jmobius.gameserver.mechanics.events.holders.actor.player.clan.OnPlayerClanLvlUp; +import org.l2jmobius.gameserver.mechanics.siege.Castle; +import org.l2jmobius.gameserver.mechanics.skill.Skill; +import org.l2jmobius.gameserver.mechanics.skill.holders.SkillLearn; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.serverpackets.CreatureSay; +import org.l2jmobius.gameserver.network.serverpackets.PledgeReceiveSubPledgeCreated; +import org.l2jmobius.gameserver.network.serverpackets.PledgeShowInfoUpdate; +import org.l2jmobius.gameserver.network.serverpackets.PledgeShowMemberListAll; +import org.l2jmobius.gameserver.network.serverpackets.PledgeShowMemberListDeleteAll; +import org.l2jmobius.gameserver.network.serverpackets.PledgeShowMemberListUpdate; +import org.l2jmobius.gameserver.network.serverpackets.PledgeSkillList; +import org.l2jmobius.gameserver.network.serverpackets.PledgeSkillListAdd; +import org.l2jmobius.gameserver.network.serverpackets.ServerPacket; +import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; + +public class Clan +{ + private static final Logger LOGGER = Logger.getLogger(Clan.class.getName()); + + // SQL queries + private static final String INSERT_CLAN_DATA = "INSERT INTO clan_data (clan_id,clan_name,clan_level,hasCastle,blood_alliance_count,blood_oath_count,ally_id,ally_name,leader_id,crest_id,crest_large_id,ally_crest_id,new_leader_id) values (?,?,?,?,?,?,?,?,?,?,?,?,?)"; + private static final String SELECT_CLAN_DATA = "SELECT * FROM clan_data where clan_id=?"; + + // Ally Penalty Types + /** Clan leaved ally */ + public static final int PENALTY_TYPE_CLAN_LEAVED = 1; + /** Clan was dismissed from ally */ + public static final int PENALTY_TYPE_CLAN_DISMISSED = 2; + /** Leader clan dismiss clan from ally */ + public static final int PENALTY_TYPE_DISMISS_CLAN = 3; + /** Leader clan dissolve ally */ + public static final int PENALTY_TYPE_DISSOLVE_ALLY = 4; + + // Sub-unit types + /** Clan subunit type of Academy */ + public static final int SUBUNIT_ACADEMY = -1; + /** Clan subunit type of Royal Guard A */ + public static final int SUBUNIT_ROYAL1 = 100; + /** Clan subunit type of Royal Guard B */ + public static final int SUBUNIT_ROYAL2 = 200; + /** Clan subunit type of Order of Knights A-1 */ + public static final int SUBUNIT_KNIGHT1 = 1001; + /** Clan subunit type of Order of Knights A-2 */ + public static final int SUBUNIT_KNIGHT2 = 1002; + /** Clan subunit type of Order of Knights B-1 */ + public static final int SUBUNIT_KNIGHT3 = 2001; + /** Clan subunit type of Order of Knights B-2 */ + public static final int SUBUNIT_KNIGHT4 = 2002; + + private String _name; + private int _clanId; + private ClanMember _leader; + private final Map _members = new ConcurrentHashMap<>(); + + private String _allyName; + private int _allyId = 0; + private int _level; + private int _castleId; + private int _hideoutId; + private int _hiredGuards; + private int _crestId; + private int _crestLargeId; + private int _allyCrestId; + private int _auctionBiddedAt = 0; + private long _allyPenaltyExpiryTime; + private int _allyPenaltyType; + private long _charPenaltyExpiryTime; + private long _dissolvingExpiryTime; + private int _bloodAllianceCount; + private int _bloodOathCount; + + private final ItemContainer _warehouse = new ClanWarehouse(this); + private final Set _atWarWith = ConcurrentHashMap.newKeySet(); + private final Set _atWarAttackers = ConcurrentHashMap.newKeySet(); + + private Forum _forum; + + /** Map(Integer, Skill) containing all skills of the Clan */ + private final Map _skills = new ConcurrentHashMap<>(); + private final Map _privs = new ConcurrentHashMap<>(); + private final Map _subPledges = new ConcurrentHashMap<>(); + private final Map _subPledgeSkills = new ConcurrentHashMap<>(); + + private int _reputationScore = 0; + private int _rank = 0; + + private String _notice; + private boolean _noticeEnabled = false; + private static final int MAX_NOTICE_LENGTH = 8192; + private int _newLeaderId; + + private final AtomicInteger _siegeKills = new AtomicInteger(); + private final AtomicInteger _siegeDeaths = new AtomicInteger(); + + /** + * Called if a clan is referenced only by id. In this case all other data needs to be fetched from db + * @param clanId A valid clan Id to create and restore + */ + public Clan(int clanId) + { + _clanId = clanId; + initializePrivs(); + restore(); + _warehouse.restore(); + } + + /** + * Called only if a new clan is created + * @param clanId A valid clan Id to create + * @param clanName A valid clan name + */ + public Clan(int clanId, String clanName) + { + _clanId = clanId; + _name = clanName; + initializePrivs(); + } + + /** + * @return Returns the clanId. + */ + public int getId() + { + return _clanId; + } + + /** + * @param clanId The clanId to set. + */ + public void setClanId(int clanId) + { + _clanId = clanId; + } + + /** + * @return Returns the leaderId. + */ + public int getLeaderId() + { + return _leader != null ? _leader.getObjectId() : 0; + } + + /** + * @return PledgeMember of clan leader. + */ + public ClanMember getLeader() + { + return _leader; + } + + /** + * @param leader the leader to set. + */ + public void setLeader(ClanMember leader) + { + _leader = leader; + _members.put(leader.getObjectId(), leader); + } + + public void setNewLeader(ClanMember member) + { + final Player newLeader = member.getPlayer(); + final ClanMember exMember = _leader; + final Player exLeader = exMember.getPlayer(); + + // Notify to scripts. + if (EventDispatcher.getInstance().hasListener(EventType.ON_PLAYER_CLAN_LEADER_CHANGE)) + { + EventDispatcher.getInstance().notifyEventAsync(new OnPlayerClanLeaderChange(exMember, member, this)); + } + + if (exLeader != null) + { + if (exLeader.isFlying()) + { + exLeader.dismount(); + } + + if (getLevel() >= SiegeManager.getInstance().getSiegeClanMinLevel()) + { + SiegeManager.getInstance().removeSiegeSkills(exLeader); + } + + exLeader.getClanPrivileges().disableAll(); + exLeader.broadcastUserInfo(); + } + else + { + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement("UPDATE characters SET clan_privs = ? WHERE charId = ?")) + { + ps.setInt(1, 0); + ps.setInt(2, getLeaderId()); + ps.execute(); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Couldn't update clan privs for old clan leader", e); + } + } + + setLeader(member); + if (_newLeaderId != 0) + { + setNewLeaderId(0, true); + } + + updateClanInDB(); + + if (exLeader != null) + { + exLeader.setPledgeClass(ClanMember.calculatePledgeClass(exLeader)); + exLeader.broadcastUserInfo(); + exLeader.checkItemRestriction(); + } + + if (newLeader != null) + { + newLeader.setPledgeClass(ClanMember.calculatePledgeClass(newLeader)); + newLeader.getClanPrivileges().enableAll(); + + if (getLevel() >= SiegeManager.getInstance().getSiegeClanMinLevel()) + { + SiegeManager.getInstance().addSiegeSkills(newLeader); + } + + newLeader.broadcastUserInfo(); + } + else + { + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement("UPDATE characters SET clan_privs = ? WHERE charId = ?")) + { + ps.setInt(1, ClanPrivileges.getCompleteMask()); + ps.setInt(2, getLeaderId()); + ps.execute(); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Couldn't update clan privs for new clan leader", e); + } + } + + broadcastClanStatus(); + broadcastToOnlineMembers(new SystemMessage(SystemMessageId.CLAN_LORD_PRIVILEGES_HAVE_BEEN_TRANSFERRED_TO_S1).addString(member.getName())); + + LOGGER.log(Level.INFO, "Leader of Clan: " + getName() + " changed to: " + member.getName() + " ex leader: " + exMember.getName()); + } + + /** + * @return the clan leader's name. + */ + public String getLeaderName() + { + if (_leader == null) + { + LOGGER.warning(Clan.class.getName() + ": Clan " + getName() + " without clan leader!"); + return ""; + } + + return _leader.getName(); + } + + /** + * @return the clan name. + */ + public String getName() + { + return _name; + } + + /** + * @param name The name to set. + */ + public void setName(String name) + { + _name = name; + } + + /** + * Adds a clan member to the clan. + * @param member the clan member. + */ + private void addClanMember(ClanMember member) + { + _members.put(member.getObjectId(), member); + } + + /** + * Adds a clan member to the clan.
        + * Using a different constructor, to make it easier to read. + * @param player the clan member + */ + public void addClanMember(Player player) + { + final ClanMember member = new ClanMember(this, player); + member.setPlayer(player); + addClanMember(member); + + player.setClan(this); + player.setPledgeClass(ClanMember.calculatePledgeClass(player)); + player.sendPacket(new PledgeShowMemberListUpdate(player)); + player.sendPacket(new PledgeSkillList(this)); + addSkillEffects(player); + + // Notify to scripts. + if (EventDispatcher.getInstance().hasListener(EventType.ON_PLAYER_CLAN_JOIN)) + { + EventDispatcher.getInstance().notifyEventAsync(new OnPlayerClanJoin(member, this)); + } + } + + /** + * Updates player status in clan. + * @param player the player to be updated. + */ + public void updateClanMember(Player player) + { + final ClanMember member = new ClanMember(player.getClan(), player); + if (player.isClanLeader()) + { + setLeader(member); + } + + addClanMember(member); + } + + /** + * @param name the name of the required clan member. + * @return the clan member for a given name. + */ + public ClanMember getClanMember(String name) + { + for (ClanMember temp : _members.values()) + { + if (temp.getName().equals(name)) + { + return temp; + } + } + + return null; + } + + /** + * @param objectId the required clan member object Id. + * @return the clan member for a given {@code objectId}. + */ + public ClanMember getClanMember(int objectId) + { + return _members.get(objectId); + } + + /** + * @param objectId the object Id of the member that will be removed. + * @param clanJoinExpiryTime time penalty to join a clan. + */ + public void removeClanMember(int objectId, long clanJoinExpiryTime) + { + final ClanMember exMember = _members.remove(objectId); + if (exMember == null) + { + LOGGER.warning("Member Object ID: " + objectId + " not found in clan while trying to remove"); + return; + } + + final int leadssubpledge = getLeaderSubPledge(objectId); + if (leadssubpledge != 0) + { + // Sub-unit leader withdraws, position becomes vacant and leader + // should appoint new via NPC. + getSubPledge(leadssubpledge).setLeaderId(0); + updateSubPledgeInDB(leadssubpledge); + } + + if (exMember.getApprentice() != 0) + { + final ClanMember apprentice = getClanMember(exMember.getApprentice()); + if (apprentice != null) + { + if (apprentice.getPlayer() != null) + { + apprentice.getPlayer().setSponsor(0); + } + else + { + apprentice.setApprenticeAndSponsor(0, 0); + } + + apprentice.saveApprenticeAndSponsor(0, 0); + } + } + + if (exMember.getSponsor() != 0) + { + final ClanMember sponsor = getClanMember(exMember.getSponsor()); + if (sponsor != null) + { + if (sponsor.getPlayer() != null) + { + sponsor.getPlayer().setApprentice(0); + } + else + { + sponsor.setApprenticeAndSponsor(0, 0); + } + + sponsor.saveApprenticeAndSponsor(0, 0); + } + } + + exMember.saveApprenticeAndSponsor(0, 0); + if (PlayerConfig.REMOVE_CASTLE_CIRCLETS) + { + CastleManager.getInstance().removeCirclet(exMember, getCastleId()); + } + + final Player player = exMember.getPlayer(); + if (player != null) + { + if (!player.isNoble()) + { + player.setTitle(""); + } + + player.setApprentice(0); + player.setSponsor(0); + + if (player.isClanLeader()) + { + SiegeManager.getInstance().removeSiegeSkills(player); + player.setClanCreateExpiryTime(System.currentTimeMillis() + TimeUnit.DAYS.toMillis(PlayerConfig.ALT_CLAN_CREATE_DAYS)); + } + + // Remove Clan skills from Player. + removeSkillEffects(player); + + // Remove Residential skills. + if (getCastleId() > 0) + { + final Castle castle = CastleManager.getInstance().getCastleByOwner(this); + if (castle != null) + { + castle.removeResidentialSkills(player); + } + } + + player.sendSkillList(); + player.setClan(null); + + // Players leaving from clan academy have no penalty. + if (exMember.getPledgeType() != -1) + { + player.setClanJoinExpiryTime(clanJoinExpiryTime); + } + + player.setPledgeClass(ClanMember.calculatePledgeClass(player)); + player.broadcastUserInfo(); + + // Disable clan tab. + player.sendPacket(PledgeShowMemberListDeleteAll.STATIC_PACKET); + } + else + { + removeMemberInDatabase(exMember.getObjectId(), clanJoinExpiryTime, getLeaderId() == objectId ? System.currentTimeMillis() + TimeUnit.DAYS.toMillis(PlayerConfig.ALT_CLAN_CREATE_DAYS) : 0); + } + + // Notify to scripts. + if (EventDispatcher.getInstance().hasListener(EventType.ON_PLAYER_CLAN_LEFT)) + { + EventDispatcher.getInstance().notifyEventAsync(new OnPlayerClanLeft(exMember, this)); + } + } + + public Collection getMembers() + { + return _members.values(); + } + + public List getOfflineMembersIds() + { + final List list = new ArrayList<>(); + for (ClanMember temp : _members.values()) + { + if ((temp != null) && !temp.isOnline()) + { + list.add(temp.getObjectId()); + } + } + + return list; + } + + public int getMembersCount() + { + return _members.size(); + } + + public int getSubPledgeMembersCount(int subpl) + { + int result = 0; + for (ClanMember temp : _members.values()) + { + if (temp.getPledgeType() == subpl) + { + result++; + } + } + + return result; + } + + /** + * @param pledgeType the Id of the pledge type. + * @return the maximum number of members allowed for a given {@code pledgeType}. + */ + public int getMaxNrOfMembers(int pledgeType) + { + int limit = 0; + + switch (pledgeType) + { + case 0: + { + switch (_level) + { + case 3: + { + limit = 30; + break; + } + case 2: + { + limit = 20; + break; + } + case 1: + { + limit = 15; + break; + } + case 0: + { + limit = 10; + break; + } + default: + { + limit = 40; + break; + } + } + break; + } + case -1: + { + limit = 20; + break; + } + case 100: + case 200: + { + switch (_level) + { + case 11: + { + limit = 30; + break; + } + default: + { + limit = 20; + break; + } + } + break; + } + case 1001: + case 1002: + case 2001: + case 2002: + { + switch (_level) + { + case 9: + case 10: + case 11: + { + limit = 25; + break; + } + default: + { + limit = 10; + break; + } + } + break; + } + default: + { + break; + } + } + + return limit; + } + + /** + * @param exclude the object Id to exclude from list. + * @return all online members excluding the one with object id {code exclude}. + */ + public List getOnlineMembers(int exclude) + { + final List onlineMembers = new ArrayList<>(); + for (ClanMember temp : _members.values()) + { + if ((temp != null) && temp.isOnline() && (temp.getObjectId() != exclude)) + { + onlineMembers.add(temp.getPlayer()); + } + } + + return onlineMembers; + } + + /** + * @return the online clan member count. + */ + public int getOnlineMembersCount() + { + int count = 0; + for (ClanMember temp : _members.values()) + { + if ((temp == null) || !temp.isOnline()) + { + continue; + } + + count++; + } + + return count; + } + + /** + * @return the alliance Id. + */ + public int getAllyId() + { + return _allyId; + } + + /** + * @return the alliance name. + */ + public String getAllyName() + { + return _allyName; + } + + /** + * @param allyCrestId the alliance crest Id to be set. + */ + public void setAllyCrestId(int allyCrestId) + { + _allyCrestId = allyCrestId; + } + + /** + * @return the alliance crest Id. + */ + public int getAllyCrestId() + { + return _allyCrestId; + } + + /** + * @return the clan level. + */ + public int getLevel() + { + return _level; + } + + /** + * Sets the clan level and updates the clan forum if it's needed. + * @param level the clan level to be set. + */ + private void setLevel(int level) + { + _level = level; + if ((_level >= 2) && (_forum == null) && GeneralConfig.ENABLE_COMMUNITY_BOARD) + { + final Forum forum = ForumsBBSManager.getInstance().getForumByName("ClanRoot"); + if (forum != null) + { + _forum = forum.getChildByName(_name); + if (_forum == null) + { + _forum = ForumsBBSManager.getInstance().createNewForum(_name, ForumsBBSManager.getInstance().getForumByName("ClanRoot"), Forum.CLAN, Forum.CLANMEMBERONLY, getId()); + } + } + } + } + + /** + * @return the castle Id for this clan if owns a castle, zero otherwise. + */ + public int getCastleId() + { + return _castleId; + } + + /** + * @return the hideout Id for this clan if owns a hideout, zero otherwise. + */ + public int getHideoutId() + { + return _hideoutId; + } + + /** + * @param crestId the Id of the clan crest to be set. + */ + public void setCrestId(int crestId) + { + _crestId = crestId; + } + + /** + * @return Returns the clanCrestId. + */ + public int getCrestId() + { + return _crestId; + } + + /** + * @param crestLargeId The id of pledge LargeCrest. + */ + public void setCrestLargeId(int crestLargeId) + { + _crestLargeId = crestLargeId; + } + + /** + * @return Returns the clan CrestLargeId + */ + public int getCrestLargeId() + { + return _crestLargeId; + } + + /** + * @param allyId The allyId to set. + */ + public void setAllyId(int allyId) + { + _allyId = allyId; + } + + /** + * @param allyName The allyName to set. + */ + public void setAllyName(String allyName) + { + _allyName = allyName; + } + + /** + * @param castleId the castle Id to set. + */ + public void setCastleId(int castleId) + { + _castleId = castleId; + } + + /** + * @param hideoutId the hideout Id to set. + */ + public void setHideoutId(int hideoutId) + { + _hideoutId = hideoutId; + } + + /** + * @param id the Id of the player to be verified. + * @return {code true} if the player belongs to the clan. + */ + public boolean isMember(int id) + { + return (id != 0) && _members.containsKey(id); + } + + /** + * @return the Blood Alliance count for this clan + */ + public int getBloodAllianceCount() + { + return _bloodAllianceCount; + } + + /** + * Increase Blood Alliance count by config predefined count and updates the database. + */ + public void increaseBloodAllianceCount() + { + _bloodAllianceCount += SiegeManager.getInstance().getBloodAllianceReward(); + updateBloodAllianceCountInDB(); + } + + /** + * Reset the Blood Alliance count to zero and updates the database. + */ + public void resetBloodAllianceCount() + { + _bloodAllianceCount = 0; + updateBloodAllianceCountInDB(); + } + + /** + * Store current Bloood Alliances count in database. + */ + public void updateBloodAllianceCountInDB() + { + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement("UPDATE clan_data SET blood_alliance_count=? WHERE clan_id=?")) + { + ps.setInt(1, _bloodAllianceCount); + ps.setInt(2, _clanId); + ps.execute(); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Exception on updateBloodAllianceCountInDB(): " + e.getMessage(), e); + } + } + + /** + * @return the Blood Oath count for this clan + */ + public int getBloodOathCount() + { + return _bloodOathCount; + } + + /** + * Increase Blood Oath count by config predefined count and updates the database. + */ + public void increaseBloodOathCount() + { + _bloodOathCount += FeatureConfig.FS_BLOOD_OATH_COUNT; + updateBloodOathCountInDB(); + } + + /** + * Reset the Blood Oath count to zero and updates the database. + */ + public void resetBloodOathCount() + { + _bloodOathCount = 0; + updateBloodOathCountInDB(); + } + + /** + * Store current Bloood Alliances count in database. + */ + public void updateBloodOathCountInDB() + { + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement("UPDATE clan_data SET blood_oath_count=? WHERE clan_id=?")) + { + ps.setInt(1, _bloodOathCount); + ps.setInt(2, _clanId); + ps.execute(); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Exception on updateBloodAllianceCountInDB(): " + e.getMessage(), e); + } + } + + /** + * Updates in database clan information: + *
          + *
        • Clan leader Id
        • + *
        • Alliance Id
        • + *
        • Alliance name
        • + *
        • Clan's reputation
        • + *
        • Alliance's penalty expiration time
        • + *
        • Alliance's penalty type
        • + *
        • Character's penalty expiration time
        • + *
        • Dissolving expiration time
        • + *
        • Clan's id
        • + *
        + */ + public void updateClanInDB() + { + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement("UPDATE clan_data SET leader_id=?,ally_id=?,ally_name=?,reputation_score=?,ally_penalty_expiry_time=?,ally_penalty_type=?,char_penalty_expiry_time=?,dissolving_expiry_time=?,new_leader_id=? WHERE clan_id=?")) + { + ps.setInt(1, getLeaderId()); + ps.setInt(2, _allyId); + ps.setString(3, _allyName); + ps.setInt(4, _reputationScore); + ps.setLong(5, _allyPenaltyExpiryTime); + ps.setInt(6, _allyPenaltyType); + ps.setLong(7, _charPenaltyExpiryTime); + ps.setLong(8, _dissolvingExpiryTime); + ps.setInt(9, _newLeaderId); + ps.setInt(10, _clanId); + ps.execute(); + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "Error saving clan: " + e.getMessage(), e); + } + } + + /** + * Stores in database clan information: + *
          + *
        • Clan Id
        • + *
        • Clan name
        • + *
        • Clan level
        • + *
        • Has castle
        • + *
        • Alliance Id
        • + *
        • Alliance name
        • + *
        • Clan leader Id
        • + *
        • Clan crest Id
        • + *
        • Clan large crest Id
        • + *
        • Alliance crest Id
        • + *
        + */ + public void store() + { + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement(INSERT_CLAN_DATA)) + { + ps.setInt(1, _clanId); + ps.setString(2, _name); + ps.setInt(3, _level); + ps.setInt(4, _castleId); + ps.setInt(5, _bloodAllianceCount); + ps.setInt(6, _bloodOathCount); + ps.setInt(7, _allyId); + ps.setString(8, _allyName); + ps.setInt(9, getLeaderId()); + ps.setInt(10, _crestId); + ps.setInt(11, _crestLargeId); + ps.setInt(12, _allyCrestId); + ps.setInt(13, _newLeaderId); + ps.execute(); + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "Error saving new clan: " + e.getMessage(), e); + } + } + + /** + * Removes a clan member from this clan. + * @param playerId the clan member object ID to be removed + * @param clanJoinExpiryTime the time penalty for the player to join a new clan + * @param clanCreateExpiryTime the time penalty for the player to create a new clan + */ + private void removeMemberInDatabase(int playerId, long clanJoinExpiryTime, long clanCreateExpiryTime) + { + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps1 = con.prepareStatement("UPDATE characters SET clanid=0, title=?, clan_join_expiry_time=?, clan_create_expiry_time=?, clan_privs=0, wantspeace=0, subpledge=0, lvl_joined_academy=0, apprentice=0, sponsor=0 WHERE charId=?"); + PreparedStatement ps2 = con.prepareStatement("UPDATE characters SET apprentice=0 WHERE apprentice=?"); + PreparedStatement ps3 = con.prepareStatement("UPDATE characters SET sponsor=0 WHERE sponsor=?")) + { + // Remove clan member. + ps1.setString(1, ""); + ps1.setLong(2, clanJoinExpiryTime); + ps1.setLong(3, clanCreateExpiryTime); + ps1.setInt(4, playerId); + ps1.execute(); + + // Remove apprentice. + ps2.setInt(1, playerId); + ps2.execute(); + + // Remove sponsor. + ps3.setInt(1, playerId); + ps3.execute(); + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "Error removing clan member: " + e.getMessage(), e); + } + } + + private void restore() + { + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement(SELECT_CLAN_DATA)) + { + ps.setInt(1, _clanId); + try (ResultSet clanData = ps.executeQuery()) + { + if (clanData.next()) + { + setName(clanData.getString("clan_name")); + setLevel(clanData.getInt("clan_level")); + setCastleId(clanData.getInt("hasCastle")); + _bloodAllianceCount = clanData.getInt("blood_alliance_count"); + _bloodOathCount = clanData.getInt("blood_oath_count"); + setAllyId(clanData.getInt("ally_id")); + setAllyName(clanData.getString("ally_name")); + setAllyPenaltyExpiryTime(clanData.getLong("ally_penalty_expiry_time"), clanData.getInt("ally_penalty_type")); + if (_allyPenaltyExpiryTime < System.currentTimeMillis()) + { + setAllyPenaltyExpiryTime(0, 0); + } + + setCharPenaltyExpiryTime(clanData.getLong("char_penalty_expiry_time")); + if ((_charPenaltyExpiryTime + (PlayerConfig.ALT_CLAN_JOIN_DAYS * 86400000)) < System.currentTimeMillis()) // 24*60*60*1000 = 86400000 + { + setCharPenaltyExpiryTime(0); + } + + setDissolvingExpiryTime(clanData.getLong("dissolving_expiry_time")); + + setCrestId(clanData.getInt("crest_id")); + setCrestLargeId(clanData.getInt("crest_large_id")); + setAllyCrestId(clanData.getInt("ally_crest_id")); + + setReputationScore(clanData.getInt("reputation_score")); + setAuctionBiddedAt(clanData.getInt("auction_bid_at"), false); + setNewLeaderId(clanData.getInt("new_leader_id"), false); + + final int leaderId = clanData.getInt("leader_id"); + ps.clearParameters(); + + try (PreparedStatement select = con.prepareStatement("SELECT char_name,level,classid,charId,title,power_grade,subpledge,apprentice,sponsor,sex,race FROM characters WHERE clanid=?")) + { + select.setInt(1, _clanId); + try (ResultSet clanMember = select.executeQuery()) + { + ClanMember member = null; + while (clanMember.next()) + { + member = new ClanMember(this, clanMember); + if (member.getObjectId() == leaderId) + { + setLeader(member); + } + else + { + addClanMember(member); + } + } + } + } + } + } + + restoreSubPledges(); + restoreRankPrivs(); + restoreSkills(); + restoreNotice(); + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "Error restoring clan data: " + e.getMessage(), e); + } + } + + private void restoreNotice() + { + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement("SELECT enabled,notice FROM clan_notices WHERE clan_id=?")) + { + ps.setInt(1, _clanId); + try (ResultSet noticeData = ps.executeQuery()) + { + while (noticeData.next()) + { + _noticeEnabled = Boolean.parseBoolean(noticeData.getString("enabled")); + _notice = noticeData.getString("notice"); + } + } + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "Error restoring clan notice: " + e.getMessage(), e); + } + } + + private void storeNotice(String noticeValue, boolean enabled) + { + String notice = noticeValue; + if (notice == null) + { + notice = ""; + } + + if (notice.length() > MAX_NOTICE_LENGTH) + { + notice = notice.substring(0, MAX_NOTICE_LENGTH - 1); + } + + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement("INSERT INTO clan_notices (clan_id,notice,enabled) values (?,?,?) ON DUPLICATE KEY UPDATE notice=?,enabled=?")) + { + ps.setInt(1, _clanId); + ps.setString(2, notice); + if (enabled) + { + ps.setString(3, "true"); + } + else + { + ps.setString(3, "false"); + } + + ps.setString(4, notice); + if (enabled) + { + ps.setString(5, "true"); + } + else + { + ps.setString(5, "false"); + } + + ps.execute(); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Error could not store clan notice: " + e.getMessage(), e); + } + + _notice = notice; + _noticeEnabled = enabled; + } + + public void setNoticeEnabled(boolean enabled) + { + storeNotice(getNotice(), enabled); + } + + public void setNotice(String notice) + { + storeNotice(notice, _noticeEnabled); + } + + public boolean isNoticeEnabled() + { + return _noticeEnabled; + } + + public String getNotice() + { + if (_notice == null) + { + return ""; + } + + // Bypass exploit check. + final String text = _notice.toLowerCase(); + if (text.contains("action") && text.contains("bypass")) + { + return ""; + } + + // Returns text without tags. + return _notice.replaceAll("<.*?>", ""); + } + + private void restoreSkills() + { + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement("SELECT skill_id,skill_level,sub_pledge_id FROM clan_skills WHERE clan_id=?")) + { + // Retrieve all skills of this Player from the database. + ps.setInt(1, _clanId); + try (ResultSet rset = ps.executeQuery()) + { + // Go though the recordset of this SQL query. + while (rset.next()) + { + final int id = rset.getInt("skill_id"); + final int level = rset.getInt("skill_level"); + + // Create a Skill object for each record. + final Skill skill = SkillData.getInstance().getSkill(id, level); + + // Add the Skill object to the Clan _skills. + final int subType = rset.getInt("sub_pledge_id"); + if (subType == -2) + { + _skills.put(skill.getId(), skill); + } + else if (subType == 0) + { + _subPledgeSkills.put(skill.getId(), skill); + } + else + { + final SubPledge subunit = _subPledges.get(subType); + if (subunit != null) + { + subunit.addNewSkill(skill); + } + else + { + LOGGER.info("Missing subpledge " + subType + " for clan " + this + ", skill skipped."); + } + } + } + } + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "Error restoring clan skills: " + e.getMessage(), e); + } + } + + /** + * @return all the clan skills. + */ + public Collection getAllSkills() + { + return _skills.values(); + } + + /** + * @return the map containing this clan skills. + */ + public Map getSkills() + { + return _skills; + } + + /** + * Used to add a skill to skill list of this Pledge + * @param newSkill + * @return + */ + public Skill addSkill(Skill newSkill) + { + return newSkill != null ? _skills.put(newSkill.getId(), newSkill) : null; + } + + public Skill addNewSkill(Skill newSkill) + { + return addNewSkill(newSkill, -2); + } + + /** + * Used to add a new skill to the list, send a packet to all online clan members, update their stats and store it in db + * @param newSkill + * @param subType + * @return + */ + public Skill addNewSkill(Skill newSkill, int subType) + { + Skill oldSkill = null; + if (newSkill != null) + { + if (subType == -2) + { + oldSkill = _skills.put(newSkill.getId(), newSkill); + } + else if (subType == 0) + { + oldSkill = _subPledgeSkills.put(newSkill.getId(), newSkill); + } + else + { + final SubPledge subunit = getSubPledge(subType); + if (subunit == null) + { + LOGGER.log(Level.WARNING, "Subpledge " + subType + " does not exist for clan " + this); + return oldSkill; + } + + oldSkill = subunit.addNewSkill(newSkill); + } + + try (Connection con = DatabaseFactory.getConnection()) + { + if (oldSkill != null) + { + try (PreparedStatement ps = con.prepareStatement("UPDATE clan_skills SET skill_level=? WHERE skill_id=? AND clan_id=?")) + { + ps.setInt(1, newSkill.getLevel()); + ps.setInt(2, oldSkill.getId()); + ps.setInt(3, _clanId); + ps.execute(); + } + } + else + { + try (PreparedStatement ps = con.prepareStatement("INSERT INTO clan_skills (clan_id,skill_id,skill_level,skill_name,sub_pledge_id) VALUES (?,?,?,?,?)")) + { + ps.setInt(1, _clanId); + ps.setInt(2, newSkill.getId()); + ps.setInt(3, newSkill.getLevel()); + ps.setString(4, newSkill.getName()); + ps.setInt(5, subType); + ps.execute(); + } + } + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Error could not store clan skills: " + e.getMessage(), e); + } + + final SystemMessage sm = new SystemMessage(SystemMessageId.THE_CLAN_SKILL_S1_HAS_BEEN_ADDED); + sm.addSkillName(newSkill.getId()); + + for (ClanMember temp : _members.values()) + { + if ((temp != null) && (temp.getPlayer() != null) && temp.isOnline()) + { + if (subType == -2) + { + if (newSkill.getMinPledgeClass() <= temp.getPlayer().getPledgeClass()) + { + temp.getPlayer().addSkill(newSkill, false); // Skill is not saved to player DB. + temp.getPlayer().sendPacket(new PledgeSkillListAdd(newSkill.getId(), newSkill.getLevel())); + temp.getPlayer().sendPacket(sm); + temp.getPlayer().sendSkillList(); + } + } + else if (temp.getPledgeType() == subType) + { + temp.getPlayer().addSkill(newSkill, false); // Skill is not saved to player DB. + // temp.getPlayer().sendPacket(new ExSubPledgeSkillAdd(subType, newSkill.getId(), newSkill.getLevel())); + temp.getPlayer().sendPacket(sm); + temp.getPlayer().sendSkillList(); + } + } + } + } + + return oldSkill; + } + + public void addSkillEffects() + { + for (Skill skill : _skills.values()) + { + for (ClanMember temp : _members.values()) + { + try + { + if ((temp != null) && temp.isOnline() && (skill.getMinPledgeClass() <= temp.getPlayer().getPledgeClass())) + { + temp.getPlayer().addSkill(skill, false); + } + } + catch (NullPointerException e) + { + LOGGER.log(Level.WARNING, e.getMessage(), e); + } + } + } + } + + public void addSkillEffects(Player player) + { + if (player == null) + { + return; + } + + final int playerSocialClass = player.getPledgeClass() + 1; + for (Skill skill : _skills.values()) + { + final SkillLearn skillLearn = SkillTreeData.getInstance().getPledgeSkill(skill.getId(), skill.getLevel()); + if ((skillLearn == null) || (skillLearn.getSocialClass() == null) || (playerSocialClass >= skillLearn.getSocialClass().ordinal())) + { + player.addSkill(skill, false); // Skill is not saved to player DB. + } + } + + final SubPledge subunit = getSubPledge(player.getPledgeType()); + if (subunit == null) + { + return; + } + + for (Skill skill : subunit.getSkills()) + { + player.addSkill(skill, false); // Skill is not saved to player DB. + } + + if (_reputationScore < 0) + { + skillsStatus(player, true); + } + } + + public void removeSkillEffects(Player player) + { + if (player == null) + { + return; + } + + for (Skill skill : _skills.values()) + { + player.removeSkill(skill, false); // Skill is not saved to player DB. + } + + if (player.getPledgeType() == 0) + { + for (Skill skill : _subPledgeSkills.values()) + { + player.removeSkill(skill, false); // Skill is not saved to player DB. + } + } + else + { + final SubPledge subunit = getSubPledge(player.getPledgeType()); + if (subunit == null) + { + return; + } + + for (Skill skill : subunit.getSkills()) + { + player.removeSkill(skill, false); // Skill is not saved to player DB. + } + } + } + + public void skillsStatus(Player player, boolean disable) + { + if (player == null) + { + return; + } + + for (Skill skill : _skills.values()) + { + if (disable) + { + player.disableSkill(skill, -1); + } + else + { + player.enableSkill(skill); + } + } + + if (player.getPledgeType() == 0) + { + for (Skill skill : _subPledgeSkills.values()) + { + if (disable) + { + player.disableSkill(skill, -1); + } + else + { + player.enableSkill(skill); + } + } + } + else + { + final SubPledge subunit = getSubPledge(player.getPledgeType()); + if (subunit != null) + { + for (Skill skill : subunit.getSkills()) + { + if (disable) + { + player.disableSkill(skill, -1); + } + else + { + player.enableSkill(skill); + } + } + } + } + } + + public void broadcastToOnlineAllyMembers(ServerPacket packet) + { + for (Clan clan : ClanTable.getInstance().getClanAllies(getAllyId())) + { + clan.broadcastToOnlineMembers(packet); + } + } + + public void broadcastToOnlineMembers(ServerPacket packet) + { + for (ClanMember member : _members.values()) + { + if ((member != null) && member.isOnline()) + { + member.getPlayer().sendPacket(packet); + } + } + } + + public void broadcastCSToOnlineMembers(CreatureSay packet, Player broadcaster) + { + for (ClanMember member : _members.values()) + { + if ((member != null) && member.isOnline() && !BlockList.isBlocked(member.getPlayer(), broadcaster)) + { + member.getPlayer().sendPacket(packet); + } + } + } + + public void broadcastToOtherOnlineMembers(ServerPacket packet, Player player) + { + for (ClanMember member : _members.values()) + { + if ((member != null) && member.isOnline() && (member.getPlayer() != player)) + { + member.getPlayer().sendPacket(packet); + } + } + } + + @Override + public String toString() + { + return _name + "[" + _clanId + "]"; + } + + public ItemContainer getWarehouse() + { + return _warehouse; + } + + public boolean isAtWarWith(int id) + { + return _atWarWith.contains(id); + } + + public boolean isAtWarWith(Clan clan) + { + return _atWarWith.contains(clan.getId()); + } + + public boolean isAtWarAttacker(int id) + { + return _atWarAttackers.contains(id); + } + + public void setEnemyClan(Clan clan) + { + _atWarWith.add(clan.getId()); + } + + public void setEnemyClan(int id) + { + _atWarWith.add(id); + } + + public void setAttackerClan(Clan clan) + { + _atWarAttackers.add(clan.getId()); + } + + public void setAttackerClan(int clan) + { + _atWarAttackers.add(clan); + } + + public void deleteEnemyClan(Clan clan) + { + _atWarWith.remove(clan.getId()); + } + + public void deleteAttackerClan(Clan clan) + { + _atWarAttackers.remove(clan.getId()); + } + + public int getHiredGuards() + { + return _hiredGuards; + } + + public void incrementHiredGuards() + { + _hiredGuards++; + } + + public boolean isAtWar() + { + return (_atWarWith != null) && !_atWarWith.isEmpty(); + } + + public Set getWarList() + { + return _atWarWith; + } + + public Set getAttackerList() + { + return _atWarAttackers; + } + + public void broadcastClanStatus() + { + for (Player member : getOnlineMembers(0)) + { + member.sendPacket(PledgeShowMemberListDeleteAll.STATIC_PACKET); + member.sendPacket(new PledgeShowMemberListAll(this, member)); + } + } + + public static class SubPledge + { + private final int _id; + private String _subPledgeName; + private int _leaderId; + private final Map _subPledgeSkills = new HashMap<>(); + + public SubPledge(int id, String name, int leaderId) + { + _id = id; + _subPledgeName = name; + _leaderId = leaderId; + } + + public int getId() + { + return _id; + } + + public String getName() + { + return _subPledgeName; + } + + public void setName(String name) + { + _subPledgeName = name; + } + + public int getLeaderId() + { + return _leaderId; + } + + public void setLeaderId(int leaderId) + { + _leaderId = leaderId; + } + + public Skill addNewSkill(Skill skill) + { + return _subPledgeSkills.put(skill.getId(), skill); + } + + public Collection getSkills() + { + return _subPledgeSkills.values(); + } + + public Skill getSkill(int id) + { + return _subPledgeSkills.get(id); + } + } + + public static class RankPrivs + { + private final int _rankId; + private final int _party; // TODO: Find out what this stuff means and implement it. + private final ClanPrivileges _rankPrivs; + + public RankPrivs(int rank, int party, int privs) + { + _rankId = rank; + _party = party; + _rankPrivs = new ClanPrivileges(privs); + } + + public RankPrivs(int rank, int party, ClanPrivileges rankPrivs) + { + _rankId = rank; + _party = party; + _rankPrivs = rankPrivs; + } + + public int getRank() + { + return _rankId; + } + + public int getParty() + { + return _party; + } + + public ClanPrivileges getPrivs() + { + return _rankPrivs; + } + + public void setPrivs(int privs) + { + _rankPrivs.setMask(privs); + } + } + + private void restoreSubPledges() + { + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement("SELECT sub_pledge_id,name,leader_id FROM clan_subpledges WHERE clan_id=?")) + { + // Retrieve all subpledges of this clan from the database. + ps.setInt(1, _clanId); + try (ResultSet rset = ps.executeQuery()) + { + while (rset.next()) + { + final int id = rset.getInt("sub_pledge_id"); + final String name = rset.getString("name"); + final int leaderId = rset.getInt("leader_id"); + + // Create a SubPledge object for each record. + final SubPledge pledge = new SubPledge(id, name, leaderId); + _subPledges.put(id, pledge); + } + } + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Could not restore clan sub-units: " + e.getMessage(), e); + } + } + + /** + * used to retrieve subPledge by type + * @param pledgeType + * @return + */ + public SubPledge getSubPledge(int pledgeType) + { + return _subPledges.get(pledgeType); + } + + /** + * Used to retrieve subPledge by type + * @param pledgeName + * @return + */ + public SubPledge getSubPledge(String pledgeName) + { + for (SubPledge sp : _subPledges.values()) + { + if (sp.getName().equalsIgnoreCase(pledgeName)) + { + return sp; + } + } + + return null; + } + + /** + * Used to retrieve all subPledges + * @return + */ + public Collection getAllSubPledges() + { + return _subPledges.values(); + } + + public SubPledge createSubPledge(Player player, int pledgeTypeValue, int leaderId, String subPledgeName) + { + SubPledge subPledge = null; + final int pledgeType = getAvailablePledgeTypes(pledgeTypeValue); + if (pledgeType == 0) + { + if (pledgeType == SUBUNIT_ACADEMY) + { + player.sendPacket(SystemMessageId.YOUR_CLAN_HAS_ALREADY_ESTABLISHED_A_CLAN_ACADEMY); + } + else + { + player.sendMessage("You can't create any more sub-units of this type"); + } + + return null; + } + + if (_leader.getObjectId() == leaderId) + { + player.sendMessage("Leader is not correct"); + return null; + } + + // Royal Guard 5000 points per each + // Order of Knights 10000 points per each + if ((pledgeType != -1) && (((_reputationScore < FeatureConfig.ROYAL_GUARD_COST) && (pledgeType < SUBUNIT_KNIGHT1)) || ((_reputationScore < FeatureConfig.KNIGHT_UNIT_COST) && (pledgeType > SUBUNIT_ROYAL2)))) + { + player.sendPacket(SystemMessageId.THE_CLAN_REPUTATION_SCORE_IS_TOO_LOW); + return null; + } + + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement("INSERT INTO clan_subpledges (clan_id,sub_pledge_id,name,leader_id) values (?,?,?,?)")) + { + ps.setInt(1, _clanId); + ps.setInt(2, pledgeType); + ps.setString(3, subPledgeName); + ps.setInt(4, pledgeType != -1 ? leaderId : 0); + ps.execute(); + + subPledge = new SubPledge(pledgeType, subPledgeName, leaderId); + _subPledges.put(pledgeType, subPledge); + + if (pledgeType != -1) + { + // Royal Guard 5000 points per each + // Order of Knights 10000 points per each + if (pledgeType < SUBUNIT_KNIGHT1) + { + setReputationScore(_reputationScore - FeatureConfig.ROYAL_GUARD_COST); + } + else + { + setReputationScore(_reputationScore - FeatureConfig.KNIGHT_UNIT_COST); + // TODO: clan lvl9 or more can reinforce knights cheaper if first knight unit already created, use Config.KNIGHT_REINFORCE_COST + } + } + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "Error saving sub clan data: " + e.getMessage(), e); + } + + broadcastToOnlineMembers(new PledgeShowInfoUpdate(_leader.getClan())); + broadcastToOnlineMembers(new PledgeReceiveSubPledgeCreated(subPledge, _leader.getClan())); + return subPledge; + } + + public int getAvailablePledgeTypes(int pledgeType) + { + if (_subPledges.get(pledgeType) != null) + { + // LOGGER.warning("found sub-unit with id: "+pledgeType); + switch (pledgeType) + { + case SUBUNIT_ACADEMY: + { + return 0; + } + case SUBUNIT_ROYAL1: + { + return getAvailablePledgeTypes(SUBUNIT_ROYAL2); + } + case SUBUNIT_ROYAL2: + { + return 0; + } + case SUBUNIT_KNIGHT1: + { + return getAvailablePledgeTypes(SUBUNIT_KNIGHT2); + } + case SUBUNIT_KNIGHT2: + { + return getAvailablePledgeTypes(SUBUNIT_KNIGHT3); + } + case SUBUNIT_KNIGHT3: + { + return getAvailablePledgeTypes(SUBUNIT_KNIGHT4); + } + case SUBUNIT_KNIGHT4: + { + return 0; + } + } + } + + return pledgeType; + } + + public void updateSubPledgeInDB(int pledgeType) + { + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement("UPDATE clan_subpledges SET leader_id=?, name=? WHERE clan_id=? AND sub_pledge_id=?")) + { + ps.setInt(1, getSubPledge(pledgeType).getLeaderId()); + ps.setString(2, getSubPledge(pledgeType).getName()); + ps.setInt(3, _clanId); + ps.setInt(4, pledgeType); + ps.execute(); + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "Error updating subpledge: " + e.getMessage(), e); + } + } + + private void restoreRankPrivs() + { + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement("SELECT privs,`rank`,party FROM clan_privs WHERE clan_id=?")) + { + // Retrieve all skills of this Player from the database. + ps.setInt(1, _clanId); + // LOGGER.warning("clanPrivs restore for ClanId : "+getClanId()); + try (ResultSet rset = ps.executeQuery()) + { + // Go though the recordset of this SQL query. + while (rset.next()) + { + final int rank = rset.getInt("rank"); + // Create a SubPledge object for each record. + if (rank == -1) + { + continue; + } + + // int party = rset.getInt("party"); + final int privileges = rset.getInt("privs"); + _privs.get(rank).setPrivs(privileges); + } + } + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "Error restoring clan privs by rank: " + e.getMessage(), e); + } + } + + public void initializePrivs() + { + for (int i = 1; i < 10; i++) + { + _privs.put(i, new RankPrivs(i, 0, new ClanPrivileges())); + } + } + + public ClanPrivileges getRankPrivs(int rank) + { + return _privs.get(rank) != null ? _privs.get(rank).getPrivs() : new ClanPrivileges(); + } + + public void setRankPrivs(int rank, int privs) + { + if (_privs.get(rank) != null) + { + _privs.get(rank).setPrivs(privs); + + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement("REPLACE INTO clan_privs (clan_id,`rank`,party,privs) VALUES (?,?,?,?)")) + { + // Retrieve all skills of this Player from the database. + ps.setInt(1, _clanId); + ps.setInt(2, rank); + ps.setInt(3, 0); + ps.setInt(4, privs); + ps.execute(); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Could not store clan privs for rank: " + e.getMessage(), e); + } + + for (ClanMember cm : getMembers()) + { + if (cm.isOnline() && (cm.getPowerGrade() == rank) && (cm.getPlayer() != null)) + { + cm.getPlayer().getClanPrivileges().setMask(privs); + cm.getPlayer().updateUserInfo(); + } + } + + broadcastClanStatus(); + } + else + { + _privs.put(rank, new RankPrivs(rank, 0, privs)); + + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement("REPLACE INTO clan_privs (clan_id,`rank`,party,privs) VALUES (?,?,?,?)")) + { + // Retrieve all skills of this Player from the database. + ps.setInt(1, _clanId); + ps.setInt(2, rank); + ps.setInt(3, 0); + ps.setInt(4, privs); + ps.execute(); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Could not create new rank and store clan privs for rank: " + e.getMessage(), e); + } + } + } + + /** + * @return all RankPrivs. + */ + public Collection getAllRankPrivs() + { + return _privs.values(); + } + + public int getLeaderSubPledge(int leaderId) + { + int id = 0; + for (SubPledge sp : _subPledges.values()) + { + if (sp.getLeaderId() == 0) + { + continue; + } + + if (sp.getLeaderId() == leaderId) + { + id = sp.getId(); + } + } + + return id; + } + + public synchronized void addReputationScore(int value) + { + setReputationScore(_reputationScore + value); + } + + public synchronized void takeReputationScore(int value) + { + setReputationScore(_reputationScore - value); + } + + private void setReputationScore(int value) + { + if ((_reputationScore >= 0) && (value < 0)) + { + broadcastToOnlineMembers(new SystemMessage(SystemMessageId.SINCE_THE_CLAN_REPUTATION_SCORE_HAS_DROPPED_TO_0_OR_LOWER_YOUR_CLAN_SKILL_S_WILL_BE_DE_ACTIVATED)); + for (ClanMember member : _members.values()) + { + if (member.isOnline() && (member.getPlayer() != null)) + { + skillsStatus(member.getPlayer(), true); + } + } + } + else if ((_reputationScore < 0) && (value >= 0)) + { + broadcastToOnlineMembers(new SystemMessage(SystemMessageId.CLAN_SKILLS_WILL_NOW_BE_ACTIVATED_SINCE_THE_CLAN_S_REPUTATION_SCORE_IS_0_OR_HIGHER)); + for (ClanMember member : _members.values()) + { + if (member.isOnline() && (member.getPlayer() != null)) + { + skillsStatus(member.getPlayer(), false); + } + } + } + + _reputationScore = value; + if (_reputationScore > 100000000) + { + _reputationScore = 100000000; + } + + if (_reputationScore < -100000000) + { + _reputationScore = -100000000; + } + + broadcastToOnlineMembers(new PledgeShowInfoUpdate(this)); + } + + public int getReputationScore() + { + return _reputationScore; + } + + public synchronized void setRank(int rank) + { + _rank = rank; + } + + public int getRank() + { + return _rank; + } + + public int getAuctionBiddedAt() + { + return _auctionBiddedAt; + } + + public void setAuctionBiddedAt(int id, boolean storeInDb) + { + _auctionBiddedAt = id; + if (storeInDb) + { + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement("UPDATE clan_data SET auction_bid_at=? WHERE clan_id=?")) + { + ps.setInt(1, id); + ps.setInt(2, _clanId); + ps.execute(); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Could not store auction for clan: " + e.getMessage(), e); + } + } + } + + /** + * @param player the clan inviting player. + * @param target the invited player. + * @param pledgeType the pledge type to join. + * @return {core true} if player and target meet various conditions to join a clan. + */ + public boolean checkClanJoinCondition(Player player, Player target, int pledgeType) + { + if (player == null) + { + return false; + } + + if (!player.hasAccess(ClanAccess.INVITE_MEMBER)) + { + player.sendPacket(SystemMessageId.YOU_ARE_NOT_AUTHORIZED_TO_DO_THAT); + return false; + } + + if (target == null) + { + player.sendPacket(SystemMessageId.YOU_HAVE_INVITED_THE_WRONG_TARGET); + return false; + } + + if (player.getObjectId() == target.getObjectId()) + { + player.sendPacket(SystemMessageId.YOU_CANNOT_ASK_YOURSELF_TO_APPLY_TO_A_CLAN); + return false; + } + + if (_charPenaltyExpiryTime > System.currentTimeMillis()) + { + player.sendPacket(SystemMessageId.AFTER_A_CLAN_MEMBER_IS_DISMISSED_FROM_A_CLAN_THE_CLAN_MUST_WAIT_AT_LEAST_A_DAY_BEFORE_ACCEPTING_A_NEW_MEMBER); + return false; + } + + if (target.getClanId() != 0) + { + final SystemMessage sm = new SystemMessage(SystemMessageId.S1_IS_WORKING_WITH_ANOTHER_CLAN); + sm.addString(target.getName()); + player.sendPacket(sm); + return false; + } + + if (target.getClanJoinExpiryTime() > System.currentTimeMillis()) + { + final SystemMessage sm = new SystemMessage(SystemMessageId.S1_CANNOT_JOIN_THE_CLAN_BECAUSE_ONE_DAY_HAS_NOT_YET_PASSED_SINCE_HE_SHE_LEFT_ANOTHER_CLAN); + sm.addString(target.getName()); + player.sendPacket(sm); + return false; + } + + if (((target.getLevel() > 40) || (target.getPlayerClass().level() >= 2)) && (pledgeType == -1)) + { + final SystemMessage sm = new SystemMessage(SystemMessageId.S1_DOES_NOT_MEET_THE_REQUIREMENTS_TO_JOIN_A_CLAN_ACADEMY); + sm.addString(target.getName()); + player.sendPacket(sm); + player.sendPacket(SystemMessageId.TO_JOIN_A_CLAN_ACADEMY_CHARACTERS_MUST_BE_LEVEL_40_OR_BELOW_NOT_BELONG_ANOTHER_CLAN_AND_NOT_YET_COMPLETED_THEIR_2ND_CLASS_TRANSFER); + return false; + } + + if (getSubPledgeMembersCount(pledgeType) >= getMaxNrOfMembers(pledgeType)) + { + if (pledgeType == 0) + { + final SystemMessage sm = new SystemMessage(SystemMessageId.S1_IS_FULL_AND_CANNOT_ACCEPT_ADDITIONAL_CLAN_MEMBERS_AT_THIS_TIME); + sm.addString(_name); + player.sendPacket(sm); + } + else + { + player.sendPacket(SystemMessageId.THE_ACADEMY_ROYAL_GUARD_ORDER_OF_KNIGHTS_IS_FULL_AND_CANNOT_ACCEPT_NEW_MEMBERS_AT_THIS_TIME); + } + + return false; + } + + return true; + } + + /** + * @param player the clan inviting player. + * @param target the invited player. + * @return {core true} if player and target meet various conditions to join a clan. + */ + public boolean checkAllyJoinCondition(Player player, Player target) + { + if (player == null) + { + return false; + } + + if ((player.getAllyId() == 0) || !player.isClanLeader() || (player.getClanId() != player.getAllyId())) + { + player.sendPacket(SystemMessageId.THIS_FEATURE_IS_ONLY_AVAILABLE_ALLIANCE_LEADERS); + return false; + } + + final Clan leaderClan = player.getClan(); + if ((leaderClan.getAllyPenaltyExpiryTime() > System.currentTimeMillis()) && (leaderClan.getAllyPenaltyType() == PENALTY_TYPE_DISMISS_CLAN)) + { + player.sendPacket(SystemMessageId.YOU_MAY_NOT_ACCEPT_ANY_CLAN_WITHIN_A_DAY_AFTER_EXPELLING_ANOTHER_CLAN); + return false; + } + + if (target == null) + { + player.sendPacket(SystemMessageId.YOU_HAVE_INVITED_THE_WRONG_TARGET); + return false; + } + + if (player.getObjectId() == target.getObjectId()) + { + player.sendPacket(SystemMessageId.YOU_CANNOT_ASK_YOURSELF_TO_APPLY_TO_A_CLAN); + return false; + } + + if (target.getClan() == null) + { + player.sendPacket(SystemMessageId.THE_TARGET_MUST_BE_A_CLAN_MEMBER); + return false; + } + + if (!target.isClanLeader()) + { + final SystemMessage sm = new SystemMessage(SystemMessageId.S1_IS_NOT_A_CLAN_LEADER); + sm.addString(target.getName()); + player.sendPacket(sm); + return false; + } + + final Clan targetClan = target.getClan(); + if (target.getAllyId() != 0) + { + final SystemMessage sm = new SystemMessage(SystemMessageId.S1_CLAN_IS_ALREADY_A_MEMBER_OF_S2_ALLIANCE); + sm.addString(targetClan.getName()); + sm.addString(targetClan.getAllyName()); + player.sendPacket(sm); + return false; + } + + if (targetClan.getAllyPenaltyExpiryTime() > System.currentTimeMillis()) + { + if (targetClan.getAllyPenaltyType() == PENALTY_TYPE_CLAN_LEAVED) + { + final SystemMessage sm = new SystemMessage(SystemMessageId.S1_CLAN_CANNOT_JOIN_THE_ALLIANCE_BECAUSE_ONE_DAY_HAS_NOT_YET_PASSED_SINCE_IT_LEFT_ANOTHER_ALLIANCE); + sm.addString(target.getClan().getName()); + sm.addString(target.getClan().getAllyName()); + player.sendPacket(sm); + return false; + } + + if (targetClan.getAllyPenaltyType() == PENALTY_TYPE_CLAN_DISMISSED) + { + player.sendPacket(SystemMessageId.A_CLAN_THAT_HAS_WITHDRAWN_OR_BEEN_EXPELLED_CANNOT_ENTER_INTO_AN_ALLIANCE_WITHIN_ONE_DAY_OF_WITHDRAWAL_OR_EXPULSION); + return false; + } + } + + if (player.isInsideZone(ZoneId.SIEGE) && target.isInsideZone(ZoneId.SIEGE)) + { + player.sendPacket(SystemMessageId.THE_OPPOSING_CLAN_IS_PARTICIPATING_IN_A_SIEGE_BATTLE); + return false; + } + + if (leaderClan.isAtWarWith(targetClan.getId())) + { + player.sendPacket(SystemMessageId.YOU_MAY_NOT_ALLY_WITH_A_CLAN_YOU_ARE_CURRENTLY_AT_WAR_WITH_THAT_WOULD_BE_DIABOLICAL_AND_TREACHEROUS); + return false; + } + + if (ClanTable.getInstance().getClanAllies(player.getAllyId()).size() >= PlayerConfig.ALT_MAX_NUM_OF_CLANS_IN_ALLY) + { + player.sendPacket(SystemMessageId.YOU_HAVE_EXCEEDED_THE_LIMIT); + return false; + } + + return true; + } + + public long getAllyPenaltyExpiryTime() + { + return _allyPenaltyExpiryTime; + } + + public int getAllyPenaltyType() + { + return _allyPenaltyType; + } + + public void setAllyPenaltyExpiryTime(long expiryTime, int penaltyType) + { + _allyPenaltyExpiryTime = expiryTime; + _allyPenaltyType = penaltyType; + } + + public long getCharPenaltyExpiryTime() + { + return _charPenaltyExpiryTime; + } + + public void setCharPenaltyExpiryTime(long time) + { + _charPenaltyExpiryTime = time; + } + + public long getDissolvingExpiryTime() + { + return _dissolvingExpiryTime; + } + + public void setDissolvingExpiryTime(long time) + { + _dissolvingExpiryTime = time; + } + + public void createAlly(Player player, String allyName) + { + if (player == null) + { + return; + } + + if (!player.isClanLeader()) + { + player.sendPacket(SystemMessageId.ONLY_CLAN_LEADERS_MAY_CREATE_ALLIANCES); + return; + } + + if (_allyId != 0) + { + player.sendPacket(SystemMessageId.YOU_ALREADY_BELONG_TO_ANOTHER_ALLIANCE); + return; + } + + if (_level < 5) + { + player.sendPacket(SystemMessageId.TO_CREATE_AN_ALLIANCE_YOUR_CLAN_MUST_BE_LEVEL_5_OR_HIGHER); + return; + } + + if ((_allyPenaltyExpiryTime > System.currentTimeMillis()) && (_allyPenaltyType == PENALTY_TYPE_DISSOLVE_ALLY)) + { + player.sendPacket(SystemMessageId.YOU_CANNOT_CREATE_A_NEW_ALLIANCE_WITHIN_10_DAYS_AFTER_DISSOLUTION); + return; + } + + if (_dissolvingExpiryTime > System.currentTimeMillis()) + { + player.sendPacket(SystemMessageId.YOU_MAY_NOT_CREATE_AN_ALLIANCE_DURING_THE_TERM_OF_DISSOLUTION_POSTPONEMENT); + return; + } + + if (!StringUtil.isAlphaNumeric(allyName)) + { + player.sendPacket(SystemMessageId.INCORRECT_ALLIANCE_NAME_PLEASE_TRY_AGAIN); + return; + } + + if ((allyName.length() > 16) || (allyName.length() < 2)) + { + player.sendPacket(SystemMessageId.INCORRECT_LENGTH_FOR_AN_ALLIANCE_NAME); + return; + } + + if (ClanTable.getInstance().isAllyExists(allyName)) + { + player.sendPacket(SystemMessageId.THIS_ALLIANCE_NAME_ALREADY_EXISTS); + return; + } + + setAllyId(_clanId); + setAllyName(allyName.trim()); + setAllyPenaltyExpiryTime(0, 0); + updateClanInDB(); + + player.updateUserInfo(); + + // TODO: Need correct message id + player.sendMessage("Alliance " + allyName + " has been created."); + } + + public void dissolveAlly(Player player) + { + if (_allyId == 0) + { + player.sendPacket(SystemMessageId.YOU_ARE_NOT_CURRENTLY_ALLIED_WITH_ANY_CLANS); + return; + } + + if (!player.isClanLeader() || (_clanId != _allyId)) + { + player.sendPacket(SystemMessageId.THIS_FEATURE_IS_ONLY_AVAILABLE_ALLIANCE_LEADERS); + return; + } + + if (player.isInsideZone(ZoneId.SIEGE)) + { + player.sendPacket(SystemMessageId.YOU_CANNOT_DISSOLVE_AN_ALLIANCE_WHILE_AN_AFFILIATED_CLAN_IS_PARTICIPATING_IN_A_SIEGE_BATTLE); + return; + } + + broadcastToOnlineAllyMembers(new SystemMessage(SystemMessageId.THE_ALLIANCE_HAS_BEEN_DISSOLVED)); + + final long currentTime = System.currentTimeMillis(); + for (Clan clan : ClanTable.getInstance().getClanAllies(getAllyId())) + { + if (clan.getId() != getId()) + { + clan.setAllyId(0); + clan.setAllyName(null); + clan.setAllyPenaltyExpiryTime(0, 0); + clan.updateClanInDB(); + } + } + + setAllyId(0); + setAllyName(null); + changeAllyCrest(0, false); + setAllyPenaltyExpiryTime(currentTime + (PlayerConfig.ALT_CREATE_ALLY_DAYS_WHEN_DISSOLVED * 86400000), PENALTY_TYPE_DISSOLVE_ALLY); // 24*60*60*1000 = 86400000 + updateClanInDB(); + } + + public boolean levelUpClan(Player player) + { + if (!player.isClanLeader()) + { + player.sendPacket(SystemMessageId.YOU_ARE_NOT_AUTHORIZED_TO_DO_THAT); + return false; + } + + if (System.currentTimeMillis() < _dissolvingExpiryTime) + { + player.sendPacket(SystemMessageId.YOU_CANNOT_RAISE_YOUR_CLAN_LEVEL_DURING_THE_TERM_OF_DISPERSION_POSTPONEMENT); + return false; + } + + boolean increaseClanLevel = false; + + switch (_level) + { + case 0: + { + // Upgrade to 1 + if ((player.getSp() >= 20000) && (player.getAdena() >= 650000) && player.reduceAdena(ItemProcessType.FEE, 650000, player.getTarget(), true)) + { + player.setSp(player.getSp() - 20000); + final SystemMessage sp = new SystemMessage(SystemMessageId.YOUR_SP_HAS_DECREASED_BY_S1); + sp.addInt(20000); + player.sendPacket(sp); + increaseClanLevel = true; + } + break; + } + case 1: + { + // Upgrade to 2 + if ((player.getSp() >= 100000) && (player.getAdena() >= 2500000) && player.reduceAdena(ItemProcessType.FEE, 2500000, player.getTarget(), true)) + { + player.setSp(player.getSp() - 100000); + final SystemMessage sp = new SystemMessage(SystemMessageId.YOUR_SP_HAS_DECREASED_BY_S1); + sp.addInt(100000); + player.sendPacket(sp); + increaseClanLevel = true; + } + break; + } + case 2: + { + // Upgrade to 3 + if ((player.getSp() >= 350000) && (player.getInventory().getItemByItemId(1419) != null) && player.destroyItemByItemId(ItemProcessType.FEE, 1419, 1, player.getTarget(), false)) + { + player.setSp(player.getSp() - 350000); + final SystemMessage sp = new SystemMessage(SystemMessageId.YOUR_SP_HAS_DECREASED_BY_S1); + sp.addInt(350000); + player.sendPacket(sp); + final SystemMessage sm = new SystemMessage(SystemMessageId.S1_HAS_DISAPPEARED); + sm.addItemName(1419); + player.sendPacket(sm); + increaseClanLevel = true; + } + break; + } + case 3: + { + // Upgrade to 4 (itemId 3874 = Alliance Manifesto). + if ((player.getSp() >= 1000000) && (player.getInventory().getItemByItemId(3874) != null) && player.destroyItemByItemId(ItemProcessType.FEE, 3874, 1, player.getTarget(), false)) + { + player.setSp(player.getSp() - 1000000); + final SystemMessage sp = new SystemMessage(SystemMessageId.YOUR_SP_HAS_DECREASED_BY_S1); + sp.addInt(1000000); + player.sendPacket(sp); + final SystemMessage sm = new SystemMessage(SystemMessageId.S1_HAS_DISAPPEARED); + sm.addItemName(3874); + player.sendPacket(sm); + increaseClanLevel = true; + } + break; + } + case 4: + { + // Upgrade to 5 (itemId 3870 = Seal of Aspiration). + if ((player.getSp() >= 2500000) && (player.getInventory().getItemByItemId(3870) != null) && player.destroyItemByItemId(ItemProcessType.FEE, 3870, 1, player.getTarget(), false)) + { + player.setSp(player.getSp() - 2500000); + final SystemMessage sp = new SystemMessage(SystemMessageId.YOUR_SP_HAS_DECREASED_BY_S1); + sp.addInt(2500000); + player.sendPacket(sp); + final SystemMessage sm = new SystemMessage(SystemMessageId.S1_HAS_DISAPPEARED); + sm.addItemName(3870); + player.sendPacket(sm); + increaseClanLevel = true; + } + break; + } + case 5: + { + // Upgrade to 6 + if ((_reputationScore >= FeatureConfig.CLAN_LEVEL_6_COST) && (_members.size() >= FeatureConfig.CLAN_LEVEL_6_REQUIREMENT)) + { + setReputationScore(_reputationScore - FeatureConfig.CLAN_LEVEL_6_COST); + final SystemMessage cr = new SystemMessage(SystemMessageId.S1_POINTS_HAVE_BEEN_DEDUCTED_FROM_THE_CLAN_S_REPUTATION_SCORE); + cr.addInt(FeatureConfig.CLAN_LEVEL_6_COST); + player.sendPacket(cr); + increaseClanLevel = true; + } + break; + } + case 6: + { + // Upgrade to 7 + if ((_reputationScore >= FeatureConfig.CLAN_LEVEL_7_COST) && (_members.size() >= FeatureConfig.CLAN_LEVEL_7_REQUIREMENT)) + { + setReputationScore(_reputationScore - FeatureConfig.CLAN_LEVEL_7_COST); + final SystemMessage cr = new SystemMessage(SystemMessageId.S1_POINTS_HAVE_BEEN_DEDUCTED_FROM_THE_CLAN_S_REPUTATION_SCORE); + cr.addInt(FeatureConfig.CLAN_LEVEL_7_COST); + player.sendPacket(cr); + increaseClanLevel = true; + } + break; + } + case 7: + { + // Upgrade to 8 + if ((_reputationScore >= FeatureConfig.CLAN_LEVEL_8_COST) && (_members.size() >= FeatureConfig.CLAN_LEVEL_8_REQUIREMENT)) + { + setReputationScore(_reputationScore - FeatureConfig.CLAN_LEVEL_8_COST); + final SystemMessage cr = new SystemMessage(SystemMessageId.S1_POINTS_HAVE_BEEN_DEDUCTED_FROM_THE_CLAN_S_REPUTATION_SCORE); + cr.addInt(FeatureConfig.CLAN_LEVEL_8_COST); + player.sendPacket(cr); + increaseClanLevel = true; + } + break; + } + default: + { + return false; + } + } + + if (!increaseClanLevel) + { + player.sendPacket(SystemMessageId.THE_CONDITIONS_NECESSARY_TO_INCREASE_THE_CLAN_S_LEVEL_HAVE_NOT_BEEN_MET); + return false; + } + + // the player should know that he has less sp now :p + player.updateUserInfo(); + + player.sendItemList(false); + changeLevel(_level + 1); + + // Notify to scripts. + if (EventDispatcher.getInstance().hasListener(EventType.ON_PLAYER_CLAN_LEVELUP)) + { + EventDispatcher.getInstance().notifyEventAsync(new OnPlayerClanLvlUp(player, this)); + } + + return true; + } + + public void changeLevel(int level) + { + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement("UPDATE clan_data SET clan_level = ? WHERE clan_id = ?")) + { + ps.setInt(1, level); + ps.setInt(2, _clanId); + ps.execute(); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "could not increase clan level:" + e.getMessage(), e); + } + + setLevel(level); + setRank(ClanTable.getInstance().getClanRank(this)); + + if (_leader.isOnline()) + { + final Player leader = _leader.getPlayer(); + if (level > 4) + { + SiegeManager.getInstance().addSiegeSkills(leader); + leader.sendPacket(SystemMessageId.NOW_THAT_YOUR_CLAN_LEVEL_IS_ABOVE_LEVEL_5_IT_CAN_ACCUMULATE_CLAN_REPUTATION_POINTS); + } + else if (level < 5) + { + SiegeManager.getInstance().removeSiegeSkills(leader); + } + } + + // Notify all the members about it. + broadcastToOnlineMembers(new SystemMessage(SystemMessageId.YOUR_CLAN_S_SKILL_LEVEL_HAS_INCREASED)); + broadcastToOnlineMembers(new PledgeShowInfoUpdate(this)); + } + + /** + * Change the clan crest. If crest id is 0, crest is removed. New crest id is saved to database. + * @param crestId if 0, crest is removed, else new crest id is set and saved to database + */ + public void changeClanCrest(int crestId) + { + if (_crestId != 0) + { + CrestTable.getInstance().removeCrest(getCrestId()); + } + + setCrestId(crestId); + + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement("UPDATE clan_data SET crest_id = ? WHERE clan_id = ?")) + { + ps.setInt(1, crestId); + ps.setInt(2, _clanId); + ps.executeUpdate(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "Could not update crest for clan " + _name + " [" + _clanId + "] : " + e.getMessage(), e); + } + + for (Player member : getOnlineMembers(0)) + { + member.broadcastUserInfo(); + } + } + + /** + * Change the ally crest. If crest id is 0, crest is removed. New crest id is saved to database. + * @param crestId if 0, crest is removed, else new crest id is set and saved to database + * @param onlyThisClan + */ + public void changeAllyCrest(int crestId, boolean onlyThisClan) + { + String sqlStatement = "UPDATE clan_data SET ally_crest_id = ? WHERE clan_id = ?"; + int allyId = _clanId; + if (!onlyThisClan) + { + if (_allyCrestId != 0) + { + CrestTable.getInstance().removeCrest(getAllyCrestId()); + } + + sqlStatement = "UPDATE clan_data SET ally_crest_id = ? WHERE ally_id = ?"; + allyId = _allyId; + } + + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement(sqlStatement)) + { + ps.setInt(1, crestId); + ps.setInt(2, allyId); + ps.executeUpdate(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "Could not update ally crest for ally/clan id " + allyId + " : " + e.getMessage(), e); + } + + if (onlyThisClan) + { + setAllyCrestId(crestId); + for (Player member : getOnlineMembers(0)) + { + member.broadcastUserInfo(); + } + } + else + { + for (Clan clan : ClanTable.getInstance().getClanAllies(getAllyId())) + { + clan.setAllyCrestId(crestId); + for (Player member : clan.getOnlineMembers(0)) + { + member.broadcastUserInfo(); + } + } + } + } + + /** + * Change the large crest. If crest id is 0, crest is removed. New crest id is saved to database. + * @param crestId if 0, crest is removed, else new crest id is set and saved to database + */ + public void changeLargeCrest(int crestId) + { + if (_crestLargeId != 0) + { + CrestTable.getInstance().removeCrest(getCrestLargeId()); + } + + setCrestLargeId(crestId); + + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement("UPDATE clan_data SET crest_large_id = ? WHERE clan_id = ?")) + { + ps.setInt(1, crestId); + ps.setInt(2, _clanId); + ps.executeUpdate(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "Could not update large crest for clan " + _name + " [" + _clanId + "] : " + e.getMessage(), e); + } + + for (Player member : getOnlineMembers(0)) + { + member.broadcastUserInfo(); + } + } + + /** + * Check if this clan can learn the skill for the given skill ID, level. + * @param skillId + * @param skillLevel + * @return {@code true} if skill can be learned. + */ + public boolean isLearnableSubSkill(int skillId, int skillLevel) + { + Skill current = _subPledgeSkills.get(skillId); + + // is next level? + if ((current != null) && ((current.getLevel() + 1) == skillLevel)) + { + return true; + } + + // is first level? + if ((current == null) && (skillLevel == 1)) + { + return true; + } + + // other sub-pledges + for (SubPledge subunit : _subPledges.values()) + { + // disable academy + if (subunit.getId() == -1) + { + continue; + } + + current = subunit.getSkill(skillId); + + // is next level? + if ((current != null) && ((current.getLevel() + 1) == skillLevel)) + { + return true; + } + + // is first level? + if ((current == null) && (skillLevel == 1)) + { + return true; + } + } + + return false; + } + + public boolean isLearnableSubPledgeSkill(Skill skill, int subType) + { + // academy + if (subType == -1) + { + return false; + } + + final int id = skill.getId(); + Skill current; + if (subType == 0) + { + current = _subPledgeSkills.get(id); + } + else + { + current = _subPledges.get(subType).getSkill(id); + } + + // is next level? + if ((current != null) && ((current.getLevel() + 1) == skill.getLevel())) + { + return true; + } + + // is first level? + if ((current == null) && (skill.getLevel() == 1)) + { + return true; + } + + return false; + } + + public void setNewLeaderId(int objectId, boolean storeInDb) + { + _newLeaderId = objectId; + if (storeInDb) + { + updateClanInDB(); + } + } + + public int getNewLeaderId() + { + return _newLeaderId; + } + + public Player getNewLeader() + { + return World.getPlayer(_newLeaderId); + } + + public String getNewLeaderName() + { + return CharInfoTable.getInstance().getNameById(_newLeaderId); + } + + public int getSiegeKills() + { + return _siegeKills.get(); + } + + public int getSiegeDeaths() + { + return _siegeDeaths.get(); + } + + public int addSiegeKill() + { + return _siegeKills.incrementAndGet(); + } + + public int addSiegeDeath() + { + return _siegeDeaths.incrementAndGet(); + } + + public void clearSiegeKills() + { + _siegeKills.set(0); + } + + public void clearSiegeDeaths() + { + _siegeDeaths.set(0); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/clan/ClanAccess.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/clan/ClanAccess.java new file mode 100644 index 0000000..c56acd2 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/clan/ClanAccess.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.clan; + +/** + * @author Mobius + */ +public enum ClanAccess +{ + NONE, + INVITE_MEMBER, + ASSIGN_TITLE, + ACCESS_WAREHOUSE, + MODIFY_RANKS, + WAR_DECLARATION, + REMOVE_MEMBER, + CHANGE_CREST, + DISMISS_MENTEE, + MEMBER_FAME, + ACCESS_AIRSHIP, + HALL_OPEN_DOOR, + HALL_FUNCTIONS, + HALL_AUCTION, + HALL_BANISH, + HALL_MANAGE_FUNCTIONS, + CASTLE_OPEN_DOOR, + CASTLE_MANOR, + CASTLE_SIEGE, + CASTLE_FUNCTIONS, + CASTLE_BANISH, + CASTLE_VAULT, + CASTLE_MERCENARIES, + CASTLE_MANAGE_FUNCTIONS; + + public int getMask() + { + return 1 << ordinal(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/clan/ClanInfo.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/clan/ClanInfo.java new file mode 100644 index 0000000..2087d0f --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/clan/ClanInfo.java @@ -0,0 +1,49 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.clan; + +/** + * @author UnAfraid + */ +public class ClanInfo +{ + private final Clan _clan; + private final int _total; + private final int _online; + + public ClanInfo(Clan clan) + { + _clan = clan; + _total = clan.getMembersCount(); + _online = clan.getOnlineMembersCount(); + } + + public Clan getClan() + { + return _clan; + } + + public int getTotal() + { + return _total; + } + + public int getOnline() + { + return _online; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/clan/ClanMember.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/clan/ClanMember.java new file mode 100644 index 0000000..c6a9991 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/clan/ClanMember.java @@ -0,0 +1,817 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.clan; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.commons.database.DatabaseFactory; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.managers.SiegeManager; + +/** + * This class holds the clan members data. + */ +public class ClanMember +{ + private static final Logger LOGGER = Logger.getLogger(ClanMember.class.getName()); + + private final Clan _clan; + private int _objectId; + private String _name; + private String _title; + private int _powerGrade; + private int _level; + private int _classId; + private boolean _sex; + private int _raceOrdinal; + private Player _player; + private int _pledgeType; + private int _apprentice; + private int _sponsor; + + /** + * Used to restore a clan member from the database. + * @param clan the clan where the clan member belongs. + * @param clanMember the clan member result set + * @throws SQLException if the columnLabel is not valid or a database error occurs + */ + public ClanMember(Clan clan, ResultSet clanMember) throws SQLException + { + if (clan == null) + { + throw new IllegalArgumentException("Cannot create a Clan Member with a null clan."); + } + + _clan = clan; + _name = clanMember.getString("char_name"); + _level = clanMember.getInt("level"); + _classId = clanMember.getInt("classid"); + _objectId = clanMember.getInt("charId"); + _pledgeType = clanMember.getInt("subpledge"); + _title = clanMember.getString("title"); + _powerGrade = clanMember.getInt("power_grade"); + _apprentice = clanMember.getInt("apprentice"); + _sponsor = clanMember.getInt("sponsor"); + _sex = clanMember.getInt("sex") != 0; + _raceOrdinal = clanMember.getInt("race"); + } + + /** + * Creates a clan member from a player instance. + * @param clan the clan where the player belongs + * @param player the player from which the clan member will be created + */ + public ClanMember(Clan clan, Player player) + { + if (clan == null) + { + throw new IllegalArgumentException("Cannot create a Clan Member if player has a null clan."); + } + + _player = player; + _clan = clan; + _name = player.getName(); + _level = player.getLevel(); + _classId = player.getPlayerClass().getId(); + _objectId = player.getObjectId(); + _pledgeType = player.getPledgeType(); + _powerGrade = player.getPowerGrade(); + _title = player.getTitle(); + _sponsor = 0; + _apprentice = 0; + _sex = player.getAppearance().isFemale(); + _raceOrdinal = player.getRace().ordinal(); + } + + /** + * Sets the player instance. + * @param player the new player instance + */ + public void setPlayer(Player player) + { + if ((player == null) && (_player != null)) + { + // This is here to keep the data when the player logs off. + _name = _player.getName(); + _level = _player.getLevel(); + _classId = _player.getPlayerClass().getId(); + _objectId = _player.getObjectId(); + _powerGrade = _player.getPowerGrade(); + _pledgeType = _player.getPledgeType(); + _title = _player.getTitle(); + _apprentice = _player.getApprentice(); + _sponsor = _player.getSponsor(); + _sex = _player.getAppearance().isFemale(); + _raceOrdinal = _player.getRace().ordinal(); + } + + if (player != null) + { + _clan.addSkillEffects(player); + if ((_clan.getLevel() > 3) && player.isClanLeader()) + { + SiegeManager.getInstance().addSiegeSkills(player); + } + + if (player.isClanLeader()) + { + _clan.setLeader(this); + } + } + + _player = player; + } + + /** + * Gets the player instance. + * @return the player instance + */ + public Player getPlayer() + { + return _player; + } + + /** + * Verifies if the clan member is online. + * @return {@code true} if is online + */ + public boolean isOnline() + { + return (_player != null) && _player.isOnline() && !_player.isInOfflineMode(); + } + + /** + * Gets the class id. + * @return the classId + */ + public int getClassId() + { + return _player != null ? _player.getPlayerClass().getId() : _classId; + } + + /** + * Gets the level. + * @return the level + */ + public int getLevel() + { + return _player != null ? _player.getLevel() : _level; + } + + /** + * Gets the name. + * @return the name + */ + public String getName() + { + return _player != null ? _player.getName() : _name; + } + + /** + * Gets the object id. + * @return Returns the objectId. + */ + public int getObjectId() + { + return _player != null ? _player.getObjectId() : _objectId; + } + + /** + * Gets the title. + * @return the title + */ + public String getTitle() + { + return _player != null ? _player.getTitle() : _title; + } + + /** + * Gets the pledge type. + * @return the pledge type + */ + public int getPledgeType() + { + return _player != null ? _player.getPledgeType() : _pledgeType; + } + + /** + * Sets the pledge type. + * @param pledgeType the new pledge type + */ + public void setPledgeType(int pledgeType) + { + _pledgeType = pledgeType; + if (_player != null) + { + _player.setPledgeType(pledgeType); + } + else + { + // Db save if char not logged in. + updatePledgeType(); + } + } + + /** + * Update pledge type. + */ + public void updatePledgeType() + { + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement("UPDATE characters SET subpledge=? WHERE charId=?")) + { + ps.setLong(1, _pledgeType); + ps.setInt(2, getObjectId()); + ps.execute(); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Could not update pledge type: " + e.getMessage(), e); + } + } + + /** + * Gets the power grade. + * @return the power grade + */ + public int getPowerGrade() + { + return _player != null ? _player.getPowerGrade() : _powerGrade; + } + + /** + * Sets the power grade. + * @param powerGrade the new power grade + */ + public void setPowerGrade(int powerGrade) + { + _powerGrade = powerGrade; + if (_player != null) + { + _player.setPowerGrade(powerGrade); + } + else + { + // Db save if char not logged in. + updatePowerGrade(); + } + } + + /** + * Update the characters table of the database with power grade. + */ + public void updatePowerGrade() + { + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement("UPDATE characters SET power_grade=? WHERE charId=?")) + { + ps.setLong(1, _powerGrade); + ps.setInt(2, getObjectId()); + ps.execute(); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Could not update power _grade: " + e.getMessage(), e); + } + } + + /** + * Sets the apprentice and sponsor. + * @param apprenticeID the apprentice id + * @param sponsorID the sponsor id + */ + public void setApprenticeAndSponsor(int apprenticeID, int sponsorID) + { + _apprentice = apprenticeID; + _sponsor = sponsorID; + } + + /** + * Gets the player's race ordinal. + * @return the race ordinal + */ + public int getRaceOrdinal() + { + return _player != null ? _player.getRace().ordinal() : _raceOrdinal; + } + + /** + * Gets the player's sex. + * @return true if female + */ + public boolean getSex() + { + return _player != null ? _player.getAppearance().isFemale() : _sex; + } + + /** + * Gets the sponsor. + * @return the sponsor + */ + public int getSponsor() + { + return _player != null ? _player.getSponsor() : _sponsor; + } + + /** + * Gets the apprentice. + * @return the apprentice + */ + public int getApprentice() + { + return _player != null ? _player.getApprentice() : _apprentice; + } + + /** + * Gets the apprentice or sponsor name. + * @return the apprentice or sponsor name + */ + public String getApprenticeOrSponsorName() + { + if (_player != null) + { + _apprentice = _player.getApprentice(); + _sponsor = _player.getSponsor(); + } + + if (_apprentice != 0) + { + final ClanMember apprentice = _clan.getClanMember(_apprentice); + if (apprentice != null) + { + return apprentice.getName(); + } + + return "Error"; + } + + if (_sponsor != 0) + { + final ClanMember sponsor = _clan.getClanMember(_sponsor); + if (sponsor != null) + { + return sponsor.getName(); + } + + return "Error"; + } + + return ""; + } + + /** + * Gets the clan. + * @return the clan + */ + public Clan getClan() + { + return _clan; + } + + /** + * Calculate pledge class. + * @param player the player + * @return the int + */ + public static int calculatePledgeClass(Player player) + { + int pledgeClass = 0; + if (player == null) + { + return pledgeClass; + } + + final Clan clan = player.getClan(); + if (clan != null) + { + switch (clan.getLevel()) + { + case 4: + { + if (player.isClanLeader()) + { + pledgeClass = 3; + } + break; + } + case 5: + { + if (player.isClanLeader()) + { + pledgeClass = 4; + } + else + { + pledgeClass = 2; + } + break; + } + case 6: + { + switch (player.getPledgeType()) + { + case -1: + { + pledgeClass = 1; + break; + } + case 100: + case 200: + { + pledgeClass = 2; + break; + } + case 0: + { + if (player.isClanLeader()) + { + pledgeClass = 5; + } + else + { + switch (clan.getLeaderSubPledge(player.getObjectId())) + { + case 100: + case 200: + { + pledgeClass = 4; + break; + } + case -1: + default: + { + pledgeClass = 3; + break; + } + } + } + break; + } + } + break; + } + case 7: + { + switch (player.getPledgeType()) + { + case -1: + { + pledgeClass = 1; + break; + } + case 100: + case 200: + { + pledgeClass = 3; + break; + } + case 1001: + case 1002: + case 2001: + case 2002: + { + pledgeClass = 2; + break; + } + case 0: + { + if (player.isClanLeader()) + { + pledgeClass = 7; + } + else + { + switch (clan.getLeaderSubPledge(player.getObjectId())) + { + case 100: + case 200: + { + pledgeClass = 6; + break; + } + case 1001: + case 1002: + case 2001: + case 2002: + { + pledgeClass = 5; + break; + } + case -1: + default: + { + pledgeClass = 4; + break; + } + } + } + break; + } + } + break; + } + case 8: + { + switch (player.getPledgeType()) + { + case -1: + { + pledgeClass = 1; + break; + } + case 100: + case 200: + { + pledgeClass = 4; + break; + } + case 1001: + case 1002: + case 2001: + case 2002: + { + pledgeClass = 3; + break; + } + case 0: + { + if (player.isClanLeader()) + { + pledgeClass = 8; + } + else + { + switch (clan.getLeaderSubPledge(player.getObjectId())) + { + case 100: + case 200: + { + pledgeClass = 7; + break; + } + case 1001: + case 1002: + case 2001: + case 2002: + { + pledgeClass = 6; + break; + } + case -1: + default: + { + pledgeClass = 5; + break; + } + } + } + break; + } + } + break; + } + case 9: + { + switch (player.getPledgeType()) + { + case -1: + { + pledgeClass = 1; + break; + } + case 100: + case 200: + { + pledgeClass = 5; + break; + } + case 1001: + case 1002: + case 2001: + case 2002: + { + pledgeClass = 4; + break; + } + case 0: + { + if (player.isClanLeader()) + { + pledgeClass = 9; + } + else + { + switch (clan.getLeaderSubPledge(player.getObjectId())) + { + case 100: + case 200: + { + pledgeClass = 8; + break; + } + case 1001: + case 1002: + case 2001: + case 2002: + { + pledgeClass = 7; + break; + } + case -1: + default: + { + pledgeClass = 6; + break; + } + } + } + break; + } + } + break; + } + case 10: + { + switch (player.getPledgeType()) + { + case -1: + { + pledgeClass = 1; + break; + } + case 100: + case 200: + { + pledgeClass = 6; + break; + } + case 1001: + case 1002: + case 2001: + case 2002: + { + pledgeClass = 5; + break; + } + case 0: + { + if (player.isClanLeader()) + { + pledgeClass = 10; + } + else + { + switch (clan.getLeaderSubPledge(player.getObjectId())) + { + case 100: + case 200: + { + pledgeClass = 9; + break; + } + case 1001: + case 1002: + case 2001: + case 2002: + { + pledgeClass = 8; + break; + } + case -1: + default: + { + pledgeClass = 7; + break; + } + } + } + break; + } + } + break; + } + case 11: + { + switch (player.getPledgeType()) + { + case -1: + { + pledgeClass = 1; + break; + } + case 100: + case 200: + { + pledgeClass = 7; + break; + } + case 1001: + case 1002: + case 2001: + case 2002: + { + pledgeClass = 6; + break; + } + case 0: + { + if (player.isClanLeader()) + { + pledgeClass = 11; + } + else + { + switch (clan.getLeaderSubPledge(player.getObjectId())) + { + case 100: + case 200: + { + pledgeClass = 10; + break; + } + case 1001: + case 1002: + case 2001: + case 2002: + { + pledgeClass = 9; + break; + } + case -1: + default: + { + pledgeClass = 8; + break; + } + } + } + break; + } + } + break; + } + default: + { + pledgeClass = 1; + break; + } + } + } + + if (player.isNoble() && (pledgeClass < 5)) + { + pledgeClass = 5; + } + + if (player.isHero() && (pledgeClass < 8)) + { + pledgeClass = 8; + } + + return pledgeClass; + } + + /** + * Save apprentice and sponsor. + * @param apprentice the apprentice + * @param sponsor the sponsor + */ + public void saveApprenticeAndSponsor(int apprentice, int sponsor) + { + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement("UPDATE characters SET apprentice=?,sponsor=? WHERE charId=?")) + { + ps.setInt(1, apprentice); + ps.setInt(2, sponsor); + ps.setInt(3, getObjectId()); + ps.execute(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "Could not save apprentice/sponsor: " + e.getMessage(), e); + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/clan/ClanPrivileges.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/clan/ClanPrivileges.java new file mode 100644 index 0000000..950cd69 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/clan/ClanPrivileges.java @@ -0,0 +1,182 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.clan; + +/** + * The {@link ClanPrivileges} class manages a set of {@link ClanAccess} for clan members using a mask representation. + * @author Mobius + */ +public class ClanPrivileges +{ + private int _mask; + + /** + * Initializes a new instance of ClanPrivileges with all privileges disabled. + */ + public ClanPrivileges() + { + _mask = 0; + } + + /** + * Initializes a new instance of ClanPrivileges with the specified {@link ClanAccess} mask. + * @param mask The mask representing the privileges to be set. + */ + public ClanPrivileges(int mask) + { + _mask = mask; + } + + /** + * Returns the current {@link ClanAccess} mask. + * @return The integer mask representing the currently enabled privileges. + */ + public int getMask() + { + return _mask; + } + + /** + * Sets the {@link ClanAccess} mask to a specified value. + * @param mask The integer mask representing the privileges to set. + */ + public void setMask(int mask) + { + _mask = mask; + } + + /** + * Disables all privileges by setting the mask to 0. + */ + public void disableAll() + { + _mask = 0; + } + + /** + * Enables all possible privileges by setting the mask to the complete mask. + */ + public void enableAll() + { + _mask = getCompleteMask(); + } + + /** + * Calculates and returns a mask with all privileges enabled. + * @return The integer mask with all bits corresponding to {@link ClanAccess} set to 1. + */ + public static int getCompleteMask() + { + int mask = 0; + for (ClanAccess access : ClanAccess.values()) + { + mask |= 1 << access.ordinal(); + } + + return mask; + } + + /** + * Sets the specified privileges, disabling any previously set privileges. + * @param privileges The array of {@link ClanAccess} to enable. + */ + public void setPrivileges(ClanAccess... privileges) + { + disableAll(); + for (ClanAccess access : privileges) + { + _mask |= 1 << access.ordinal(); + } + } + + /** + * Sets the specified minimum privilege and additional privileges, disabling any others. + * @param minimum The minimum {@link ClanAccess} to enable. + * @param privileges Additional {@link ClanAccess} to enable. + */ + public void setMinimumPrivileges(ClanAccess minimum, ClanAccess... privileges) + { + disableAll(); + addMinimumPrivileges(minimum, privileges); + } + + /** + * Adds the specified minimum privilege and additional privileges to the current mask. + * @param minimum The minimum {@link ClanAccess} to add. + * @param privileges Additional {@link ClanAccess} to add. + */ + public void addMinimumPrivileges(ClanAccess minimum, ClanAccess... privileges) + { + _mask |= 1 << minimum.ordinal(); + for (ClanAccess access : privileges) + { + _mask |= 1 << access.ordinal(); + } + } + + /** + * Removes the specified minimum privilege and additional privileges from the current mask. + * @param minimum The minimum {@link ClanAccess} to remove. + * @param privileges Additional {@link ClanAccess} to remove. + */ + public void removeMinimumPrivileges(ClanAccess minimum, ClanAccess... privileges) + { + _mask &= ~(1 << minimum.ordinal()); + for (ClanAccess access : privileges) + { + _mask &= ~(1 << access.ordinal()); + } + } + + /** + * Checks if the specified minimum privilege and additional privileges are enabled in the mask. + * @param minimum The minimum {@link ClanAccess} to check. + * @param privileges Additional {@link ClanAccess} to check. + * @return true if all specified privileges are enabled; false otherwise. + */ + public boolean hasMinimumPrivileges(ClanAccess minimum, ClanAccess... privileges) + { + if ((_mask & (1 << minimum.ordinal())) == 0) + { + return false; + } + + for (ClanAccess access : privileges) + { + if ((_mask & (1 << access.ordinal())) == 0) + { + return false; + } + } + + return true; + } + + /** + * Creates a clone of this ClanPrivileges instance, preserving the current mask. + * @return A new instance of ClanPrivileges with the same {@link ClanAccess} mask. + */ + @Override + public ClanPrivileges clone() + { + return new ClanPrivileges(_mask); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/clan/Crest.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/clan/Crest.java new file mode 100644 index 0000000..b36efbc --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/clan/Crest.java @@ -0,0 +1,51 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.clan; + +import org.l2jmobius.gameserver.entity.clan.enums.CrestType; + +/** + * @author NosBit + */ +public class Crest +{ + private final int _id; + private final byte[] _data; + private final CrestType _type; + + public Crest(int id, byte[] data, CrestType type) + { + _id = id; + _data = data; + _type = type; + } + + public int getId() + { + return _id; + } + + public byte[] getData() + { + return _data; + } + + public CrestType getType() + { + return _type; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/clan/enums/CrestType.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/clan/enums/CrestType.java new file mode 100644 index 0000000..21d31d7 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/clan/enums/CrestType.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.clan.enums; + +public enum CrestType +{ + PLEDGE(1), + PLEDGE_LARGE(2), + ALLY(3); + + private final int _id; + + CrestType(int id) + { + _id = id; + } + + public int getId() + { + return _id; + } + + public static CrestType getById(int id) + { + for (CrestType crestType : values()) + { + if (crestType.getId() == id) + { + return crestType; + } + } + + return null; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/cubic/CubicSelectionMode.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/cubic/CubicSelectionMode.java new file mode 100644 index 0000000..26c64a8 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/cubic/CubicSelectionMode.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.cubic; + +/** + * Cubic skill selection mode loaded from XML. + * @author BazookaRpm + */ +public enum CubicSelectionMode +{ + RANDOM, + WEIGHTED, + ORDERED +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/cubic/CubicSkill.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/cubic/CubicSkill.java new file mode 100644 index 0000000..2157b91 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/cubic/CubicSkill.java @@ -0,0 +1,119 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.cubic; + +import java.util.ArrayList; +import java.util.List; + +import org.l2jmobius.gameserver.entity.WorldObject; +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.instance.Cubic; +import org.l2jmobius.gameserver.entity.cubic.conditions.ICubicCondition; +import org.l2jmobius.gameserver.mechanics.skill.BuffInfo; +import org.l2jmobius.gameserver.mechanics.skill.holders.SkillHolder; +import org.l2jmobius.gameserver.util.StatSet; + +/** + * Cubic skill data loaded from XML.
        + * Stores target routing and optional execution conditions for a cubic skill. + * @author UnAfraid + */ +public class CubicSkill extends SkillHolder implements ICubicConditionHolder +{ + private final int _triggerRate; + private final CubicTargetType _targetType; + private final boolean _targetDebuff; + private final boolean _priority; + private final List _conditions = new ArrayList<>(); + + public CubicSkill(StatSet set) + { + super(set.getInt("id"), set.getInt("level")); + _triggerRate = set.getInt("triggerRate", 100); + _targetType = set.getEnum("target", CubicTargetType.class, CubicTargetType.TARGET); + _targetDebuff = set.getBoolean("targetDebuff", false); + _priority = set.getBoolean("priority", false); + } + + public int getTriggerRate() + { + return _triggerRate; + } + + public CubicTargetType getTargetType() + { + return _targetType; + } + + public boolean isPriority() + { + return _priority; + } + + @Override + public boolean validateConditions(Cubic cubic, Creature owner, WorldObject target) + { + if (target == null) + { + return false; + } + + if (_targetDebuff && !hasDispellableDebuff(target)) + { + return false; + } + + if (_conditions.isEmpty()) + { + return true; + } + + for (ICubicCondition condition : _conditions) + { + if (!condition.test(cubic, owner, target)) + { + return false; + } + } + + return true; + } + + private boolean hasDispellableDebuff(WorldObject target) + { + if (!target.isCreature()) + { + return false; + } + + for (BuffInfo info : target.asCreature().getEffectList().getDebuffs()) + { + if (info.getSkill().canBeDispeled()) + { + return true; + } + } + + return false; + } + + @Override + public void addCondition(ICubicCondition condition) + { + _conditions.add(condition); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/cubic/CubicTargetType.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/cubic/CubicTargetType.java new file mode 100644 index 0000000..227ae9d --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/cubic/CubicTargetType.java @@ -0,0 +1,29 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.cubic; + +/** + * Cubic target selection type. + * @author UnAfraid + */ +public enum CubicTargetType +{ + TARGET, + BY_SKILL, + HEAL, + MASTER; +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/cubic/ICubicConditionHolder.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/cubic/ICubicConditionHolder.java new file mode 100644 index 0000000..51a615a --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/cubic/ICubicConditionHolder.java @@ -0,0 +1,33 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.cubic; + +import org.l2jmobius.gameserver.entity.WorldObject; +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.instance.Cubic; +import org.l2jmobius.gameserver.entity.cubic.conditions.ICubicCondition; + +/** + * Cubic condition holder contract. + * @author UnAfraid + */ +public interface ICubicConditionHolder +{ + boolean validateConditions(Cubic cubic, Creature owner, WorldObject target); + + void addCondition(ICubicCondition condition); +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/cubic/conditions/HealthCondition.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/cubic/conditions/HealthCondition.java new file mode 100644 index 0000000..c9f0ee2 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/cubic/conditions/HealthCondition.java @@ -0,0 +1,49 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.cubic.conditions; + +import org.l2jmobius.gameserver.entity.WorldObject; +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.instance.Cubic; + +/** + * Validates target HP percentage inside a configured range. + * @author UnAfraid + */ +public class HealthCondition implements ICubicCondition +{ + private final int _min; + private final int _max; + + public HealthCondition(int min, int max) + { + _min = min; + _max = max; + } + + @Override + public boolean test(Cubic cubic, Creature owner, WorldObject target) + { + if ((target == null) || (!target.isCreature() && !target.isDoor())) + { + return false; + } + + final double hpPercent = (target.isDoor() ? target.asDoor() : target.asCreature()).getCurrentHpPercent(); + return (hpPercent > _min) && (hpPercent < _max); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/cubic/conditions/HpCondition.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/cubic/conditions/HpCondition.java new file mode 100644 index 0000000..dc6947a --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/cubic/conditions/HpCondition.java @@ -0,0 +1,67 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.cubic.conditions; + +import org.l2jmobius.gameserver.entity.WorldObject; +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.instance.Cubic; + +/** + * Validates target HP percentage against a configured threshold. + * @author UnAfraid + */ +public class HpCondition implements ICubicCondition +{ + private final HpConditionType _type; + private final int _hpPercent; + + public HpCondition(HpConditionType type, int hpPercent) + { + _type = type; + _hpPercent = hpPercent; + } + + @Override + public boolean test(Cubic cubic, Creature owner, WorldObject target) + { + if ((target == null) || (!target.isCreature() && !target.isDoor())) + { + return false; + } + + final double hpPercent = (target.isDoor() ? target.asDoor() : target.asCreature()).getCurrentHpPercent(); + switch (_type) + { + case GREATER: + { + return hpPercent > _hpPercent; + } + case LESSER: + { + return hpPercent < _hpPercent; + } + } + + return false; + } + + public enum HpConditionType + { + GREATER, + LESSER; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/cubic/conditions/ICubicCondition.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/cubic/conditions/ICubicCondition.java new file mode 100644 index 0000000..ab62e7a --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/cubic/conditions/ICubicCondition.java @@ -0,0 +1,30 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.cubic.conditions; + +import org.l2jmobius.gameserver.entity.WorldObject; +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.instance.Cubic; + +/** + * Cubic condition contract. + * @author UnAfraid + */ +public interface ICubicCondition +{ + boolean test(Cubic cubic, Creature owner, WorldObject target); +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/cubic/conditions/RangeCondition.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/cubic/conditions/RangeCondition.java new file mode 100644 index 0000000..7facfa7 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/cubic/conditions/RangeCondition.java @@ -0,0 +1,41 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.cubic.conditions; + +import org.l2jmobius.gameserver.entity.WorldObject; +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.instance.Cubic; + +/** + * Validates distance between cubic owner and target. + * @author Sdw + */ +public class RangeCondition implements ICubicCondition +{ + private final int _range; + + public RangeCondition(int range) + { + _range = range; + } + + @Override + public boolean test(Cubic cubic, Creature owner, WorldObject target) + { + return (owner != null) && (target != null) && (owner.calculateDistance2D(target) <= _range); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/groups/AbstractPlayerGroup.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/groups/AbstractPlayerGroup.java new file mode 100644 index 0000000..3b5e5a6 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/groups/AbstractPlayerGroup.java @@ -0,0 +1,199 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.groups; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; + +import org.l2jmobius.commons.util.Rnd; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.holders.player.BlockList; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.serverpackets.CreatureSay; +import org.l2jmobius.gameserver.network.serverpackets.ServerPacket; +import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; + +/** + * @author Battlecruiser + */ +public abstract class AbstractPlayerGroup +{ + /** + * @return a list of all members of this group + */ + public abstract List getMembers(); + + /** + * @return a list of object IDs of the members of this group + */ + public List getMembersObjectId() + { + final List ids = new ArrayList<>(); + forEachMember(m -> + { + ids.add(m.getObjectId()); + return true; + }); + + return ids; + } + + /** + * @return the leader of this group + */ + public abstract Player getLeader(); + + /** + * Change the leader of this group to the specified player. + * @param leader the player to set as the new leader of this group + */ + public abstract void setLeader(Player leader); + + /** + * @return the leader's object ID + */ + public int getLeaderObjectId() + { + final Player leader = getLeader(); + if (leader == null) + { + return 0; + } + + return leader.getObjectId(); + } + + /** + * Check if a given player is the leader of this group. + * @param player the player to check + * @return {@code true} if the specified player is the leader of this group, {@code false} otherwise + */ + public boolean isLeader(Player player) + { + if (player == null) + { + return false; + } + + final Player leader = getLeader(); + if (leader == null) + { + return false; + } + + return leader.getObjectId() == player.getObjectId(); + } + + /** + * @return the count of all players in this group + */ + public int getMemberCount() + { + return getMembers().size(); + } + + /** + * @return the level of this group + */ + public abstract int getLevel(); + + /** + * Broadcast a packet to every member of this group. + * @param packet the packet to broadcast + */ + public void broadcastPacket(ServerPacket packet) + { + forEachMember(m -> + { + if (m != null) + { + m.sendPacket(packet); + } + + return true; + }); + } + + /** + * Broadcast a system message to this group. + * @param message the system message to broadcast + */ + public void broadcastMessage(SystemMessageId message) + { + broadcastPacket(new SystemMessage(message)); + } + + /** + * Broadcast a text message to this group. + * @param text to broadcast + */ + public void broadcastString(String text) + { + broadcastPacket(new SystemMessage(text)); + } + + public void broadcastCreatureSay(CreatureSay msg, Player broadcaster) + { + forEachMember(m -> + { + if ((m != null) && !BlockList.isBlocked(m, broadcaster)) + { + m.sendPacket(msg); + } + + return true; + }); + } + + /** + * Check if this group contains a given player. + * @param player the player to check + * @return {@code true} if this group contains the specified player, {@code false} otherwise + */ + public boolean containsPlayer(Player player) + { + return getMembers().contains(player); + } + + /** + * @return a random member of this group + */ + public Player getRandomPlayer() + { + return getMembers().get(Rnd.get(getMembers().size())); + } + + /** + * Iterates over the group and executes procedure on each member + * @param procedure the prodecure to be executed on each member.
        + * If executing the procedure on a member returns {@code true}, the loop continues to the next member, otherwise it breaks the loop + * @return {@code true} if the procedure executed correctly, {@code false} if the loop was broken prematurely + */ + public boolean forEachMember(Function procedure) + { + for (Player player : getMembers()) + { + if (!procedure.apply(player)) + { + return false; + } + } + + return true; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/groups/CommandChannel.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/groups/CommandChannel.java new file mode 100644 index 0000000..3765001 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/groups/CommandChannel.java @@ -0,0 +1,272 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.groups; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; + +import org.l2jmobius.gameserver.config.PlayerConfig; +import org.l2jmobius.gameserver.entity.WorldObject; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.serverpackets.ExCloseMPCC; +import org.l2jmobius.gameserver.network.serverpackets.ExOpenMPCC; +import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; + +/** + * This class serves as a container for command channels. + * @author chris_00 + */ +public class CommandChannel extends AbstractPlayerGroup +{ + private final Collection _parties = ConcurrentHashMap.newKeySet(); + private Player _commandLeader; + private int _channelLvl; + + /** + * Create a new command channel and add the leader's party to it. + * @param leader the leader of this command channel + */ + public CommandChannel(Player leader) + { + _commandLeader = leader; + final Party party = leader.getParty(); + _parties.add(party); + _channelLvl = party.getLevel(); + party.setCommandChannel(this); + party.broadcastMessage(SystemMessageId.THE_COMMAND_CHANNEL_HAS_BEEN_FORMED); + party.broadcastPacket(ExOpenMPCC.STATIC_PACKET); + } + + /** + * Add a party to this command channel. + * @param party the party to add + */ + public void addParty(Party party) + { + if (party == null) + { + return; + } + + // Update the CCinfo for existing players. + // broadcastPacket(new ExMPCCPartyInfoUpdate(party, 1)); + _parties.add(party); + if (party.getLevel() > _channelLvl) + { + _channelLvl = party.getLevel(); + } + + party.setCommandChannel(this); + party.broadcastPacket(new SystemMessage(SystemMessageId.YOU_HAVE_JOINED_THE_COMMAND_CHANNEL)); + party.broadcastPacket(ExOpenMPCC.STATIC_PACKET); + } + + /** + * Remove a party from this command channel. + * @param party the party to remove + */ + public void removeParty(Party party) + { + if (party == null) + { + return; + } + + _parties.remove(party); + _channelLvl = 0; + for (Party pty : _parties) + { + if (pty.getLevel() > _channelLvl) + { + _channelLvl = pty.getLevel(); + } + } + + party.setCommandChannel(null); + party.broadcastPacket(new ExCloseMPCC()); + if (_parties.size() < 2) + { + broadcastPacket(new SystemMessage(SystemMessageId.THE_COMMAND_CHANNEL_HAS_BEEN_DISBANDED)); + disbandChannel(); + } + // else + // { + // Update the CCinfo for existing players. + // broadcastPacket(new ExMPCCPartyInfoUpdate(party, 0)); + // } + } + + /** + * Disband this command channel. + */ + public void disbandChannel() + { + if (_parties != null) + { + for (Party party : _parties) + { + if (party != null) + { + removeParty(party); + } + } + + _parties.clear(); + } + } + + /** + * @return the total count of all members of this command channel + */ + @Override + public int getMemberCount() + { + int count = 0; + for (Party party : _parties) + { + if (party != null) + { + count += party.getMemberCount(); + } + } + + return count; + } + + /** + * @return a list of all parties in this command channel + */ + public Collection getParties() + { + return _parties; + } + + /** + * @return a list of all members in this command channel + */ + @Override + public List getMembers() + { + final List members = new ArrayList<>(); + for (Party party : _parties) + { + members.addAll(party.getMembers()); + } + + return members; + } + + /** + * @return the level of this command channel (equals the level of the highest-leveled character in this command channel) + */ + @Override + public int getLevel() + { + return _channelLvl; + } + + @Override + public void setLeader(Player leader) + { + _commandLeader = leader; + if (leader.getLevel() > _channelLvl) + { + _channelLvl = leader.getLevel(); + } + } + + /** + * @param obj + * @return true if proper condition for RaidWar + */ + public boolean meetRaidWarCondition(WorldObject obj) + { + if (!(obj.isCreature() && obj.asCreature().isRaid())) + { + return false; + } + + return (getMemberCount() >= PlayerConfig.LOOT_RAIDS_PRIVILEGE_CC_SIZE); + } + + /** + * @return the leader of this command channel + */ + @Override + public Player getLeader() + { + return _commandLeader; + } + + /** + * Check if a given player is in this command channel. + * @param player the player to check + * @return {@code true} if he does, {@code false} otherwise + */ + @Override + public boolean containsPlayer(Player player) + { + if ((_parties != null) && !_parties.isEmpty()) + { + for (Party party : _parties) + { + if (party.containsPlayer(player)) + { + return true; + } + } + } + + return false; + } + + /** + * Iterates over all command channel members without the need to allocate a new list + * @see org.l2jmobius.gameserver.entity.groups.AbstractPlayerGroup#forEachMember(Function) + */ + @Override + public boolean forEachMember(Function function) + { + if ((_parties != null) && !_parties.isEmpty()) + { + for (Party party : _parties) + { + if (!party.forEachMember(function)) + { + return false; + } + } + } + + return true; + } + + /** + * Check whether the leader of this command channel is the same as the leader of the specified command channel
        + * (which essentially means they're the same group). + * @param cc the other command channel to check against + * @return {@code true} if this command channel equals the specified command channel, {@code false} otherwise + */ + public boolean equals(CommandChannel cc) + { + return (cc != null) && (getLeaderObjectId() == cc.getLeaderObjectId()); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/groups/Party.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/groups/Party.java new file mode 100644 index 0000000..9238407 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/groups/Party.java @@ -0,0 +1,1062 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.groups; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.Future; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.commons.threads.ThreadPool; +import org.l2jmobius.commons.util.Rnd; +import org.l2jmobius.gameserver.config.PlayerConfig; +import org.l2jmobius.gameserver.config.RatesConfig; +import org.l2jmobius.gameserver.config.custom.ClassBalanceConfig; +import org.l2jmobius.gameserver.config.custom.PremiumSystemConfig; +import org.l2jmobius.gameserver.entity.actor.Attackable; +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.Summon; +import org.l2jmobius.gameserver.entity.groups.matching.PartyMatchRoomList; +import org.l2jmobius.gameserver.entity.item.enums.ItemProcessType; +import org.l2jmobius.gameserver.entity.item.holders.ItemHolder; +import org.l2jmobius.gameserver.entity.item.instance.Item; +import org.l2jmobius.gameserver.entity.itemcontainer.Inventory; +import org.l2jmobius.gameserver.managers.DuelManager; +import org.l2jmobius.gameserver.managers.ItemManager; +import org.l2jmobius.gameserver.managers.PcCafePointsManager; +import org.l2jmobius.gameserver.mechanics.sevensigns.DimensionalRift; +import org.l2jmobius.gameserver.mechanics.sevensigns.SevenSignsFestival; +import org.l2jmobius.gameserver.mechanics.stats.Stat; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.serverpackets.ExCloseMPCC; +import org.l2jmobius.gameserver.network.serverpackets.ExOpenMPCC; +import org.l2jmobius.gameserver.network.serverpackets.PartyMemberPosition; +import org.l2jmobius.gameserver.network.serverpackets.PartySmallWindowAdd; +import org.l2jmobius.gameserver.network.serverpackets.PartySmallWindowAll; +import org.l2jmobius.gameserver.network.serverpackets.PartySmallWindowDelete; +import org.l2jmobius.gameserver.network.serverpackets.PartySmallWindowDeleteAll; +import org.l2jmobius.gameserver.network.serverpackets.ServerPacket; +import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; +import org.l2jmobius.gameserver.taskmanagers.GameTimeTaskManager; +import org.l2jmobius.gameserver.util.LocationUtil; + +/** + * This class serves as a container for player parties. + * @author nuocnam + */ +public class Party extends AbstractPlayerGroup +{ + private static final Logger LOGGER = Logger.getLogger(Party.class.getName()); + + // @formatter:off + private static final double[] BONUS_EXP_SP = + { + 1.0, 1.10, 1.20, 1.30, 1.40, 1.50, 2.0, 2.10, 2.20 + }; + // @formatter:on + + private static final Duration PARTY_POSITION_BROADCAST_INTERVAL = Duration.ofSeconds(12); + + private final List _members = new CopyOnWriteArrayList<>(); + private boolean _pendingInvitation = false; + private long _pendingInviteTimeout; + private int _partyLvl = 0; + private PartyDistributionType _distributionType = PartyDistributionType.FINDERS_KEEPERS; + private int _itemLastLoot = 0; + private CommandChannel _commandChannel = null; + private DimensionalRift _dr; + private Future _positionBroadcastTask = null; + protected PartyMemberPosition _positionPacket; + private boolean _disbanding = false; + + /** + * Construct a new Party object with a single member - the leader. + * @param leader the leader of this party + * @param partyDistributionType the item distribution rule of this party + */ + public Party(Player leader, PartyDistributionType partyDistributionType) + { + _members.add(leader); + _partyLvl = leader.getLevel(); + _distributionType = partyDistributionType; + } + + /** + * Check if another player can start invitation process. + * @return {@code true} if this party waits for a response on an invitation, {@code false} otherwise + */ + public boolean getPendingInvitation() + { + return _pendingInvitation; + } + + /** + * Set invitation process flag and store time for expiration.
        + * Happens when a player joins party or declines to join. + * @param value the pending invitation state to set + */ + public void setPendingInvitation(boolean value) + { + _pendingInvitation = value; + _pendingInviteTimeout = GameTimeTaskManager.getInstance().getGameTicks() + (Player.REQUEST_TIMEOUT * GameTimeTaskManager.TICKS_PER_SECOND); + } + + /** + * Check if a player invitation request is expired. + * @return {@code true} if time is expired, {@code false} otherwise + * @see org.l2jmobius.gameserver.entity.actor.Player#isRequestExpired() + */ + public boolean isInvitationRequestExpired() + { + return _pendingInviteTimeout <= GameTimeTaskManager.getInstance().getGameTicks(); + } + + /** + * Get a random member from this party. + * @param itemId the ID of the item for which the member must have inventory space + * @param target the object of which the member must be within a certain range (must not be null) + * @return a random member from this party or {@code null} if none of the members have inventory space for the specified item + */ + private Player getCheckedRandomMember(int itemId, Creature target) + { + final List availableMembers = new ArrayList<>(); + for (Player member : _members) + { + if (member.getInventory().validateCapacityByItemId(itemId) && LocationUtil.checkIfInRange(PlayerConfig.ALT_PARTY_RANGE, target, member, true)) + { + availableMembers.add(member); + } + } + + return !availableMembers.isEmpty() ? availableMembers.get(Rnd.get(availableMembers.size())) : null; + } + + /** + * get next item looter + * @param itemId + * @param target + * @return + */ + private Player getCheckedNextLooter(int itemId, Creature target) + { + for (int i = 0; i < getMemberCount(); i++) + { + if (++_itemLastLoot >= getMemberCount()) + { + _itemLastLoot = 0; + } + + Player member; + try + { + member = _members.get(_itemLastLoot); + if (member.getInventory().validateCapacityByItemId(itemId) && LocationUtil.checkIfInRange(PlayerConfig.ALT_PARTY_RANGE, target, member, true)) + { + return member; + } + } + catch (Exception e) + { + // continue, take another member if this just logged off + } + } + + return null; + } + + /** + * get next item looter + * @param player + * @param itemId + * @param spoil + * @param target + * @return + */ + private Player getActualLooter(Player player, int itemId, boolean spoil, Creature target) + { + Player looter = null; + + switch (_distributionType) + { + case RANDOM: + { + if (!spoil) + { + looter = getCheckedRandomMember(itemId, target); + } + break; + } + case RANDOM_INCLUDING_SPOIL: + { + looter = getCheckedRandomMember(itemId, target); + break; + } + case BY_TURN: + { + if (!spoil) + { + looter = getCheckedNextLooter(itemId, target); + } + break; + } + case BY_TURN_INCLUDING_SPOIL: + { + looter = getCheckedNextLooter(itemId, target); + break; + } + } + + return looter != null ? looter : player; + } + + /** + * Broadcasts UI update and User Info for new party leader. + */ + public void broadcastToPartyMembersNewLeader() + { + for (Player member : _members) + { + if (member != null) + { + member.sendPacket(PartySmallWindowDeleteAll.STATIC_PACKET); + member.sendPacket(new PartySmallWindowAll(member, this)); + member.broadcastUserInfo(); + } + } + } + + /** + * Send a Server->Client packet to all other Player of the Party. + * @param player + * @param packet + */ + public void broadcastToPartyMembers(Player player, ServerPacket packet) + { + for (Player member : _members) + { + if ((member != null) && (member.getObjectId() != player.getObjectId())) + { + member.sendPacket(packet); + } + } + } + + /** + * adds new member to party + * @param player + */ + public void addPartyMember(Player player) + { + if (_members.contains(player)) + { + return; + } + + // add player to party + _members.add(player); + + // sends new member party window for all members + // we do all actions before adding member to a list, this speeds things up a little + player.sendPacket(new PartySmallWindowAll(player, this)); + + // sends pets/summons of party members + // for (Player pMember : _members) + // { + // if ((pMember != null) && pMember.hasSummon()) + // { + // player.sendPacket(new ExPartyPetWindowAdd(pMember.getSummon())); + // } + // } + + SystemMessage msg = new SystemMessage(SystemMessageId.YOU_HAVE_JOINED_S1_S_PARTY); + msg.addString(getLeader().getName()); + player.sendPacket(msg); + + msg = new SystemMessage(SystemMessageId.S1_HAS_JOINED_THE_PARTY); + msg.addString(player.getName()); + broadcastToPartyMembers(player, msg); + + for (Player member : _members) + { + if (member != player) + { + member.sendPacket(new PartySmallWindowAdd(player, this)); + } + } + + // send the position of all party members to the new party member + // player.sendPacket(new PartyMemberPosition(this)); + // send the position of the new party member to all party members (except the new one - he knows his own position) + // broadcastToPartyMembers(player, new PartyMemberPosition(this)); + + // if member has pet/summon add it to other as well + // if (player.hasSummon()) + // { + // broadcastPacket(new ExPartyPetWindowAdd(player.getSummon())); + // } + + // adjust party level + if (player.getLevel() > _partyLvl) + { + _partyLvl = player.getLevel(); + } + + // update partySpelled + Summon summon; + for (Player member : _members) + { + if (member != null) + { + member.updateEffectIcons(true); // update party icons only + summon = member.getSummon(); + member.broadcastUserInfo(); + if (summon != null) + { + summon.updateEffectIcons(); + } + } + } + + if (isInDimensionalRift()) + { + _dr.partyMemberInvited(); + } + + // open the CCInformationwindow + if (isInCommandChannel()) + { + player.sendPacket(ExOpenMPCC.STATIC_PACKET); + } + + if (_positionBroadcastTask == null) + { + _positionBroadcastTask = ThreadPool.scheduleAtFixedRate(() -> + { + if (_positionPacket == null) + { + _positionPacket = new PartyMemberPosition(this); + } + else + { + _positionPacket.reuse(this); + } + + broadcastPacket(_positionPacket); + }, PARTY_POSITION_BROADCAST_INTERVAL.toMillis() / 2, PARTY_POSITION_BROADCAST_INTERVAL.toMillis()); + } + } + + /** + * Removes a party member using its name. + * @param name player the player to be removed from the party. + * @param type the message type {@link PartyMessageType}. + */ + public void removePartyMember(String name, PartyMessageType type) + { + removePartyMember(getPlayerByName(name), type); + } + + /** + * Removes a party member instance. + * @param player the player to be removed from the party. + * @param type the message type {@link PartyMessageType}. + */ + public void removePartyMember(Player player, PartyMessageType type) + { + if (_members.contains(player)) + { + final boolean isLeader = isLeader(player); + if (!_disbanding && ((_members.size() == 2) || (isLeader && !PlayerConfig.ALT_LEAVE_PARTY_LEADER && (type != PartyMessageType.DISCONNECTED)))) + { + disbandParty(); + return; + } + + _members.remove(player); + recalculatePartyLevel(); + + if (player.isFestivalParticipant()) + { + SevenSignsFestival.getInstance().updateParticipants(player, this); + } + + if (player.isInDuel()) + { + DuelManager.getInstance().onRemoveFromParty(player); + } + + try + { + // Channeling a player! + if (player.isChanneling() && (player.getSkillChannelizer().hasChannelized())) + { + player.abortCast(); + } + else if (player.isChannelized()) + { + player.getSkillChannelized().abortChannelization(); + } + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "removePartyMember: ", e); + } + + SystemMessage msg; + if (type == PartyMessageType.EXPELLED) + { + player.sendPacket(SystemMessageId.YOU_HAVE_BEEN_EXPELLED_FROM_THE_PARTY); + msg = new SystemMessage(SystemMessageId.S1_WAS_EXPELLED_FROM_THE_PARTY); + msg.addString(player.getName()); + broadcastPacket(msg); + } + else if ((type == PartyMessageType.LEFT) || (type == PartyMessageType.DISCONNECTED)) + { + player.sendPacket(SystemMessageId.YOU_HAVE_WITHDRAWN_FROM_THE_PARTY); + msg = new SystemMessage(SystemMessageId.S1_HAS_LEFT_THE_PARTY); + msg.addString(player.getName()); + broadcastPacket(msg); + } + + // UI update. + player.sendPacket(PartySmallWindowDeleteAll.STATIC_PACKET); + player.setParty(null); + broadcastPacket(new PartySmallWindowDelete(player)); + + // if (player.hasSummon()) + // { + // broadcastPacket(new ExPartyPetWindowDelete(player.getSummon())); + // } + + if (isInDimensionalRift()) + { + _dr.partyMemberExited(player); + } + + // Close the CCInfoWindow + if (isInCommandChannel()) + { + player.sendPacket(new ExCloseMPCC()); + } + + if (isLeader && (_members.size() > 1) && (PlayerConfig.ALT_LEAVE_PARTY_LEADER || (type == PartyMessageType.DISCONNECTED))) + { + msg = new SystemMessage(SystemMessageId.S1_HAS_BECOME_THE_PARTY_LEADER); + msg.addString(getLeader().getName()); + broadcastPacket(msg); + broadcastToPartyMembersNewLeader(); + } + else if (_members.size() == 1) + { + if (isInCommandChannel()) + { + // delete the whole command channel when the party who opened the channel is disbanded + if (_commandChannel.getLeader().getObjectId() == getLeader().getObjectId()) + { + _commandChannel.disbandChannel(); + } + else + { + _commandChannel.removeParty(this); + } + } + + final Player leader = getLeader(); + if (leader != null) + { + leader.setParty(null); + if (leader.isInDuel()) + { + DuelManager.getInstance().onRemoveFromParty(leader); + } + } + + if (_positionBroadcastTask != null) + { + _positionBroadcastTask.cancel(false); + _positionBroadcastTask = null; + } + + _members.clear(); + } + } + } + + /** + * Disperse a party and send a message to all its members. + */ + public void disbandParty() + { + _disbanding = true; + broadcastPacket(new SystemMessage(SystemMessageId.THE_PARTY_HAS_DISPERSED)); + for (Player member : _members) + { + if (member != null) + { + removePartyMember(member, PartyMessageType.NONE); + } + } + } + + /** + * Change party leader (used for string arguments) + * @param name the name of the player to set as the new party leader + */ + public void changePartyLeader(String name) + { + setLeader(getPlayerByName(name)); + } + + @Override + public void setLeader(Player player) + { + if ((player != null) && !player.isInDuel()) + { + if (_members.contains(player)) + { + if (isLeader(player)) + { + player.sendPacket(SystemMessageId.SLOW_DOWN_YOU_ARE_ALREADY_THE_PARTY_LEADER); + } + else + { + // Swap party members + final Player temp = getLeader(); + final int p1 = _members.indexOf(player); + _members.set(0, player); + _members.set(p1, temp); + SystemMessage msg = new SystemMessage(SystemMessageId.S1_HAS_BECOME_THE_PARTY_LEADER); + msg.addString(getLeader().getName()); + broadcastPacket(msg); + broadcastToPartyMembersNewLeader(); + if (isInCommandChannel() && _commandChannel.isLeader(temp)) + { + _commandChannel.setLeader(getLeader()); + msg = new SystemMessage(SystemMessageId.COMMAND_CHANNEL_AUTHORITY_HAS_BEEN_TRANSFERRED_TO_S1); + msg.addString(_commandChannel.getLeader().getName()); + _commandChannel.broadcastPacket(msg); + } + + if (player.isInPartyMatchRoom()) + { + PartyMatchRoomList.getInstance().getPlayerRoom(player).changeLeader(player); + } + } + } + else + { + player.sendPacket(SystemMessageId.YOU_MAY_ONLY_TRANSFER_PARTY_LEADERSHIP_TO_ANOTHER_MEMBER_OF_THE_PARTY); + } + } + } + + /** + * Finds a player in the party by their name. + * @param name The name of the player to find. + * @return The player with the matching name, or {@code null} if no player with that name is in the party. + */ + public Player getPlayerByName(String name) + { + for (Player member : _members) + { + if (member.getName().equalsIgnoreCase(name)) + { + return member; + } + } + + return null; + } + + /** + * Distributes an item to party members. + * @param player The player initiating the distribution. + * @param item The item to be distributed. + */ + public void distributeItem(Player player, Item item) + { + if (item.getId() == Inventory.ADENA_ID) + { + distributeAdena(player, item.getCount(), player); + ItemManager.destroyItem(ItemProcessType.LOOT, item, player, null); + return; + } + + final Player target = getActualLooter(player, item.getId(), false, player); + target.addItem(ItemProcessType.LOOT, item, player, true); + + // Send messages to other party members about reward. + if (item.getCount() > 1) + { + final SystemMessage msg = new SystemMessage(SystemMessageId.S1_HAS_OBTAINED_S3_S2); + msg.addString(target.getName()); + msg.addItemName(item); + msg.addInt(item.getCount()); + broadcastToPartyMembers(target, msg); + } + else + { + final SystemMessage msg = new SystemMessage(SystemMessageId.S1_HAS_OBTAINED_S2); + msg.addString(target.getName()); + msg.addItemName(item); + broadcastToPartyMembers(target, msg); + } + } + + /** + * Distributes item loot between party members. + * @param player the reference player + * @param itemId the item ID + * @param itemCount the item count + * @param spoil {@code true} if it's spoil loot + * @param target the NPC target + */ + public void distributeItem(Player player, int itemId, int itemCount, boolean spoil, Attackable target) + { + if (itemId == Inventory.ADENA_ID) + { + distributeAdena(player, itemCount, target); + return; + } + + final Player looter = getActualLooter(player, itemId, spoil, target); + looter.addItem(spoil ? ItemProcessType.SWEEP : ItemProcessType.LOOT, itemId, itemCount, target, true); + + // Send messages to other party members about reward. + if (itemCount > 1) + { + final SystemMessage msg = spoil ? new SystemMessage(SystemMessageId.S1_HAS_OBTAINED_S3_S2_BY_USING_SWEEPER) : new SystemMessage(SystemMessageId.S1_HAS_OBTAINED_S3_S2); + msg.addString(looter.getName()); + msg.addItemName(itemId); + msg.addInt(itemCount); + broadcastToPartyMembers(looter, msg); + } + else + { + final SystemMessage msg = spoil ? new SystemMessage(SystemMessageId.S1_HAS_OBTAINED_S2_BY_USING_SWEEPER) : new SystemMessage(SystemMessageId.S1_HAS_OBTAINED_S2); + msg.addString(looter.getName()); + msg.addItemName(itemId); + broadcastToPartyMembers(looter, msg); + } + } + + /** + * Method overload for {@link Party#distributeItem(Player, int, int, boolean, Attackable)} + * @param player the reference player + * @param item the item holder + * @param spoil {@code true} if it's spoil loot + * @param target the NPC target + */ + public void distributeItem(Player player, ItemHolder item, boolean spoil, Attackable target) + { + distributeItem(player, item.getId(), item.getCount(), spoil, target); + } + + /** + * Distribute adena to party members who are in range of the target. + * @param player The player responsible for the kill or loot (used as reference). + * @param adena Total amount of adena to distribute. + * @param target The target creature (used for range checking). + */ + public void distributeAdena(Player player, int adena, Creature target) + { + // List to collect party members eligible to receive adena. + final List toReward = new ArrayList<>(); + + // Iterate over all party members to find those within the allowed party range of the target. + for (Player member : _members) + { + if (LocationUtil.checkIfInRange(PlayerConfig.ALT_PARTY_RANGE, target, member, true)) + { + toReward.add(member); // Add eligible member to the reward list. + } + } + + // If no members are eligible or there is no adena to distribute, exit early. + if (toReward.isEmpty() || (adena <= 0)) + { + return; + } + + final int baseShare = adena / toReward.size(); // Calculate the base share of adena each eligible member will receive. + final int remainder = adena % toReward.size(); // Calculate the leftover adena that can't be evenly split (remainder). + final Map adenaDistribution = new HashMap<>(); // Map to track how much adena each member will receive. + + // Assign the base share to every eligible member initially. + for (Player member : toReward) + { + adenaDistribution.put(member, baseShare); + } + + // Distribute the leftover adena (1 adena each) to some members starting from a random index. + if (remainder > 0) + { + final int randomIndex = Rnd.get(toReward.size()); + for (int i = 0; i < remainder; i++) + { + final Player member = toReward.get((randomIndex + i) % toReward.size()); + adenaDistribution.put(member, adenaDistribution.get(member) + 1); // Increase the member's share by 1 to include remainder. + } + } + + // Finally, give each member their total calculated adena amount. + for (Entry entry : adenaDistribution.entrySet()) + { + final int amount = entry.getValue(); + if (amount > 0) + { + // Add adena to the player's inventory and send message. + entry.getKey().addAdena(ItemProcessType.LOOT, amount, player, true); + } + } + } + + /** + * Distribute Experience and SP rewards to Player Party members in the known area of the last attacker.
        + *
        + * Actions: + *
      • Get the Player owner of the Servitor (if necessary)
      • + *
      • Calculate the Experience and SP reward distribution rate
      • + *
      • Add Experience and SP to the Player

      • + * @param xpRewardValue The Experience reward to distribute + * @param spRewardValue The SP reward to distribute + * @param rewardedMembers The list of Player to reward + * @param topLvl + * @param partyDmg + * @param target + */ + public void distributeXpAndSp(double xpRewardValue, double spRewardValue, List rewardedMembers, int topLvl, long partyDmg, Attackable target) + { + final List validMembers = getValidMembers(rewardedMembers, topLvl, target); + double xpReward = xpRewardValue * getExpBonus(validMembers.size()); + double spReward = spRewardValue * getSpBonus(validMembers.size()); + int sqLevelSum = 0; + int averagePartyMemberLevel = 0; + for (Player member : validMembers) + { + sqLevelSum += member.getLevel() * member.getLevel(); + averagePartyMemberLevel += member.getLevel(); + } + + averagePartyMemberLevel /= Math.max(1, validMembers.size()); + + final float vitalityPoints = (target.getVitalityPoints(averagePartyMemberLevel, partyDmg) * RatesConfig.RATE_PARTY_XP) / validMembers.size(); + final boolean useVitalityRate = target.useVitalityRate(); + for (Player member : rewardedMembers) + { + if (member.isDead()) + { + continue; + } + + // Calculate and add the EXP and SP reward to the member. + if (validMembers.contains(member)) + { + // The servitor penalty + final float penalty = member.hasServitor() ? member.getSummon().asServitor().getExpMultiplier() : 1; + final double sqLevel = member.getLevel() * member.getLevel(); + final double preCalculation = (sqLevel / sqLevelSum) * penalty; + + // Add the XP/SP points to the requested party member. + double addexp = Math.round(member.calcStat(Stat.EXPSP_RATE, xpReward * preCalculation, null, null)); + final double addsp = member.calcStat(Stat.EXPSP_RATE, spReward * preCalculation, null, null); + addexp = calculateExpSpPartyCutoff(member.asPlayer(), topLvl, addexp, addsp, useVitalityRate); + if (addexp > 0) + { + member.updateVitalityPoints(vitalityPoints, true, false); + PcCafePointsManager.getInstance().givePcCafePoint(member, addexp); + } + } + else + { + member.addExpAndSp(0, 0); + } + } + } + + private double calculateExpSpPartyCutoff(Player player, int topLvl, double addExpValue, double addSpValue, boolean vit) + { + double addExp = addExpValue * ClassBalanceConfig.EXP_AMOUNT_MULTIPLIERS[player.getPlayerClass().getId()]; + double addSp = addSpValue * ClassBalanceConfig.SP_AMOUNT_MULTIPLIERS[player.getPlayerClass().getId()]; + + // Premium rates + if (player.hasPremiumStatus()) + { + addExp *= PremiumSystemConfig.PREMIUM_RATE_XP; + addSp *= PremiumSystemConfig.PREMIUM_RATE_SP; + } + + double xp = addExp; + double sp = addSp; + if (PlayerConfig.PARTY_XP_CUTOFF_METHOD == PartyExpType.HIGHFIVE) + { + int i = 0; + final int levelDiff = topLvl - player.getLevel(); + for (int[] gap : PlayerConfig.PARTY_XP_CUTOFF_GAPS) + { + if ((levelDiff >= gap[0]) && (levelDiff <= gap[1])) + { + xp = (addExp * PlayerConfig.PARTY_XP_CUTOFF_GAP_PERCENTS[i]) / 100; + sp = (addSp * PlayerConfig.PARTY_XP_CUTOFF_GAP_PERCENTS[i]) / 100; + player.addExpAndSp(xp, sp, vit); + break; + } + + i++; + } + } + else + { + player.addExpAndSp(addExp, addSp, vit); + } + + return xp; + } + + /** + * refresh party level + */ + public void recalculatePartyLevel() + { + int newLevel = 0; + for (Player member : _members) + { + if (member == null) + { + _members.remove(member); + continue; + } + + if (member.getLevel() > newLevel) + { + newLevel = member.getLevel(); + } + } + + _partyLvl = newLevel; + } + + private List getValidMembers(List members, int topLvl, Attackable target) + { + final List validMembers = new ArrayList<>(); + switch (PlayerConfig.PARTY_XP_CUTOFF_METHOD) + { + case LEVEL: + { + for (Player member : members) + { + if ((target.getInstanceId() == member.getInstanceId()) && ((topLvl - member.getLevel()) <= PlayerConfig.PARTY_XP_CUTOFF_LEVEL)) + { + validMembers.add(member); + } + } + break; + } + case PERCENTAGE: + { + int sqLevelSum = 0; + for (Player member : members) + { + if (target.getInstanceId() == member.getInstanceId()) + { + sqLevelSum += (member.getLevel() * member.getLevel()); + } + } + + for (Player member : members) + { + final int sqLevel = member.getLevel() * member.getLevel(); + if ((target.getInstanceId() == member.getInstanceId()) && ((sqLevel * 100) >= (sqLevelSum * PlayerConfig.PARTY_XP_CUTOFF_PERCENT))) + { + validMembers.add(member); + } + } + break; + } + case AUTO: + { + int sqLevelSum = 0; + for (Player member : members) + { + sqLevelSum += (member.getLevel() * member.getLevel()); + } + + int i = members.size() - 1; + if (i < 1) + { + return members; + } + + if (i >= BONUS_EXP_SP.length) + { + i = BONUS_EXP_SP.length - 1; + } + + for (Player member : members) + { + final int sqLevel = member.getLevel() * member.getLevel(); + if ((target.getInstanceId() == member.getInstanceId()) && (sqLevel >= (sqLevelSum / (members.size() * members.size())))) + { + validMembers.add(member); + } + } + break; + } + case HIGHFIVE: + { + for (Player member : members) + { + if (target.getInstanceId() == member.getInstanceId()) + { + validMembers.add(member); + } + } + break; + } + case NONE: + { + for (Player member : members) + { + if (target.getInstanceId() == member.getInstanceId()) + { + validMembers.add(member); + } + } + break; + } + } + + return validMembers; + } + + private double getBaseExpSpBonus(int membersCount) + { + int i = membersCount - 1; + if (i < 1) + { + return 1; + } + + if (i >= BONUS_EXP_SP.length) + { + i = BONUS_EXP_SP.length - 1; + } + + return BONUS_EXP_SP[i]; + } + + private double getExpBonus(int membersCount) + { + return (membersCount < 2) ? getBaseExpSpBonus(membersCount) : (getBaseExpSpBonus(membersCount) * RatesConfig.RATE_PARTY_XP); + } + + private double getSpBonus(int membersCount) + { + return (membersCount < 2) ? getBaseExpSpBonus(membersCount) : (getBaseExpSpBonus(membersCount) * RatesConfig.RATE_PARTY_SP); + } + + @Override + public int getLevel() + { + return _partyLvl; + } + + public PartyDistributionType getDistributionType() + { + return _distributionType; + } + + public boolean isInCommandChannel() + { + return _commandChannel != null; + } + + public CommandChannel getCommandChannel() + { + return _commandChannel; + } + + public void setCommandChannel(CommandChannel channel) + { + _commandChannel = channel; + } + + public boolean isInDimensionalRift() + { + return _dr != null; + } + + public void setDimensionalRift(DimensionalRift dr) + { + _dr = dr; + } + + public DimensionalRift getDimensionalRift() + { + return _dr; + } + + /** + * @return the leader of this party + */ + @Override + public Player getLeader() + { + if (_members.isEmpty()) + { + return null; + } + + final Player leader; + try + { + leader = _members.get(0); + } + catch (Exception e) + { + return null; + } + + return leader; + } + + /** + * @return a list of all members of this party + */ + @Override + public List getMembers() + { + return _members; + } + + /** + * Check whether the leader of this party is the same as the leader of the specified party (which essentially means they're the same group). + * @param party the other party to check against + * @return {@code true} if this party equals the specified party, {@code false} otherwise + */ + public boolean equals(Party party) + { + return (party != null) && (getLeaderObjectId() == party.getLeaderObjectId()); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/groups/PartyDistributionType.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/groups/PartyDistributionType.java new file mode 100644 index 0000000..11f72a1 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/groups/PartyDistributionType.java @@ -0,0 +1,79 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.groups; + +/** + * @author NosBit + */ +public enum PartyDistributionType +{ + FINDERS_KEEPERS(0, 487), + RANDOM(1, 488), + RANDOM_INCLUDING_SPOIL(2, 798), + BY_TURN(3, 799), + BY_TURN_INCLUDING_SPOIL(4, 800); + + private final int _id; + private final int _sysStringId; + + /** + * Constructs a party distribution type. + * @param id the id used by packets. + * @param sysStringId the sysstring id + */ + private PartyDistributionType(int id, int sysStringId) + { + _id = id; + _sysStringId = sysStringId; + } + + /** + * Gets the id used by packets. + * @return the id + */ + public int getId() + { + return _id; + } + + /** + * Gets the sysstring id used by system messages. + * @return the sysstring-e id + */ + public int getSysStringId() + { + return _sysStringId; + } + + /** + * Finds the {@code PartyDistributionType} by its id + * @param id the id + * @return the {@code PartyDistributionType} if it is found, {@code null} otherwise. + */ + public static PartyDistributionType findById(int id) + { + for (PartyDistributionType partyDistributionType : values()) + { + if (partyDistributionType.getId() == id) + { + return partyDistributionType; + } + } + + return null; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/groups/PartyExpType.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/groups/PartyExpType.java new file mode 100644 index 0000000..f246ec2 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/groups/PartyExpType.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.groups; + +/** + * @author Mobius + */ +public enum PartyExpType +{ + NONE, + AUTO, + LEVEL, + PERCENTAGE, + HIGHFIVE +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/groups/PartyMessageType.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/groups/PartyMessageType.java new file mode 100644 index 0000000..560edc0 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/groups/PartyMessageType.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.groups; + +/** + * The message type send to the party members. + */ +public enum PartyMessageType +{ + EXPELLED, + LEFT, + NONE, + DISCONNECTED +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/groups/matching/PartyMatchRoom.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/groups/matching/PartyMatchRoom.java new file mode 100644 index 0000000..67665e3 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/groups/matching/PartyMatchRoom.java @@ -0,0 +1,205 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.groups.matching; + +import java.util.ArrayList; +import java.util.List; + +import org.l2jmobius.gameserver.data.xml.MapRegionData; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.serverpackets.ExManagePartyRoomMember; +import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; + +/** + * @author Gnacik + */ +public class PartyMatchRoom +{ + private final int _id; + private String _title; + private int _loot; + private int _minLevel; + private int _maxLevel; + private int _maxmem; + private final List _members = new ArrayList<>(); + + public PartyMatchRoom(int id, String title, int loot, int minLevel, int maxLevel, int maxmem, Player owner) + { + _id = id; + _title = title; + _loot = loot; + _minLevel = minLevel; + _maxLevel = maxLevel; + _maxmem = maxmem; + _members.add(owner); + } + + public List getPartyMembers() + { + return _members; + } + + public void addMember(Player player) + { + _members.add(player); + } + + public void deleteMember(Player player) + { + if (player != getOwner()) + { + _members.remove(player); + notifyMembersAboutExit(player); + } + else if (_members.size() == 1) + { + PartyMatchRoomList.getInstance().deleteRoom(_id); + } + else + { + changeLeader(_members.get(1)); + deleteMember(player); + } + } + + public void notifyMembersAboutExit(Player player) + { + for (Player _member : _members) + { + final SystemMessage sm = new SystemMessage(SystemMessageId.S1_HAS_LEFT_THE_PARTY_ROOM); + sm.addString(player.getName()); + _member.sendPacket(sm); + _member.sendPacket(new ExManagePartyRoomMember(player, this, 2)); + } + } + + public void changeLeader(Player newLeader) + { + // Get current leader + final Player oldLeader = _members.get(0); + + // Remove new leader + _members.remove(newLeader); + + // Move him to first position. + _members.set(0, newLeader); + + // Add old leader as normal member. + _members.add(oldLeader); + + // Broadcast change + for (Player member : _members) + { + member.sendPacket(new ExManagePartyRoomMember(newLeader, this, 1)); + member.sendPacket(new ExManagePartyRoomMember(oldLeader, this, 1)); + member.sendPacket(SystemMessageId.THE_LEADER_OF_THE_PARTY_ROOM_HAS_CHANGED); + } + } + + public int getId() + { + return _id; + } + + public int getLootType() + { + return _loot; + } + + public int getMinLevel() + { + return _minLevel; + } + + public int getMaxLevel() + { + return _maxLevel; + } + + /** + *
          + *
        • 1 : Talking Island
        • + *
        • 2 : Gludio
        • + *
        • 3 : Dark Elven Ter.
        • + *
        • 4 : Elven Territory
        • + *
        • 5 : Dion
        • + *
        • 6 : Giran
        • + *
        • 7 : Neutral Zone
        • + *
        • 8 : Lyonn
        • + *
        • 9 : Schuttgart
        • + *
        • 10 : Oren
        • + *
        • 11 : Hunters Village
        • + *
        • 12 : Innadril
        • + *
        • 13 : Aden
        • + *
        • 14 : Rune
        • + *
        • 15 : Goddard
        • + *
        + * @return the id + */ + public int getLocation() + { + return MapRegionData.getInstance().getBBs(_members.get(0)); + } + + public int getMembers() + { + return _members.size(); + } + + public int getMaxMembers() + { + return _maxmem; + } + + public String getTitle() + { + return _title; + } + + public Player getOwner() + { + return _members.get(0); + } + + /* SET */ + + public void setMinLevel(int minLevel) + { + _minLevel = minLevel; + } + + public void setMaxLevel(int maxLevel) + { + _maxLevel = maxLevel; + } + + public void setLootType(int loot) + { + _loot = loot; + } + + public void setMaxMembers(int maxmem) + { + _maxmem = maxmem; + } + + public void setTitle(String title) + { + _title = title; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/groups/matching/PartyMatchRoomList.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/groups/matching/PartyMatchRoomList.java new file mode 100644 index 0000000..94871fc --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/groups/matching/PartyMatchRoomList.java @@ -0,0 +1,127 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.groups.matching; + +import java.util.Collection; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.serverpackets.ExClosePartyRoom; + +/** + * @author Gnacik + */ +public class PartyMatchRoomList +{ + private int _maxid = 1; + private final Map _rooms; + + protected PartyMatchRoomList() + { + _rooms = new ConcurrentHashMap<>(); + } + + public synchronized void addPartyMatchRoom(int id, PartyMatchRoom room) + { + _rooms.put(id, room); + _maxid++; + } + + public void deleteRoom(int id) + { + for (Player _member : getRoom(id).getPartyMembers()) + { + if (_member == null) + { + continue; + } + + _member.sendPacket(new ExClosePartyRoom()); + _member.sendPacket(SystemMessageId.THE_PARTY_ROOM_HAS_BEEN_DISBANDED); + + _member.setPartyRoom(0); + // _member.setPartyMatching(0); + _member.broadcastUserInfo(); + } + + _rooms.remove(id); + } + + public PartyMatchRoom getRoom(int id) + { + return _rooms.get(id); + } + + public Collection getRooms() + { + return _rooms.values(); + } + + public int getPartyMatchRoomCount() + { + return _rooms.size(); + } + + public int getMaxId() + { + return _maxid; + } + + public PartyMatchRoom getPlayerRoom(Player player) + { + for (PartyMatchRoom _room : _rooms.values()) + { + for (Player member : _room.getPartyMembers()) + { + if (member.equals(player)) + { + return _room; + } + } + } + + return null; + } + + public int getPlayerRoomId(Player player) + { + for (PartyMatchRoom _room : _rooms.values()) + { + for (Player member : _room.getPartyMembers()) + { + if (member.equals(player)) + { + return _room.getId(); + } + } + } + + return -1; + } + + public static PartyMatchRoomList getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final PartyMatchRoomList INSTANCE = new PartyMatchRoomList(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/groups/matching/PartyMatchWaitingList.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/groups/matching/PartyMatchWaitingList.java new file mode 100644 index 0000000..cc3d1e6 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/groups/matching/PartyMatchWaitingList.java @@ -0,0 +1,65 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.groups.matching; + +import java.util.Collection; +import java.util.concurrent.ConcurrentHashMap; + +import org.l2jmobius.gameserver.entity.actor.Player; + +/** + * @author Gnacik + */ +public class PartyMatchWaitingList +{ + private final Collection _members; + + protected PartyMatchWaitingList() + { + _members = ConcurrentHashMap.newKeySet(); + } + + public void addPlayer(Player player) + { + // player.setPartyWait(1); + if (!_members.contains(player)) + { + _members.add(player); + } + } + + public void removePlayer(Player player) + { + // player.setPartyWait(0); + _members.remove(player); + } + + public Collection getPlayers() + { + return _members; + } + + public static PartyMatchWaitingList getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final PartyMatchWaitingList INSTANCE = new PartyMatchWaitingList(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/instancezone/Instance.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/instancezone/Instance.java new file mode 100644 index 0000000..9b0c735 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/instancezone/Instance.java @@ -0,0 +1,987 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.instancezone; + +import java.io.File; +import java.io.IOException; +import java.time.DayOfWeek; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ScheduledFuture; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.xml.parsers.DocumentBuilderFactory; + +import org.w3c.dom.Document; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; + +import org.l2jmobius.commons.threads.ThreadPool; +import org.l2jmobius.gameserver.config.GeneralConfig; +import org.l2jmobius.gameserver.data.holders.InstanceReenterTimeHolder; +import org.l2jmobius.gameserver.data.xml.DoorData; +import org.l2jmobius.gameserver.entity.Location; +import org.l2jmobius.gameserver.entity.World; +import org.l2jmobius.gameserver.entity.WorldRegion; +import org.l2jmobius.gameserver.entity.actor.Npc; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.enums.player.TeleportWhereType; +import org.l2jmobius.gameserver.entity.actor.instance.Door; +import org.l2jmobius.gameserver.entity.actor.templates.DoorTemplate; +import org.l2jmobius.gameserver.entity.spawns.Spawn; +import org.l2jmobius.gameserver.managers.InstanceManager; +import org.l2jmobius.gameserver.network.enums.ChatType; +import org.l2jmobius.gameserver.network.serverpackets.CreatureSay; +import org.l2jmobius.gameserver.util.StatSet; + +/** + * Main class for game instances. + * @author evill33t, GodKratos + */ +public class Instance +{ + private static final Logger LOGGER = Logger.getLogger(Instance.class.getName()); + + private final int _id; + private int _templateId = -1; + private int _ejectTime = GeneralConfig.EJECT_DEAD_PLAYER_TIME; + /** Allow random walk for NPCs, global parameter. */ + private boolean _allowRandomWalk = true; + private final Collection _players = ConcurrentHashMap.newKeySet(); + private final Collection _npcs = ConcurrentHashMap.newKeySet(); + private final Collection _doorTemplates = ConcurrentHashMap.newKeySet(); + private final Map _doors = new ConcurrentHashMap<>(); + private final Collection _spawnTemplates = ConcurrentHashMap.newKeySet(); + private Collection _enterLocations = ConcurrentHashMap.newKeySet(); + private Location _exitLocation = null; + private boolean _allowSummon = true; + private long _emptyDestroyTime = -1; + private long _lastLeft = -1; + private long _instanceStartTime = -1; + private long _instanceEndTime = -1; + private boolean _isPvP = false; + private boolean _showTimer = false; + private boolean _isTimerIncrease = true; + private String _timerText = ""; + + // Instance reset data + private InstanceReenterType _type = InstanceReenterType.NONE; + private final List _resetData = new ArrayList<>(); + + // Instance remove buffs data + private InstanceRemoveBuffType _removeBuffType = InstanceRemoveBuffType.NONE; + private final List _exceptionList = new ArrayList<>(); + + protected ScheduledFuture _checkTimeUpTask = null; + protected final Map> _ejectDeadTasks = new ConcurrentHashMap<>(); + + public Instance(int id) + { + _id = id; + _instanceStartTime = System.currentTimeMillis(); + } + + /** + * @return the ID of this instance. + */ + public int getId() + { + return _id; + } + + /** + * Get template ID of instance world. + * @return instance template ID + */ + public int getTemplateId() + { + return _templateId; + } + + /** + * @return the eject time + */ + public int getEjectTime() + { + return _ejectTime; + } + + /** + * @param ejectTime the player eject time upon death + */ + public void setEjectTime(int ejectTime) + { + _ejectTime = ejectTime; + } + + /** + * @return whether summon friend type skills are allowed for this instance + */ + public boolean isSummonAllowed() + { + return _allowSummon; + } + + /** + * Sets the status for the instance for summon friend type skills + * @param value + */ + public void setAllowSummon(boolean value) + { + _allowSummon = value; + } + + /** + * Returns true if entire instance is PvP zone + * @return + */ + public boolean isPvP() + { + return _isPvP; + } + + /** + * Sets PvP zone status of the instance + * @param value + */ + public void setPvP(boolean value) + { + _isPvP = value; + } + + /** + * Set the instance duration task + * @param duration in milliseconds + */ + public void setDuration(int duration) + { + if (_checkTimeUpTask != null) + { + _checkTimeUpTask.cancel(true); + } + + _checkTimeUpTask = ThreadPool.schedule(new CheckTimeUp(duration), 500); + _instanceEndTime = System.currentTimeMillis() + duration + 500; + } + + /** + * Set time before empty instance will be removed + * @param time in milliseconds + */ + public void setEmptyDestroyTime(long time) + { + _emptyDestroyTime = time; + } + + /** + * Checks if the player exists within this instance + * @param objectId + * @return true if player exists in instance + */ + public boolean containsPlayer(int objectId) + { + return _players.contains(objectId); + } + + /** + * Adds the specified player to the instance + * @param objectId Players object ID + */ + public void addPlayer(int objectId) + { + _players.add(objectId); + } + + /** + * Removes the specified player from the instance list. + * @param objectId the player's object Id + */ + public void removePlayer(Integer objectId) + { + _players.remove(objectId); + if (!_players.isEmpty() || (_emptyDestroyTime < 0)) + { + return; + } + + _lastLeft = System.currentTimeMillis(); + setDuration((int) (_instanceEndTime - System.currentTimeMillis() - 500)); + } + + public void addNpc(Npc npc) + { + _npcs.add(npc); + } + + public void removeNpc(Npc npc) + { + if (npc.getSpawn() != null) + { + npc.getSpawn().stopRespawn(); + } + + _npcs.remove(npc); + } + + /** + * Adds a door into the instance + * @param set - StatSet for initializing door + */ + public void addDoor(StatSet set) + { + if (_doorTemplates.contains(set)) + { + LOGGER.warning("Door ID " + set.getInt("DoorId") + " already exists in instance " + _id); + return; + } + + _doorTemplates.add(set); + } + + /** + * Spawn doors inside instance world. + */ + public void spawnDoors() + { + for (StatSet template : _doorTemplates) + { + // Create new door instance. + final int doorId = template.getInt("DoorId"); + final StatSet doorTemplate = DoorData.getInstance().getDoorTemplate(doorId); + final Door newdoor = new Door(new DoorTemplate(doorTemplate)); + newdoor.setInstanceId(_id); + newdoor.setCurrentHp(newdoor.getMaxHp()); + newdoor.spawnMe(newdoor.getTemplate().getX(), newdoor.getTemplate().getY(), newdoor.getTemplate().getZ()); + _doors.put(doorId, newdoor); + } + } + + public Collection getPlayers() + { + return _players; + } + + public Collection getNpcs() + { + return _npcs; + } + + public Collection getDoors() + { + return _doors.values(); + } + + public Door getDoor(int id) + { + return _doors.get(id); + } + + public long getInstanceEndTime() + { + return _instanceEndTime; + } + + public long getInstanceStartTime() + { + return _instanceStartTime; + } + + public boolean isShowTimer() + { + return _showTimer; + } + + public boolean isTimerIncrease() + { + return _isTimerIncrease; + } + + public String getTimerText() + { + return _timerText; + } + + /** + * @return the spawn location for this instance to be used when enter in instance + */ + public Collection getEnterLocs() + { + return _enterLocations; + } + + /** + * Sets the spawn location for this instance to be used when enter in instance + * @param loc + */ + public void addEnterLoc(Location loc) + { + _enterLocations.add(loc); + } + + /** + * @return the spawn location for this instance to be used when leaving the instance + */ + public Location getExitLoc() + { + return _exitLocation; + } + + /** + * Sets the spawn location for this instance to be used when leaving the instance + * @param loc + */ + public void setExitLoc(Location loc) + { + _exitLocation = loc; + } + + public void removePlayers() + { + for (Integer objectId : _players) + { + final Player player = World.getPlayer(objectId); + if ((player != null) && (player.getInstanceId() == _id)) + { + player.setInstanceId(0); + if (_exitLocation != null) + { + player.teleToLocation(_exitLocation, true); + } + else + { + player.teleToLocation(TeleportWhereType.TOWN); + } + } + } + + _players.clear(); + } + + public void removeNpcs() + { + for (Npc mob : _npcs) + { + if (mob != null) + { + if (mob.getSpawn() != null) + { + mob.getSpawn().stopRespawn(); + } + + mob.deleteMe(); + } + } + + _npcs.clear(); + } + + public void removeDoors() + { + for (Door door : _doors.values()) + { + if (door != null) + { + final WorldRegion region = door.getWorldRegion(); + door.decayMe(); + + if (region != null) + { + region.removeVisibleObject(door); + } + + World.removeObject(door); + } + } + + _doors.clear(); + } + + /** + * Spawns group of instance NPCs + * @param groupName - name of group from XML definition to spawn + * @return list of spawned NPCs + */ + public List spawnGroup(String groupName) + { + final List spawnedNpcs = new ArrayList<>(); + for (StatSet set : _spawnTemplates) + { + if (set.getString("spawnGroup").equals(groupName)) + { + try + { + final Spawn spawnDat = new Spawn(set.getInt("npcId")); + spawnDat.setXYZ(set.getInt("x"), set.getInt("y"), set.getInt("z")); + spawnDat.setAmount(1); + spawnDat.setHeading(set.getInt("heading")); + spawnDat.setRespawnDelay(set.getInt("respawn"), set.getInt("respawnRandom")); + spawnDat.setInstanceId(_id); + spawnDat.setRandomWalking(set.getBoolean("allowRandomWalk")); + final Npc spawned = spawnDat.doSpawn(false); + if ((set.getInt("delay") >= 0) && spawned.isAttackable()) + { + spawned.asAttackable().setOnKillDelay(set.getInt("delay")); + } + + if (set.getInt("respawn") == 0) + { + spawnDat.stopRespawn(); + } + else + { + spawnDat.startRespawn(); + } + + spawnedNpcs.add(spawned); + } + catch (Exception e) + { + LOGGER.warning("Problem with Instance: " + e.toString()); + } + } + } + + return spawnedNpcs; + } + + public void loadInstanceTemplate(int templateId) + { + // TODO: Cache templates. + Document document = null; + final File xml = new File("." + File.separator + "data" + File.separator + "instances" + File.separator + InstanceManager.getInstance().getInstanceTemplateFileName(templateId)); + try + { + final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setValidating(false); + factory.setIgnoringComments(true); + document = factory.newDocumentBuilder().parse(xml); + for (Node n = document.getFirstChild(); n != null; n = n.getNextSibling()) + { + if ("instance".equalsIgnoreCase(n.getNodeName())) + { + parseInstance(n); + } + } + } + catch (IOException e) + { + LOGGER.log(Level.WARNING, "Instance: can not find " + xml.getAbsolutePath() + " ! " + e.getMessage(), e); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Instance: error while loading " + xml.getAbsolutePath() + " ! " + e.getMessage(), e); + } + } + + private void parseInstance(Node node) throws Exception + { + Node n = node; + _templateId = Integer.parseInt(n.getAttributes().getNamedItem("id").getNodeValue()); + Node a = n.getAttributes().getNamedItem("ejectTime"); + if (a != null) + { + _ejectTime = 1000 * Integer.parseInt(a.getNodeValue()); + } + + a = n.getAttributes().getNamedItem("allowRandomWalk"); + if (a != null) + { + _allowRandomWalk = Boolean.parseBoolean(a.getNodeValue()); + } + + for (n = n.getFirstChild(); n != null; n = n.getNextSibling()) + { + switch (n.getNodeName().toLowerCase()) + { + case "activitytime": + { + a = n.getAttributes().getNamedItem("val"); + if (a != null) + { + _checkTimeUpTask = ThreadPool.schedule(new CheckTimeUp(Integer.parseInt(a.getNodeValue()) * 60000), 15000); + _instanceEndTime = System.currentTimeMillis() + (Long.parseLong(a.getNodeValue()) * 60000) + 15000; + } + break; + } + case "allowsummon": + { + a = n.getAttributes().getNamedItem("val"); + if (a != null) + { + setAllowSummon(Boolean.parseBoolean(a.getNodeValue())); + } + break; + } + case "emptydestroytime": + { + a = n.getAttributes().getNamedItem("val"); + if (a != null) + { + _emptyDestroyTime = Long.parseLong(a.getNodeValue()) * 1000; + } + break; + } + case "showtimer": + { + a = n.getAttributes().getNamedItem("val"); + if (a != null) + { + _showTimer = Boolean.parseBoolean(a.getNodeValue()); + } + + a = n.getAttributes().getNamedItem("increase"); + if (a != null) + { + _isTimerIncrease = Boolean.parseBoolean(a.getNodeValue()); + } + + a = n.getAttributes().getNamedItem("text"); + if (a != null) + { + _timerText = a.getNodeValue(); + } + break; + } + case "pvpinstance": + { + a = n.getAttributes().getNamedItem("val"); + if (a != null) + { + setPvP(Boolean.parseBoolean(a.getNodeValue())); + } + break; + } + case "doorlist": + { + for (Node d = n.getFirstChild(); d != null; d = d.getNextSibling()) + { + int doorId = 0; + if ("door".equalsIgnoreCase(d.getNodeName())) + { + doorId = Integer.parseInt(d.getAttributes().getNamedItem("id").getNodeValue()); + final StatSet set = new StatSet(); + set.set("DoorId", doorId); + for (Node bean = d.getFirstChild(); bean != null; bean = bean.getNextSibling()) + { + if ("set".equalsIgnoreCase(bean.getNodeName())) + { + final NamedNodeMap attrs = bean.getAttributes(); + final String setname = attrs.getNamedItem("name").getNodeValue(); + final String value = attrs.getNamedItem("val").getNodeValue(); + set.set(setname, value); + } + } + + addDoor(set); + } + } + break; + } + case "spawnlist": + { + for (Node group = n.getFirstChild(); group != null; group = group.getNextSibling()) + { + if ("group".equalsIgnoreCase(group.getNodeName())) + { + final String spawnGroup = group.getAttributes().getNamedItem("name").getNodeValue(); + for (Node d = group.getFirstChild(); d != null; d = d.getNextSibling()) + { + int npcId = 0; + int x = 0; + int y = 0; + int z = 0; + int heading = 0; + int respawn = 0; + int respawnRandom = 0; + int delay = -1; + Boolean allowRandomWalk = null; + if ("npc".equalsIgnoreCase(d.getNodeName())) + { + npcId = Integer.parseInt(d.getAttributes().getNamedItem("id").getNodeValue()); + x = Integer.parseInt(d.getAttributes().getNamedItem("x").getNodeValue()); + y = Integer.parseInt(d.getAttributes().getNamedItem("y").getNodeValue()); + z = Integer.parseInt(d.getAttributes().getNamedItem("z").getNodeValue()); + if (d.getAttributes().getNamedItem("heading") != null) + { + heading = Integer.parseInt(d.getAttributes().getNamedItem("heading").getNodeValue()); + } + + if (d.getAttributes().getNamedItem("respawn") != null) + { + respawn = Integer.parseInt(d.getAttributes().getNamedItem("respawn").getNodeValue()); + } + + if (d.getAttributes().getNamedItem("onKillDelay") != null) + { + delay = Integer.parseInt(d.getAttributes().getNamedItem("onKillDelay").getNodeValue()); + } + + if (d.getAttributes().getNamedItem("respawnRandom") != null) + { + respawnRandom = Integer.parseInt(d.getAttributes().getNamedItem("respawnRandom").getNodeValue()); + } + + if (d.getAttributes().getNamedItem("allowRandomWalk") != null) + { + allowRandomWalk = Boolean.parseBoolean(d.getAttributes().getNamedItem("allowRandomWalk").getNodeValue()); + } + + final StatSet spawnSet = new StatSet(); + spawnSet.set("spawnGroup", spawnGroup); + spawnSet.set("npcId", npcId); + spawnSet.set("x", x); + spawnSet.set("y", y); + spawnSet.set("z", z); + spawnSet.set("heading", heading); + spawnSet.set("delay", delay); + spawnSet.set("respawn", respawn); + spawnSet.set("respawnRandom", respawnRandom); + if (allowRandomWalk == null) + { + spawnSet.set("allowRandomWalk", !_allowRandomWalk); + } + else + { + spawnSet.set("allowRandomWalk", !allowRandomWalk); + } + + _spawnTemplates.add(spawnSet); + } + } + } + } + break; + } + case "exitpoint": + { + final int x = Integer.parseInt(n.getAttributes().getNamedItem("x").getNodeValue()); + final int y = Integer.parseInt(n.getAttributes().getNamedItem("y").getNodeValue()); + final int z = Integer.parseInt(n.getAttributes().getNamedItem("z").getNodeValue()); + _exitLocation = new Location(x, y, z); + break; + } + case "spawnpoints": + { + _enterLocations = new ArrayList<>(); + for (Node loc = n.getFirstChild(); loc != null; loc = loc.getNextSibling()) + { + if (loc.getNodeName().equals("Location")) + { + try + { + final int x = Integer.parseInt(loc.getAttributes().getNamedItem("x").getNodeValue()); + final int y = Integer.parseInt(loc.getAttributes().getNamedItem("y").getNodeValue()); + final int z = Integer.parseInt(loc.getAttributes().getNamedItem("z").getNodeValue()); + _enterLocations.add(new Location(x, y, z)); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Error parsing instance xml: " + e.getMessage(), e); + } + } + } + break; + } + case "reenter": + { + a = n.getAttributes().getNamedItem("additionStyle"); + if (a != null) + { + _type = InstanceReenterType.valueOf(a.getNodeValue()); + } + + for (Node d = n.getFirstChild(); d != null; d = d.getNextSibling()) + { + long time = -1; + DayOfWeek day = null; + int hour = -1; + int minute = -1; + if ("reset".equalsIgnoreCase(d.getNodeName())) + { + a = d.getAttributes().getNamedItem("time"); + if (a != null) + { + time = Long.parseLong(a.getNodeValue()); + if (time > 0) + { + _resetData.add(new InstanceReenterTimeHolder(time)); + break; + } + } + else if (time == -1) + { + a = d.getAttributes().getNamedItem("day"); + if (a != null) + { + day = DayOfWeek.valueOf(a.getNodeValue().toUpperCase()); + } + + a = d.getAttributes().getNamedItem("hour"); + if (a != null) + { + hour = Integer.parseInt(a.getNodeValue()); + } + + a = d.getAttributes().getNamedItem("minute"); + if (a != null) + { + minute = Integer.parseInt(a.getNodeValue()); + } + + _resetData.add(new InstanceReenterTimeHolder(day, hour, minute)); + } + } + } + break; + } + case "removebuffs": + { + a = n.getAttributes().getNamedItem("type"); + if (a != null) + { + _removeBuffType = InstanceRemoveBuffType.valueOf(a.getNodeValue().toUpperCase()); + } + + for (Node d = n.getFirstChild(); d != null; d = d.getNextSibling()) + { + if ("skill".equalsIgnoreCase(d.getNodeName())) + { + a = d.getAttributes().getNamedItem("id"); + if (a != null) + { + _exceptionList.add(Integer.parseInt(a.getNodeValue())); + } + } + } + break; + } + } + } + } + + protected void doCheckTimeUp(int value) + { + CreatureSay cs = null; + int timeLeft; + int interval; + int remaining = value; + if (_players.isEmpty() && (_emptyDestroyTime == 0)) + { + remaining = 0; + interval = 500; + } + else if (_players.isEmpty() && (_emptyDestroyTime > 0)) + { + final Long emptyTimeLeft = (_lastLeft + _emptyDestroyTime) - System.currentTimeMillis(); + if (emptyTimeLeft <= 0) + { + interval = 0; + remaining = 0; + } + else if ((remaining > 300000) && (emptyTimeLeft > 300000)) + { + interval = 300000; + remaining -= 300000; + } + else if ((remaining > 60000) && (emptyTimeLeft > 60000)) + { + interval = 60000; + remaining -= 60000; + } + else if ((remaining > 30000) && (emptyTimeLeft > 30000)) + { + interval = 30000; + remaining -= 30000; + } + else + { + interval = 10000; + remaining -= 10000; + } + } + else if (remaining > 300000) + { + timeLeft = remaining / 60000; + interval = 300000; + for (Integer playerId : _players) + { + final Player player = World.getPlayer(playerId); + if ((player != null) && player.isOnline() && (player.getInstanceId() == _id)) + { + player.sendMessage("This dungeon will expire in " + timeLeft + " minute(s). You will be forced out of the dungeon when the time expires."); + } + } + remaining -= 300000; + } + else if (remaining > 60000) + { + timeLeft = remaining / 60000; + interval = 60000; + for (Integer playerId : _players) + { + final Player player = World.getPlayer(playerId); + if ((player != null) && player.isOnline() && (player.getInstanceId() == _id)) + { + player.sendMessage("This dungeon will expire in " + timeLeft + " minute(s). You will be forced out of the dungeon when the time expires."); + } + } + remaining -= 60000; + } + else if (remaining > 30000) + { + timeLeft = remaining / 1000; + interval = 30000; + cs = new CreatureSay(null, ChatType.ALLIANCE, "Notice", timeLeft + " seconds left."); + remaining -= 30000; + } + else + { + timeLeft = remaining / 1000; + interval = 10000; + cs = new CreatureSay(null, ChatType.ALLIANCE, "Notice", timeLeft + " seconds left."); + remaining -= 10000; + } + + if (cs != null) + { + for (Integer objectId : _players) + { + final Player player = World.getPlayer(objectId); + if ((player != null) && (player.getInstanceId() == _id)) + { + player.sendPacket(cs); + } + } + } + + cancelTimer(); + if (remaining >= 10000) + { + _checkTimeUpTask = ThreadPool.schedule(new CheckTimeUp(remaining), interval); + } + else + { + _checkTimeUpTask = ThreadPool.schedule(new TimeUp(), interval); + } + } + + public void cancelTimer() + { + if (_checkTimeUpTask != null) + { + _checkTimeUpTask.cancel(true); + } + } + + public void cancelEjectDeadPlayer(Player player) + { + final ScheduledFuture task = _ejectDeadTasks.remove(player.getObjectId()); + if (task != null) + { + task.cancel(true); + } + } + + /** + * This method is called when player dies inside instance. + * @param player + */ + public void notifyDeath(Player player) + { + if (!player.isOnEvent() && (_ejectTime > 0)) + { + // Proper system message doesn't exist in epilogue client. + player.sendMessage("If you are not resurrected within " + (_ejectTime / 1000 / 60) + " minutes, you will be expelled from the instance zone."); + + // Start eject task + _ejectDeadTasks.put(player.getObjectId(), ThreadPool.schedule(() -> + { + if (player.isDead() && (player.getInstanceId() == _id)) + { + player.setInstanceId(0); + if (_exitLocation != null) + { + player.teleToLocation(_exitLocation, true); + } + else + { + player.teleToLocation(TeleportWhereType.TOWN); + } + } + }, _ejectTime)); + } + } + + public class CheckTimeUp implements Runnable + { + private final int _remaining; + + public CheckTimeUp(int remaining) + { + _remaining = remaining; + } + + @Override + public void run() + { + doCheckTimeUp(_remaining); + } + } + + public class TimeUp implements Runnable + { + @Override + public void run() + { + InstanceManager.getInstance().destroyInstance(getId()); + } + } + + public InstanceReenterType getReenterType() + { + return _type; + } + + public void setReenterType(InstanceReenterType type) + { + _type = type; + } + + public List getReenterData() + { + return _resetData; + } + + public boolean isRemoveBuffEnabled() + { + return _removeBuffType != InstanceRemoveBuffType.NONE; + } + + public InstanceRemoveBuffType getRemoveBuffType() + { + return _removeBuffType; + } + + public List getBuffExceptionList() + { + return _exceptionList; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/instancezone/InstanceReenterType.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/instancezone/InstanceReenterType.java new file mode 100644 index 0000000..0c5202a --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/instancezone/InstanceReenterType.java @@ -0,0 +1,27 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.instancezone; + +/** + * @author FallenAngel + */ +public enum InstanceReenterType +{ + NONE, + ON_ENTER, + ON_FINISH, +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/instancezone/InstanceRemoveBuffType.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/instancezone/InstanceRemoveBuffType.java new file mode 100644 index 0000000..875b06c --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/instancezone/InstanceRemoveBuffType.java @@ -0,0 +1,28 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.instancezone; + +/** + * @author St3eT + */ +public enum InstanceRemoveBuffType +{ + NONE, + ALL, + WHITELIST, + BLACKLIST, +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/instancezone/InstanceWorld.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/instancezone/InstanceWorld.java new file mode 100644 index 0000000..fc20735 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/instancezone/InstanceWorld.java @@ -0,0 +1,420 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.instancezone; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import org.l2jmobius.gameserver.entity.World; +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.Npc; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.enums.player.TeleportWhereType; +import org.l2jmobius.gameserver.entity.actor.instance.Door; +import org.l2jmobius.gameserver.network.serverpackets.ServerPacket; +import org.l2jmobius.gameserver.util.ArrayUtil; +import org.l2jmobius.gameserver.util.StatSet; + +/** + * Basic instance zone data transfer object. + * @author Zoey76 + */ +public class InstanceWorld +{ + private Instance _instance; + private final Set _allowed = ConcurrentHashMap.newKeySet(); + private final StatSet _parameters = new StatSet(); + + /** + * Sets the instance. + * @param instance the instance + */ + public void setInstance(Instance instance) + { + _instance = instance; + } + + /** + * Gets the dynamically generated instance ID. + * @return the instance ID + */ + public int getInstanceId() + { + return _instance.getId(); + } + + /** + * Get template ID of instance world. + * @return instance template ID + */ + public int getTemplateId() + { + return _instance.getTemplateId(); + } + + public List getAllowed() + { + final List allowed = new ArrayList<>(_allowed.size()); + for (int playerId : _allowed) + { + final Player player = World.getPlayer(playerId); + if (player != null) + { + allowed.add(player); + } + } + + return allowed; + } + + public void removeAllowed(Player player) + { + _allowed.remove(player.getObjectId()); + } + + /** + * Add player who can enter to instance. + * @param player player instance + */ + public void addAllowed(Player player) + { + if (!_allowed.contains(player.getObjectId())) + { + _allowed.add(player.getObjectId()); + } + } + + /** + * Check if player can enter to instance. + * @param player player itself + * @return {@code true} when can enter, otherwise {@code false} + */ + public boolean isAllowed(Player player) + { + return _allowed.contains(player.getObjectId()); + } + + /** + * Teleport player out of instance. + * @param player player that should be moved out + */ + public void ejectPlayer(Player player) + { + if ((player != null) && (player.getInstanceId() == _instance.getId())) + { + player.setInstanceId(0); + if (_instance.getExitLoc() != null) + { + player.teleToLocation(_instance.getExitLoc(), true); + } + else + { + player.teleToLocation(TeleportWhereType.TOWN); + } + } + } + + /** + * Set instance world parameter. + * @param key parameter name + * @param value parameter value + */ + public void setParameter(String key, Object value) + { + if (value == null) + { + _parameters.remove(key); + } + else + { + _parameters.set(key, value); + } + } + + /** + * Get instance world parameters. + * @return instance parameters + */ + public StatSet getParameters() + { + return _parameters; + } + + /** + * Get status of instance world. + * @return instance status, otherwise 0 + */ + public int getStatus() + { + return _parameters.getInt("INSTANCE_STATUS", 0); + } + + /** + * Check if instance status is equal to {@code status}. + * @param status number used for status comparison + * @return {@code true} when instance status and {@code status} are equal, otherwise {@code false} + */ + public boolean isStatus(int status) + { + return getStatus() == status; + } + + /** + * Set status of instance world. + * @param value new world status + */ + public void setStatus(int value) + { + _parameters.set("INSTANCE_STATUS", value); + } + + /** + * Increment instance world status + * @return new world status + */ + public int incStatus() + { + final int status = getStatus() + 1; + setStatus(status); + return status; + } + + /** + * Get spawned NPCs from instance. + * @return set of NPCs from instance + */ + public Collection getNpcs() + { + return _instance.getNpcs(); + } + + /** + * Get spawned NPCs from instance with specific IDs. + * @param id IDs of NPCs which should be found + * @return list of filtered NPCs from instance + */ + public List getNpcs(int... id) + { + final List result = new ArrayList<>(); + for (Npc npc : _instance.getNpcs()) + { + if (ArrayUtil.contains(id, npc.getId())) + { + result.add(npc); + } + } + + return result; + } + + /** + * Get spawned NPCs from instance with specific IDs and class type. + * @param + * @param clazz + * @param ids IDs of NPCs which should be found + * @return list of filtered NPCs from instance + */ + @SafeVarargs + @SuppressWarnings("unchecked") + public final List getNpcs(Class clazz, int... ids) + { + final List result = new ArrayList<>(); + for (Npc npc : _instance.getNpcs()) + { + if (((ids.length == 0) || ArrayUtil.contains(ids, npc.getId())) && clazz.isInstance(npc)) + { + result.add((T) npc); + } + } + + return result; + } + + /** + * Get alive NPCs from instance. + * @return set of NPCs from instance + */ + public List getAliveNpcs() + { + final List result = new ArrayList<>(); + for (Npc npc : _instance.getNpcs()) + { + if (npc.getCurrentHp() > 0) + { + result.add(npc); + } + } + + return result; + } + + /** + * Get alive NPCs from instance with specific IDs. + * @param id IDs of NPCs which should be found + * @return list of filtered NPCs from instance + */ + public List getAliveNpcs(int... id) + { + final List result = new ArrayList<>(); + for (Npc npc : _instance.getNpcs()) + { + if ((npc.getCurrentHp() > 0) && ArrayUtil.contains(id, npc.getId())) + { + result.add(npc); + } + } + + return result; + } + + /** + * Get spawned and alive NPCs from instance with specific IDs and class type. + * @param + * @param clazz + * @param ids IDs of NPCs which should be found + * @return list of filtered NPCs from instance + */ + @SafeVarargs + @SuppressWarnings("unchecked") + public final List getAliveNpcs(Class clazz, int... ids) + { + final List result = new ArrayList<>(); + for (Npc npc : _instance.getNpcs()) + { + if ((((ids.length == 0) || ArrayUtil.contains(ids, npc.getId())) && (npc.getCurrentHp() > 0)) && clazz.isInstance(npc)) + { + result.add((T) npc); + } + } + + return result; + } + + /** + * Get first found spawned NPC with specific ID. + * @param id ID of NPC to be found + * @return first found NPC with specified ID, otherwise {@code null} + */ + public Npc getNpc(int id) + { + for (Npc npc : _instance.getNpcs()) + { + if (npc.getId() == id) + { + return npc; + } + } + + return null; + } + + /** + * Spawns group of instance NPCs + * @param groupName the name of group from XML definition to spawn + * @return list of spawned NPCs + */ + public List spawnGroup(String groupName) + { + return _instance.spawnGroup(groupName); + } + + /** + * Open a door if it is present in the instance and it is not open. + * @param doorId the ID of the door to open + */ + public void openDoor(int doorId) + { + final Door door = _instance.getDoor(doorId); + if ((door != null) && !door.isOpen()) + { + door.openMe(); + } + } + + /** + * Close a door if it is present in the instance and it is open. + * @param doorId the ID of the door to close + */ + public void closeDoor(int doorId) + { + final Door door = _instance.getDoor(doorId); + if ((door != null) && door.isOpen()) + { + door.closeMe(); + } + } + + /** + * Get all doors spawned inside instance world. + * @return collection of spawned doors + */ + public Collection getDoors() + { + return _instance.getDoors(); + } + + /** + * Get spawned door by template ID. + * @param doorId template ID of door + * @return instance of door if found, otherwise {@code null} + */ + public Door getDoor(int doorId) + { + return _instance.getDoor(doorId); + } + + /** + * Get count of players inside instance. + * @return players count inside instance + */ + public int getPlayersCount() + { + return _instance.getPlayers().size(); + } + + /** + * Destroy current instance world.
        + * Use this method to destroy instance world properly. + */ + public void destroy() + { + _instance.setEmptyDestroyTime(0); + _instance.setDuration(1000); + } + + /** + * Send packet to each player from instance world. + * @param packets packets to be send + */ + public void broadcastPacket(ServerPacket... packets) + { + for (int objectId : _instance.getPlayers()) + { + final Player player = World.getPlayer(objectId); + if ((player != null) && (player.getInstanceId() == _instance.getId())) + { + for (ServerPacket packet : packets) + { + player.sendPacket(packet); + } + } + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/Armor.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/Armor.java new file mode 100644 index 0000000..3ca400b --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/Armor.java @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.item; + +import org.l2jmobius.gameserver.entity.item.enums.BodyPart; +import org.l2jmobius.gameserver.entity.item.type.ArmorType; +import org.l2jmobius.gameserver.mechanics.skill.Skill; +import org.l2jmobius.gameserver.mechanics.skill.holders.SkillHolder; +import org.l2jmobius.gameserver.util.StatSet; + +/** + * This class is dedicated to the management of armors. + */ +public class Armor extends ItemTemplate +{ + /** + * Skill that activates when armor is enchanted +4. + */ + private SkillHolder _enchant4Skill = null; + private ArmorType _type; + + /** + * Constructor for Armor. + * @param set the StatSet designating the set of couples (key,value) characterizing the armor. + */ + public Armor(StatSet set) + { + super(set); + } + + @Override + public void set(StatSet set) + { + super.set(set); + _type = set.getEnum("armor_type", ArmorType.class, ArmorType.NONE); + + final BodyPart bodyPart = getBodyPart(); + if ((bodyPart == BodyPart.NECK) // + || (bodyPart == BodyPart.L_EAR) // + || (bodyPart == BodyPart.R_EAR) // + || (bodyPart == BodyPart.LR_EAR) // + || (bodyPart == BodyPart.L_FINGER) // + || (bodyPart == BodyPart.R_FINGER) // + || (bodyPart == BodyPart.LR_FINGER)) + { + _type1 = ItemTemplate.TYPE1_WEAPON_RING_EARRING_NECKLACE; + _type2 = ItemTemplate.TYPE2_ACCESSORY; + } + else + { + if ((_type == ArmorType.NONE) && (bodyPart == BodyPart.L_HAND)) + { + _type = ArmorType.SHIELD; + } + + _type1 = ItemTemplate.TYPE1_SHIELD_ARMOR; + _type2 = ItemTemplate.TYPE2_SHIELD_ARMOR; + } + + final String skill = set.getString("enchant4_skill", null); + if (skill == null) + { + return; + } + + final String[] info = skill.split("-"); + if ((info == null) || (info.length != 2)) + { + return; + } + + int id = 0; + int level = 0; + try + { + id = Integer.parseInt(info[0]); + level = Integer.parseInt(info[1]); + } + catch (Exception nfe) + { + // Incorrect syntax, don't add new skill. + LOGGER.info("> Could not parse " + skill + " in armor enchant skills! item " + this); + } + + if ((id > 0) && (level > 0)) + { + _enchant4Skill = new SkillHolder(id, level); + } + } + + /** + * @return the type of the armor. + */ + @Override + public ArmorType getItemType() + { + return _type; + } + + /** + * @return the ID of the item after applying the mask. + */ + @Override + public int getItemMask() + { + return _type.mask(); + } + + /** + * @return {@code true} if the item is an etc item, {@code false} otherwise. + */ + @Override + public boolean isArmor() + { + return true; + } + + /** + * @return skill that player get when has equipped armor +4 or more + */ + @Override + public Skill getEnchant4Skill() + { + return _enchant4Skill == null ? null : _enchant4Skill.getSkill(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/EtcItem.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/EtcItem.java new file mode 100644 index 0000000..20f1a88 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/EtcItem.java @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.item; + +import java.util.ArrayList; +import java.util.List; + +import org.l2jmobius.gameserver.entity.item.holders.ExtractableProduct; +import org.l2jmobius.gameserver.entity.item.type.ActionType; +import org.l2jmobius.gameserver.entity.item.type.EtcItemType; +import org.l2jmobius.gameserver.entity.itemcontainer.Inventory; +import org.l2jmobius.gameserver.util.StatSet; + +/** + * This class is dedicated to the management of EtcItem. + */ +public class EtcItem extends ItemTemplate +{ + private String _handler; + private EtcItemType _type; + private List _extractableItems; + private int _extractableCountMin; + private int _extractableCountMax; + private boolean _isBlessed; + + /** + * Constructor for EtcItem. + * @param set StatSet designating the set of couples (key,value) for description of the Etc + */ + public EtcItem(StatSet set) + { + super(set); + } + + @Override + public void set(StatSet set) + { + super.set(set); + _type = set.getEnum("etcitem_type", EtcItemType.class, EtcItemType.NONE); + + // l2j custom - EtcItemType.SHOT + switch (getDefaultAction()) + { + case SOULSHOT: + case SUMMON_SOULSHOT: + case SUMMON_SPIRITSHOT: + case SPIRITSHOT: + { + _type = EtcItemType.SHOT; + break; + } + } + + _type1 = ItemTemplate.TYPE1_ITEM_QUESTITEM_ADENA; + _type2 = ItemTemplate.TYPE2_OTHER; // default is other + + if (isQuestItem()) + { + _type2 = ItemTemplate.TYPE2_QUEST; + } + else if ((getId() == Inventory.ADENA_ID) || (getId() == Inventory.ANCIENT_ADENA_ID)) + { + _type2 = ItemTemplate.TYPE2_MONEY; + } + + _handler = set.getString("handler", null); // ! null ! + + _extractableCountMin = set.getInt("extractableCountMin", 0); + _extractableCountMax = set.getInt("extractableCountMax", 0); + if (_extractableCountMin > _extractableCountMax) + { + LOGGER.warning("Item " + this + " extractableCountMin is bigger than extractableCountMax!"); + } + + _isBlessed = set.getBoolean("blessed", false) || (((getDefaultAction() == ActionType.SPIRITSHOT) || (getDefaultAction() == ActionType.SOULSHOT)) && (getName() != null) && getName().contains("Blessed")); + } + + /** + * @return the type of Etc Item. + */ + @Override + public EtcItemType getItemType() + { + return _type; + } + + /** + * @return the ID of the Etc item after applying the mask. + */ + @Override + public int getItemMask() + { + return _type.mask(); + } + + /** + * @return {@code true} if the item is an etc item, {@code false} otherwise. + */ + @Override + public boolean isEtcItem() + { + return true; + } + + /** + * @return the handler name, null if no handler for item. + */ + public String getHandlerName() + { + return _handler; + } + + /** + * @return the extractable items list. + */ + public List getExtractableItems() + { + return _extractableItems; + } + + /** + * @return the minimum count of extractable items + */ + public int getExtractableCountMin() + { + return _extractableCountMin; + } + + /** + * @return the maximum count of extractable items + */ + public int getExtractableCountMax() + { + return _extractableCountMax; + } + + /** + * @param extractableProduct + */ + @Override + public void addCapsuledItem(ExtractableProduct extractableProduct) + { + if (_extractableItems == null) + { + _extractableItems = new ArrayList<>(); + } + + _extractableItems.add(extractableProduct); + } + + /** + * @return {@code true} if the item is blessed, {@code false} otherwise. + */ + public boolean isBlessed() + { + return _isBlessed; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/Henna.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/Henna.java new file mode 100644 index 0000000..98de829 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/Henna.java @@ -0,0 +1,183 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.item; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.l2jmobius.gameserver.entity.actor.enums.player.PlayerClass; +import org.l2jmobius.gameserver.util.StatSet; + +/** + * Class for the Henna object. + * @author Zoey76 + */ +public class Henna +{ + private final int _dyeId; + private final int _dyeItemId; + private final int _str; + private final int _con; + private final int _dex; + private final int _int; + private final int _men; + private final int _wit; + private final int _wearFee; + private final int _wearCount; + private final int _cancelFee; + private final int _cancelCount; + private final Set _wearClass; + + public Henna(StatSet set) + { + _dyeId = set.getInt("dyeId"); + _dyeItemId = set.getInt("dyeItemId"); + _str = set.getInt("str", 0); + _con = set.getInt("con", 0); + _dex = set.getInt("dex", 0); + _int = set.getInt("int", 0); + _men = set.getInt("men", 0); + _wit = set.getInt("wit", 0); + _wearFee = set.getInt("wear_fee"); + _wearCount = set.getInt("wear_count"); + _cancelFee = set.getInt("cancel_fee"); + _cancelCount = set.getInt("cancel_count"); + _wearClass = new HashSet<>(); + } + + /** + * @return the dye Id. + */ + public int getDyeId() + { + return _dyeId; + } + + /** + * @return the item Id, required for this dye. + */ + public int getDyeItemId() + { + return _dyeItemId; + } + + /** + * @return the STR stat. + */ + public int getStatSTR() + { + return _str; + } + + /** + * @return the CON stat. + */ + public int getStatCON() + { + return _con; + } + + /** + * @return the DEX stat. + */ + public int getStatDEX() + { + return _dex; + } + + /** + * @return the INT stat. + */ + public int getStatINT() + { + return _int; + } + + /** + * @return the MEN stat. + */ + public int getStatMEN() + { + return _men; + } + + /** + * @return the WIT stat. + */ + public int getStatWIT() + { + return _wit; + } + + /** + * @return the wear fee, cost for adding this dye to the player. + */ + public int getWearFee() + { + return _wearFee; + } + + /** + * @return the wear count, the required count to add this dye to the player. + */ + public int getWearCount() + { + return _wearCount; + } + + /** + * @return the cancel fee, cost for removing this dye from the player. + */ + public int getCancelFee() + { + return _cancelFee; + } + + /** + * @return the cancel count, the retrieved amount of dye items after removing the dye. + */ + public int getCancelCount() + { + return _cancelCount; + } + + /** + * @return the list with the allowed classes to wear this dye. + */ + public Set getAllowedWearClass() + { + return _wearClass; + } + + /** + * @param playerClass the class trying to wear this dye. + * @return {@code true} if the player is allowed to wear this dye, {@code false} otherwise. + */ + public boolean isAllowedClass(PlayerClass playerClass) + { + return _wearClass.contains(playerClass); + } + + /** + * @param wearClassIds the list of classes that can wear this dye. + */ + public void setWearClassIds(List wearClassIds) + { + _wearClass.addAll(wearClassIds); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/ItemTemplate.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/ItemTemplate.java new file mode 100644 index 0000000..76ecb1d --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/ItemTemplate.java @@ -0,0 +1,904 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.item; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.logging.Logger; + +import org.l2jmobius.commons.util.StringUtil; +import org.l2jmobius.gameserver.config.GeneralConfig; +import org.l2jmobius.gameserver.config.OlympiadConfig; +import org.l2jmobius.gameserver.config.PlayerConfig; +import org.l2jmobius.gameserver.entity.WorldObject; +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.item.enums.BodyPart; +import org.l2jmobius.gameserver.entity.item.enums.ItemGrade; +import org.l2jmobius.gameserver.entity.item.holders.ExtractableProduct; +import org.l2jmobius.gameserver.entity.item.instance.Item; +import org.l2jmobius.gameserver.entity.item.type.ActionType; +import org.l2jmobius.gameserver.entity.item.type.CrystalType; +import org.l2jmobius.gameserver.entity.item.type.EtcItemType; +import org.l2jmobius.gameserver.entity.item.type.ItemType; +import org.l2jmobius.gameserver.entity.item.type.MaterialType; +import org.l2jmobius.gameserver.mechanics.conditions.Condition; +import org.l2jmobius.gameserver.mechanics.events.ListenersContainer; +import org.l2jmobius.gameserver.mechanics.skill.Skill; +import org.l2jmobius.gameserver.mechanics.skill.holders.SkillHolder; +import org.l2jmobius.gameserver.mechanics.stats.functions.AbstractFunction; +import org.l2jmobius.gameserver.mechanics.stats.functions.FuncTemplate; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; +import org.l2jmobius.gameserver.util.StatSet; + +/** + * This class contains all informations concerning the item (weapon, armor, etc).
        + * Mother class of : + *
          + *
        • Armor
        • + *
        • EtcItem
        • + *
        • Weapon
        • + *
        + */ +public abstract class ItemTemplate extends ListenersContainer +{ + protected static final Logger LOGGER = Logger.getLogger(ItemTemplate.class.getName()); + + public static final int TYPE1_WEAPON_RING_EARRING_NECKLACE = 0; + public static final int TYPE1_SHIELD_ARMOR = 1; + public static final int TYPE1_ITEM_QUESTITEM_ADENA = 4; + + public static final int TYPE2_WEAPON = 0; + public static final int TYPE2_SHIELD_ARMOR = 1; + public static final int TYPE2_ACCESSORY = 2; + public static final int TYPE2_QUEST = 3; + public static final int TYPE2_MONEY = 4; + public static final int TYPE2_OTHER = 5; + + private int _itemId; + private int _displayId; + private String _name; + private String _icon; + private int _weight; + private boolean _stackable; + private MaterialType _materialType; + private CrystalType _crystalType; + private int _equipReuseDelay; + private int _duration; + private long _time; + private int _autoDestroyTime; + private BodyPart _bodyPart; + private int _referencePrice; + private int _crystalCount; + private boolean _sellable; + private boolean _dropable; + private boolean _destroyable; + private boolean _tradeable; + private boolean _depositable; + private boolean _enchantable; + private boolean _elementable; + private boolean _questItem; + private boolean _freightable; + private boolean _allowSelfResurrection; + private boolean _isOlyRestricted; + private boolean _forNpc; + private boolean _common; + private boolean _heroItem; + private boolean _pvpItem; + private boolean _immediateEffect; + private boolean _exImmediateEffect; + private int _defaultEnchantLevel; + private ActionType _defaultAction; + + protected int _type1; // Needed for item list (inventory). + protected int _type2; // Different lists for armor, weapon, etc. + protected List _funcTemplates; + protected List _preConditions; + private SkillHolder[] _skillHolder; + private SkillHolder _unequipSkill = null; + + private int _useSkillDisTime; + protected int _reuseDelay; + private int _sharedReuseGroup; + + /** + * Constructor of the Item that fill class variables. + * @param set : StatSet corresponding to a set of couples (key,value) for description of the item + */ + protected ItemTemplate(StatSet set) + { + set(set); + } + + public void set(StatSet set) + { + _itemId = set.getInt("item_id"); + _displayId = set.getInt("displayId", _itemId); + _name = set.getString("name"); + _icon = set.getString("icon", null); + _weight = set.getInt("weight", 0); + _materialType = set.getEnum("material", MaterialType.class, MaterialType.STEEL); + _equipReuseDelay = set.getInt("equip_reuse_delay", 0) * 1000; + _duration = set.getInt("duration", -1); + _time = set.getInt("time", -1); + _autoDestroyTime = set.getInt("auto_destroy_time", -1) * 1000; + _bodyPart = BodyPart.fromName(set.getString("bodypart", "none")); + _referencePrice = set.getInt("price", 0); + _crystalType = set.getEnum("crystal_type", CrystalType.class, CrystalType.NONE); + _crystalCount = set.getInt("crystal_count", 0); + _stackable = set.getBoolean("is_stackable", false); + _sellable = set.getBoolean("is_sellable", true); + _dropable = set.getBoolean("is_dropable", true); + _destroyable = set.getBoolean("is_destroyable", true); + _tradeable = set.getBoolean("is_tradable", true); + _questItem = set.getBoolean("is_questitem", false); + _depositable = set.getBoolean("is_depositable", true); + _elementable = set.getBoolean("element_enabled", false); + _enchantable = set.getBoolean("enchant_enabled", false); + _freightable = set.getBoolean("is_freightable", false); + _allowSelfResurrection = set.getBoolean("allow_self_resurrection", false); + _isOlyRestricted = set.getBoolean("is_oly_restricted", false); + _forNpc = set.getBoolean("for_npc", false); + _immediateEffect = set.getBoolean("immediate_effect", false); + _exImmediateEffect = set.getBoolean("ex_immediate_effect", false); + _defaultAction = set.getEnum("default_action", ActionType.class, ActionType.NONE); + _useSkillDisTime = set.getInt("useSkillDisTime", 0); + _defaultEnchantLevel = set.getInt("enchanted", 0); + _reuseDelay = set.getInt("reuse_delay", 0); + _sharedReuseGroup = set.getInt("shared_reuse_group", 0); + + String skills = set.getString("item_skill", null); + if (skills != null) + { + final String[] skillsSplit = skills.split(";"); + _skillHolder = new SkillHolder[skillsSplit.length]; + int used = 0; + for (String element : skillsSplit) + { + try + { + final String[] skillSplit = element.split("-"); + final int id = Integer.parseInt(skillSplit[0]); + if (id == 0) + { + LOGGER.info("Ignoring item_skill(" + element + ") for item " + this + ". Skill id is 0!"); + continue; + } + + final int level = Integer.parseInt(skillSplit[1]); + if (level == 0) + { + LOGGER.info("Ignoring item_skill(" + element + ") for item " + this + ". Skill level is 0!"); + continue; + } + + _skillHolder[used] = new SkillHolder(id, level); + ++used; + } + catch (Exception e) + { + LOGGER.warning("Failed to parse item_skill(" + element + ") for item " + this + "! Format: SkillId0-SkillLevel0[;SkillIdN-SkillLevelN]"); + } + } + + // this is only loading? just don't leave a null or use a collection? + if (used != _skillHolder.length) + { + final SkillHolder[] skillHolder = new SkillHolder[used]; + System.arraycopy(_skillHolder, 0, skillHolder, 0, used); + _skillHolder = skillHolder; + } + } + + skills = set.getString("unequip_skill", null); + if (skills != null) + { + final String[] info = skills.split("-"); + if ((info != null) && (info.length == 2)) + { + int id = 0; + int level = 0; + try + { + id = Integer.parseInt(info[0]); + level = Integer.parseInt(info[1]); + } + catch (Exception nfe) + { + // Incorrect syntax, don't add new skill. + LOGGER.info("Could not parse " + skills + " in weapon unequip skills! item " + this); + } + + if ((id > 0) && (level > 0)) + { + _unequipSkill = new SkillHolder(id, level); + } + } + } + + _common = ((_itemId >= 11605) && (_itemId <= 12361)); + _heroItem = ((_itemId >= 6611) && (_itemId <= 6621)) || ((_itemId >= 9388) && (_itemId <= 9390)) || (_itemId == 6842); + _pvpItem = ((_itemId >= 10667) && (_itemId <= 10835)) || ((_itemId >= 12852) && (_itemId <= 12977)) || ((_itemId >= 14363) && (_itemId <= 14525)) || (_itemId == 14528) || (_itemId == 14529) || (_itemId == 14558) || ((_itemId >= 15913) && (_itemId <= 16024)) || ((_itemId >= 16134) && (_itemId <= 16147)) || (_itemId == 16149) || (_itemId == 16151) || (_itemId == 16153) || (_itemId == 16155) || (_itemId == 16157) || (_itemId == 16159) || ((_itemId >= 16168) && (_itemId <= 16176)) || ((_itemId >= 16179) && (_itemId <= 16220)); + } + + /** + * Returns the itemType. + * @return Enum + */ + public abstract ItemType getItemType(); + + /** + * Verifies if the item is an etc item. + * @return {@code true} if the item is an etc item, {@code false} otherwise. + */ + public boolean isEtcItem() + { + return false; + } + + /** + * Verifies if the item is an armor. + * @return {@code true} if the item is an armor, {@code false} otherwise. + */ + public boolean isArmor() + { + return false; + } + + /** + * Verifies if the item is a weapon. + * @return {@code true} if the item is a weapon, {@code false} otherwise. + */ + public boolean isWeapon() + { + return false; + } + + /** + * Verifies if the item is a magic weapon. + * @return {@code true} if the weapon is magic, {@code false} otherwise. + */ + public boolean isMagicWeapon() + { + return false; + } + + /** + * @return the _equipReuseDelay + */ + public int getEquipReuseDelay() + { + return _equipReuseDelay; + } + + /** + * Returns the duration of the item + * @return int + */ + public int getDuration() + { + return _duration; + } + + /** + * Returns the time of the item + * @return long + */ + public long getTime() + { + return _time; + } + + /** + * @return the auto destroy time of the item in seconds: 0 or less - default + */ + public int getAutoDestroyTime() + { + return _autoDestroyTime; + } + + /** + * Returns the ID of the item + * @return int + */ + public int getId() + { + return _itemId; + } + + /** + * Returns the ID of the item + * @return int + */ + public int getDisplayId() + { + return _displayId; + } + + public abstract int getItemMask(); + + /** + * Return the type of material of the item + * @return MaterialType + */ + public MaterialType getMaterialType() + { + return _materialType; + } + + /** + * Returns the type 2 of the item + * @return int + */ + public int getType2() + { + return _type2; + } + + /** + * Returns the weight of the item + * @return int + */ + public int getWeight() + { + return _weight; + } + + /** + * Returns if the item is crystallizable + * @return boolean + */ + public boolean isCrystallizable() + { + return (_crystalType != CrystalType.NONE) && (_crystalCount > 0); + } + + /** + * @return return General item grade + */ + public ItemGrade getItemGrade() + { + return ItemGrade.valueOf(_crystalType); + } + + /** + * Return the type of crystal if item is crystallizable + * @return CrystalType + */ + public CrystalType getCrystalType() + { + return _crystalType; + } + + /** + * Return the ID of crystal if item is crystallizable + * @return int + */ + public int getCrystalItemId() + { + return _crystalType.getCrystalId(); + } + + /** + * Returns the crystal type. + * @return the grade of the item. + */ + public CrystalType getCrystalTypePlus() + { + return _crystalType; + } + + /** + * @return the quantity of crystals for crystallization. + */ + public int getCrystalCount() + { + return _crystalCount; + } + + /** + * @param enchantLevel + * @return the quantity of crystals for crystallization on specific enchant level + */ + public int getCrystalCount(int enchantLevel) + { + if (enchantLevel > 3) + { + switch (_type2) + { + case TYPE2_SHIELD_ARMOR: + case TYPE2_ACCESSORY: + { + return _crystalCount + (_crystalType.getCrystalEnchantBonusArmor() * ((3 * enchantLevel) - 6)); + } + case TYPE2_WEAPON: + { + return _crystalCount + (_crystalType.getCrystalEnchantBonusWeapon() * ((2 * enchantLevel) - 3)); + } + default: + { + return _crystalCount; + } + } + } + + if (enchantLevel <= 0) + { + return _crystalCount; + } + + switch (_type2) + { + case TYPE2_SHIELD_ARMOR: + case TYPE2_ACCESSORY: + { + return _crystalCount + (_crystalType.getCrystalEnchantBonusArmor() * enchantLevel); + } + case TYPE2_WEAPON: + { + return _crystalCount + (_crystalType.getCrystalEnchantBonusWeapon() * enchantLevel); + } + default: + { + return _crystalCount; + } + } + } + + /** + * @return the name of the item. + */ + public String getName() + { + return _name; + } + + /** + * @return the part of the body used with the item. + */ + public BodyPart getBodyPart() + { + return _bodyPart; + } + + /** + * @return the type 1 of the item. + */ + public int getType1() + { + return _type1; + } + + /** + * @return {@code true} if the item is stackable, {@code false} otherwise. + */ + public boolean isStackable() + { + return _stackable; + } + + /** + * @return {@code true} if the item can be equipped, {@code false} otherwise. + */ + public boolean isEquipable() + { + return (_bodyPart != BodyPart.NONE) && !(getItemType() instanceof EtcItemType); + } + + /** + * @return the price of reference of the item. + */ + public int getReferencePrice() + { + return _referencePrice; + } + + /** + * @return {@code true} if the item can be sold, {@code false} otherwise. + */ + public boolean isSellable() + { + return _sellable; + } + + /** + * @return {@code true} if the item can be dropped, {@code false} otherwise. + */ + public boolean isDropable() + { + return _dropable; + } + + /** + * @return {@code true} if the item can be destroyed, {@code false} otherwise. + */ + public boolean isDestroyable() + { + return _destroyable; + } + + /** + * @return {@code true} if the item can be traded, {@code false} otherwise. + */ + public boolean isTradeable() + { + return _tradeable; + } + + /** + * @return {@code true} if the item can be put into warehouse, {@code false} otherwise. + */ + public boolean isDepositable() + { + return _depositable; + } + + /** + * This method also check the enchant blacklist. + * @return {@code true} if the item can be enchanted, {@code false} otherwise. + */ + public boolean isEnchantable() + { + return (Arrays.binarySearch(PlayerConfig.ENCHANT_BLACKLIST, _itemId) < 0) && _enchantable; + } + + /** + * @return {@code true} if the item can be elemented, {@code false} otherwise. + */ + public boolean isElementable() + { + return _elementable; + } + + /** + * Returns if item is common + * @return boolean + */ + public boolean isCommon() + { + return _common; + } + + /** + * Returns if item is hero-only + * @return + */ + public boolean isHeroItem() + { + return _heroItem; + } + + /** + * Returns if item is pvp + * @return + */ + public boolean isPvpItem() + { + return _pvpItem; + } + + public boolean isPotion() + { + return getItemType() == EtcItemType.POTION; + } + + public boolean isElixir() + { + return getItemType() == EtcItemType.ELIXIR; + } + + public boolean isScroll() + { + return getItemType() == EtcItemType.SCROLL; + } + + /** + * Get the functions used by this item. + * @param item : Item pointing out the item + * @param creature : Creature pointing out the player + * @return the list of functions + */ + public List getStatFuncs(Item item, Creature creature) + { + if ((_funcTemplates == null) || _funcTemplates.isEmpty()) + { + return Collections. emptyList(); + } + + final List funcs = new ArrayList<>(_funcTemplates.size()); + for (FuncTemplate t : _funcTemplates) + { + final AbstractFunction f = t.getFunc(creature, creature, item, item); + if (f != null) + { + funcs.add(f); + } + } + + return funcs; + } + + /** + * Checks if a function of the specified class is already attached to this item. + * @param functionClass the class of the function to search for + * @return {@code true} if a function of the specified class exists, {@code false} otherwise + */ + public boolean hasFunction(Class functionClass) + { + if ((_funcTemplates == null) || _funcTemplates.isEmpty()) + { + return false; + } + + for (FuncTemplate template : _funcTemplates) + { + if (template.getClass().equals(functionClass)) + { + return true; + } + } + + return false; + } + + /** + * Add the FuncTemplate f to the list of functions used with the item + * @param f : FuncTemplate to add + */ + public void attach(FuncTemplate f) + { + if (_funcTemplates == null) + { + _funcTemplates = new ArrayList<>(1); + } + + _funcTemplates.add(f); + } + + public void attach(Condition c) + { + if (_preConditions == null) + { + _preConditions = new ArrayList<>(1); + } + + if (!_preConditions.contains(c)) + { + _preConditions.add(c); + } + } + + public boolean hasSkills() + { + return _skillHolder != null; + } + + /** + * Method to retrieve skills linked to this item armor and weapon: passive skills etcitem: skills used on item use <-- ??? + * @return Skills linked to this item as SkillHolder[] + */ + public SkillHolder[] getSkills() + { + return _skillHolder; + } + + /** + * @return skill that activates, when player unequip this weapon or armor + */ + public Skill getUnequipSkill() + { + return _unequipSkill == null ? null : _unequipSkill.getSkill(); + } + + public boolean checkCondition(Creature creature, WorldObject object, boolean sendMessage) + { + if (creature.isGM() && !GeneralConfig.GM_ITEM_RESTRICTION) + { + return true; + } + + // Don't allow hero equipment and restricted items during Olympiad. + if ((isOlyRestrictedItem() || _heroItem) && creature.isPlayer() && creature.asPlayer().isInOlympiadMode()) + { + if (isEquipable()) + { + creature.sendPacket(SystemMessageId.YOU_CANNOT_EQUIP_THAT_ITEM_IN_A_GRAND_OLYMPIAD_GAMES_MATCH); + } + else + { + creature.sendPacket(SystemMessageId.YOU_CANNOT_USE_THAT_ITEM_IN_A_GRAND_OLYMPIAD_GAMES_MATCH); + } + + return false; + } + + if (!isConditionAttached()) + { + return true; + } + + final Creature target = object.isCreature() ? object.asCreature() : null; + for (Condition preCondition : _preConditions) + { + if (preCondition == null) + { + continue; + } + + if (!preCondition.test(creature, target, null, null)) + { + if (creature.isSummon()) + { + creature.sendPacket(SystemMessageId.THIS_PET_CANNOT_USE_THIS_ITEM); + return false; + } + + if (sendMessage) + { + final String msg = preCondition.getMessage(); + final int msgId = preCondition.getMessageId(); + if (msg != null) + { + creature.sendMessage(msg); + } + else if (msgId != 0) + { + final SystemMessage sm = new SystemMessage(msgId); + if (preCondition.isAddName()) + { + sm.addItemName(_itemId); + } + + creature.sendPacket(sm); + } + } + + return false; + } + } + + return true; + } + + public boolean isConditionAttached() + { + return (_preConditions != null) && !_preConditions.isEmpty(); + } + + public boolean isQuestItem() + { + return _questItem; + } + + public boolean isFreightable() + { + return _freightable; + } + + public boolean isAllowSelfResurrection() + { + return _allowSelfResurrection; + } + + public boolean isOlyRestrictedItem() + { + return _isOlyRestricted || OlympiadConfig.LIST_OLY_RESTRICTED_ITEMS.contains(_itemId); + } + + public boolean isForNpc() + { + return _forNpc; + } + + /** + * Verifies if the item has effects immediately.
        + * Used for herbs mostly. + * @return {@code true} if the item applies effects immediately, {@code false} otherwise + */ + public boolean hasExImmediateEffect() + { + return _exImmediateEffect; + } + + /** + * Verifies if the item has effects immediately. + * @return {@code true} if the item applies effects immediately, {@code false} otherwise + */ + public boolean hasImmediateEffect() + { + return _immediateEffect; + } + + /** + * @return the _default_action + */ + public ActionType getDefaultAction() + { + return _defaultAction; + } + + public int useSkillDisTime() + { + return _useSkillDisTime; + } + + /** + * Gets the item reuse delay time in seconds. + * @return the reuse delay time + */ + public int getReuseDelay() + { + return _reuseDelay; + } + + /** + * Gets the shared reuse group.
        + * Items with the same reuse group will render reuse delay upon those items when used. + * @return the shared reuse group + */ + public int getSharedReuseGroup() + { + return _sharedReuseGroup; + } + + /** + * Usable in HTML windows. + * @return the icon link in client files + */ + public String getIcon() + { + return _icon; + } + + public int getDefaultEnchantLevel() + { + return _defaultEnchantLevel; + } + + public boolean isPetItem() + { + return getItemType() == EtcItemType.PET_COLLAR; + } + + /** + * @param extractableProduct + */ + public void addCapsuledItem(ExtractableProduct extractableProduct) + { + } + + public Skill getEnchant4Skill() + { + return null; + } + + /** + * Returns the name of the item followed by the item ID. + * @return the name and the ID of the item + */ + @Override + public String toString() + { + return StringUtil.concat(_name, "(", String.valueOf(_itemId), ")"); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/WarehouseItem.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/WarehouseItem.java new file mode 100644 index 0000000..f6802e9 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/WarehouseItem.java @@ -0,0 +1,259 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.item; + +import org.l2jmobius.gameserver.entity.item.enums.BodyPart; +import org.l2jmobius.gameserver.entity.item.instance.Item; +import org.l2jmobius.gameserver.entity.item.type.CrystalType; +import org.l2jmobius.gameserver.entity.item.type.ItemType; + +/** + * This class contains Item
        + * Use to sort Item of : + *
          + *
        • Armor
        • + *
        • EtcItem
        • + *
        • Weapon
        • + *
        + * @version $Revision: 1.7.2.2.2.5 $ $Date: 2005/04/06 18:25:18 $ + */ +public class WarehouseItem +{ + private final ItemTemplate _item; + private final int _object; + private final long _count; + private final int _owner; + private final int _locationSlot; + private final int _enchant; + private final CrystalType _grade; + private boolean _isAugmented; + private int _augmentationId; + private final int _customType1; + private final int _customType2; + private final int _mana; + private final int _time; + + public WarehouseItem(Item item) + { + _item = item.getTemplate(); + _object = item.getObjectId(); + _count = item.getCount(); + _owner = item.getOwnerId(); + _locationSlot = item.getLocationSlot(); + _enchant = item.getEnchantLevel(); + _customType1 = item.getCustomType1(); + _customType2 = item.getCustomType2(); + _grade = item.getTemplate().getCrystalType(); + if (item.isAugmented()) + { + _isAugmented = true; + _augmentationId = item.getAugmentation().getAugmentationId(); + } + else + { + _isAugmented = false; + } + + _mana = item.getMana(); + _time = item.isTimeLimitedItem() ? (int) (item.getRemainingTime() / 1000) : -1; + } + + /** + * @return the item. + */ + public ItemTemplate getItem() + { + return _item; + } + + /** + * @return the unique objectId. + */ + public int getObjectId() + { + return _object; + } + + /** + * @return the owner. + */ + public int getOwnerId() + { + return _owner; + } + + /** + * @return the location slot. + */ + public int getLocationSlot() + { + return _locationSlot; + } + + /** + * @return the count. + */ + public long getCount() + { + return _count; + } + + /** + * @return the first type. + */ + public int getType1() + { + return _item.getType1(); + } + + /** + * @return the second type. + */ + public int getType2() + { + return _item.getType2(); + } + + /** + * @return the second type. + */ + public ItemType getItemType() + { + return _item.getItemType(); + } + + /** + * @return the ItemId. + */ + public int getItemId() + { + return _item.getId(); + } + + /** + * @return the part of body used with this item. + */ + public BodyPart getBodyPart() + { + return _item.getBodyPart(); + } + + /** + * @return the enchant level. + */ + public int getEnchantLevel() + { + return _enchant; + } + + /** + * @return the item grade + */ + public CrystalType getItemGrade() + { + return _grade; + } + + /** + * @return {@code true} if the item is a weapon, {@code false} otherwise. + */ + public boolean isWeapon() + { + return _item instanceof Weapon; + } + + /** + * @return {@code true} if the item is an armor, {@code false} otherwise. + */ + public boolean isArmor() + { + return _item instanceof Armor; + } + + /** + * @return {@code true} if the item is an etc item, {@code false} otherwise. + */ + public boolean isEtcItem() + { + return _item instanceof EtcItem; + } + + /** + * @return the name of the item + */ + public String getItemName() + { + return _item.getName(); + } + + /** + * @return {@code true} if the item is augmented, {@code false} otherwise. + */ + public boolean isAugmented() + { + return _isAugmented; + } + + /** + * @return the augmentation If. + */ + public int getAugmentationId() + { + return _augmentationId; + } + + /** + * @return the name of the item + */ + public String getName() + { + return _item.getName(); + } + + public int getCustomType1() + { + return _customType1; + } + + public int getCustomType2() + { + return _customType2; + } + + public int getMana() + { + return _mana; + } + + public int getTime() + { + return _time; + } + + /** + * @return the name of the item + */ + @Override + public String toString() + { + return _item.toString(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/Weapon.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/Weapon.java new file mode 100644 index 0000000..e2ee5b6 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/Weapon.java @@ -0,0 +1,461 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.item; + +import java.util.Collections; +import java.util.List; + +import org.l2jmobius.commons.util.StringUtil; +import org.l2jmobius.gameserver.entity.World; +import org.l2jmobius.gameserver.entity.WorldObject; +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.Npc; +import org.l2jmobius.gameserver.entity.item.type.WeaponType; +import org.l2jmobius.gameserver.mechanics.conditions.Condition; +import org.l2jmobius.gameserver.mechanics.conditions.ConditionGameChance; +import org.l2jmobius.gameserver.mechanics.events.EventDispatcher; +import org.l2jmobius.gameserver.mechanics.events.EventType; +import org.l2jmobius.gameserver.mechanics.events.holders.actor.npc.OnNpcSkillSee; +import org.l2jmobius.gameserver.mechanics.skill.Skill; +import org.l2jmobius.gameserver.mechanics.skill.holders.SkillHolder; +import org.l2jmobius.gameserver.mechanics.stats.Formulas; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; +import org.l2jmobius.gameserver.util.StatSet; + +/** + * This class is dedicated to the management of weapons. + */ +public class Weapon extends ItemTemplate +{ + private WeaponType _type; + private boolean _isMagicWeapon; + private int _soulShotCount; + private int _spiritShotCount; + private int _mpConsume; + private int _baseAttackRange; + private int _baseAttackRadius; + private int _baseAttackAngle; + /** + * Skill that activates when item is enchanted +4 (for duals). + */ + private SkillHolder _enchant4Skill = null; + + // Attached skills for Special Abilities + private SkillHolder _skillsOnMagic; + private Condition _skillsOnMagicCondition = null; + private SkillHolder _skillsOnCrit; + private Condition _skillsOnCritCondition = null; + + private int _reducedSoulshot; + private int _reducedSoulshotChance; + + private int _reducedMpConsume; + private int _reducedMpConsumeChance; + + private boolean _isForceEquip; + private boolean _isAttackWeapon; + private boolean _useWeaponSkillsOnly; + + /** + * Constructor for Weapon. + * @param set the StatSet designating the set of couples (key,value) characterizing the weapon. + */ + public Weapon(StatSet set) + { + super(set); + } + + @Override + public void set(StatSet set) + { + super.set(set); + _type = WeaponType.valueOf(set.getString("weapon_type", "none").toUpperCase()); + _type1 = ItemTemplate.TYPE1_WEAPON_RING_EARRING_NECKLACE; + _type2 = ItemTemplate.TYPE2_WEAPON; + _isMagicWeapon = set.getBoolean("is_magic_weapon", false); + _soulShotCount = set.getInt("soulshots", 0); + _spiritShotCount = set.getInt("spiritshots", 0); + _mpConsume = set.getInt("mp_consume", 0); + _baseAttackRange = set.getInt("attack_range", 40); + final String[] damageRange = set.getString("damage_range", "").split(";"); // 0?;0?;fan sector;base attack angle + if ((damageRange.length > 1) && StringUtil.isNumeric(damageRange[2]) && StringUtil.isNumeric(damageRange[3])) + { + _baseAttackRadius = Integer.parseInt(damageRange[2]); + _baseAttackAngle = Integer.parseInt(damageRange[3]); + } + else + { + _baseAttackRadius = 40; + _baseAttackAngle = 0; + } + + final String[] reducedSoulshots = set.getString("reduced_soulshot", "").split(","); + _reducedSoulshotChance = (reducedSoulshots.length == 2) ? Integer.parseInt(reducedSoulshots[0]) : 0; + _reducedSoulshot = (reducedSoulshots.length == 2) ? Integer.parseInt(reducedSoulshots[1]) : 0; + + final String[] reducedMpConsume = set.getString("reduced_mp_consume", "").split(","); + _reducedMpConsumeChance = (reducedMpConsume.length == 2) ? Integer.parseInt(reducedMpConsume[0]) : 0; + _reducedMpConsume = (reducedMpConsume.length == 2) ? Integer.parseInt(reducedMpConsume[1]) : 0; + + String skill = set.getString("enchant4_skill", null); + if (skill != null) + { + final String[] info = skill.split("-"); + if ((info != null) && (info.length == 2)) + { + int id = 0; + int level = 0; + try + { + id = Integer.parseInt(info[0]); + level = Integer.parseInt(info[1]); + } + catch (Exception nfe) + { + // Incorrect syntax, do not add new skill. + LOGGER.info("> Could not parse " + skill + " in weapon enchant skills! item " + this); + } + + if ((id > 0) && (level > 0)) + { + _enchant4Skill = new SkillHolder(id, level); + } + } + } + + skill = set.getString("onmagic_skill", null); + if (skill != null) + { + final String[] info = skill.split("-"); + final int chance = set.getInt("onmagic_chance", 100); + if ((info != null) && (info.length == 2)) + { + int id = 0; + int level = 0; + try + { + id = Integer.parseInt(info[0]); + level = Integer.parseInt(info[1]); + } + catch (Exception nfe) + { + // Incorrect syntax, don't add new skill. + LOGGER.info("> Could not parse " + skill + " in weapon onmagic skills! item " + this); + } + + if ((id > 0) && (level > 0) && (chance > 0)) + { + _skillsOnMagic = new SkillHolder(id, level); + _skillsOnMagicCondition = new ConditionGameChance(chance); + } + } + } + + skill = set.getString("oncrit_skill", null); + if (skill != null) + { + final String[] info = skill.split("-"); + final int chance = set.getInt("oncrit_chance", 100); + if ((info != null) && (info.length == 2)) + { + int id = 0; + int level = 0; + try + { + id = Integer.parseInt(info[0]); + level = Integer.parseInt(info[1]); + } + catch (Exception nfe) + { + // Incorrect syntax, don't add new skill. + LOGGER.info("> Could not parse " + skill + " in weapon oncrit skills! item " + this); + } + + if ((id > 0) && (level > 0) && (chance > 0)) + { + _skillsOnCrit = new SkillHolder(id, level); + _skillsOnCritCondition = new ConditionGameChance(chance); + } + } + } + + _isForceEquip = set.getBoolean("isForceEquip", false); + _isAttackWeapon = set.getBoolean("isAttackWeapon", true); + _useWeaponSkillsOnly = set.getBoolean("useWeaponSkillsOnly", false); + + // Check if ranged weapon reuse delay is missing. + if ((_reuseDelay == 0) && _type.isRanged()) + { + _reuseDelay = 1500; + } + } + + /** + * @return the type of Weapon + */ + @Override + public WeaponType getItemType() + { + return _type; + } + + /** + * @return the ID of the Etc item after applying the mask. + */ + @Override + public int getItemMask() + { + return _type.mask(); + } + + /** + * @return {@code true} if the item is a weapon, {@code false} otherwise. + */ + @Override + public boolean isWeapon() + { + return true; + } + + /** + * @return {@code true} if the weapon is magic, {@code false} otherwise. + */ + @Override + public boolean isMagicWeapon() + { + return _isMagicWeapon; + } + + /** + * @return the quantity of SoulShot used. + */ + public int getSoulShotCount() + { + return _soulShotCount; + } + + /** + * @return the quantity of SpiritShot used. + */ + public int getSpiritShotCount() + { + return _spiritShotCount; + } + + /** + * @return the reduced quantity of SoultShot used. + */ + public int getReducedSoulShot() + { + return _reducedSoulshot; + } + + /** + * @return the chance to use Reduced SoultShot. + */ + public int getReducedSoulShotChance() + { + return _reducedSoulshotChance; + } + + /** + * @return the MP consumption with the weapon. + */ + public int getMpConsume() + { + return _mpConsume; + } + + public int getBaseAttackRange() + { + return _baseAttackRange; + } + + public int getBaseAttackRadius() + { + return _baseAttackRadius; + } + + public int getBaseAttackAngle() + { + return _baseAttackAngle; + } + + /** + * @return the reduced MP consumption with the weapon. + */ + public int getReducedMpConsume() + { + return _reducedMpConsume; + } + + /** + * @return the chance to use getReducedMpConsume() + */ + public int getReducedMpConsumeChance() + { + return _reducedMpConsumeChance; + } + + /** + * @return the skill that player get when has equipped weapon +4 or more (for duals SA). + */ + @Override + public Skill getEnchant4Skill() + { + return _enchant4Skill == null ? null : _enchant4Skill.getSkill(); + } + + /** + * @return {@code true} if the weapon is force equip, {@code false} otherwise. + */ + public boolean isForceEquip() + { + return _isForceEquip; + } + + /** + * @return {@code true} if the weapon is attack weapon, {@code false} otherwise. + */ + public boolean isAttackWeapon() + { + return _isAttackWeapon; + } + + /** + * @return {@code true} if the weapon is skills only, {@code false} otherwise. + */ + public boolean useWeaponSkillsOnly() + { + return _useWeaponSkillsOnly; + } + + /** + * @param caster the Creature pointing out the caster + * @param target the Creature pointing out the target + */ + public void castOnCriticalSkill(Creature caster, Creature target) + { + if (_skillsOnCrit == null) + { + return; + } + + final Skill onCritSkill = _skillsOnCrit.getSkill(); + if ((_skillsOnCritCondition != null) && !_skillsOnCritCondition.test(caster, target, onCritSkill)) + { + // Chance not met + return; + } + + if (!onCritSkill.checkCondition(caster, target, false)) + { + // Skill condition not met. + return; + } + + onCritSkill.activateSkill(caster, Collections.singletonList(target)); + } + + /** + * @param caster the Creature pointing out the caster + * @param target the Creature pointing out the target + * @param trigger the Skill pointing out the skill triggering this action + */ + public void castOnMagicSkill(Creature caster, Creature target, Skill trigger) + { + if (_skillsOnMagic == null) + { + return; + } + + final Skill onMagicSkill = _skillsOnMagic.getSkill(); + + // Trigger only if both are good or bad magic. + if (trigger.hasNegativeEffect() != onMagicSkill.hasNegativeEffect()) + { + return; + } + + // No Trigger if not Magic Skill. + if (!trigger.isMagic() && !onMagicSkill.isMagic()) + { + return; + } + + if (trigger.isToggle()) + { + return; + } + + if (caster.getAI().getCastTarget() != target) + { + return; + } + + if ((_skillsOnMagicCondition != null) && !_skillsOnMagicCondition.test(caster, target, onMagicSkill)) + { + // Chance not met + return; + } + + if (!onMagicSkill.checkCondition(caster, target, false)) + { + // Skill condition not met. + return; + } + + if (onMagicSkill.hasNegativeEffect() && (Formulas.calcShldUse(caster, target, onMagicSkill) == Formulas.SHIELD_DEFENSE_PERFECT_BLOCK)) + { + return; + } + + // Launch the magic skill and calculate its effects. + // Get the skill handler corresponding to the skill type. + onMagicSkill.activateSkill(caster, Collections.singletonList(target)); + + // Notify quests of a skill use. + if (caster.isPlayer()) + { + final List targets = Collections.singletonList(target); + World.forEachVisibleObjectInRange(caster, Npc.class, 1000, npc -> + { + if (EventDispatcher.getInstance().hasListener(EventType.ON_NPC_SKILL_SEE, npc)) + { + EventDispatcher.getInstance().notifyEventAsync(new OnNpcSkillSee(npc, caster.asPlayer(), onMagicSkill, targets, false), npc); + } + }); + } + + if (caster.isPlayer()) + { + final SystemMessage sm = new SystemMessage(SystemMessageId.S1_HAS_BEEN_ACTIVATED); + sm.addSkillName(onMagicSkill); + caster.sendPacket(sm); + } + } + + public boolean isBow() + { + return _type == WeaponType.BOW; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/enchant/AbstractEnchantItem.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/enchant/AbstractEnchantItem.java new file mode 100644 index 0000000..15ee399 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/enchant/AbstractEnchantItem.java @@ -0,0 +1,164 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.item.enchant; + +import java.util.EnumSet; +import java.util.Set; +import java.util.logging.Logger; + +import org.l2jmobius.gameserver.data.xml.ItemData; +import org.l2jmobius.gameserver.entity.item.ItemTemplate; +import org.l2jmobius.gameserver.entity.item.instance.Item; +import org.l2jmobius.gameserver.entity.item.type.CrystalType; +import org.l2jmobius.gameserver.entity.item.type.EtcItemType; +import org.l2jmobius.gameserver.util.StatSet; + +/** + * @author UnAfraid + */ +public abstract class AbstractEnchantItem +{ + protected static final Logger LOGGER = Logger.getLogger(AbstractEnchantItem.class.getName()); + + private static final Set ENCHANT_TYPES = EnumSet.noneOf(EtcItemType.class); + static + { + ENCHANT_TYPES.add(EtcItemType.ANCIENT_CRYSTAL_ENCHANT_AM); + ENCHANT_TYPES.add(EtcItemType.ANCIENT_CRYSTAL_ENCHANT_WP); + ENCHANT_TYPES.add(EtcItemType.BLESS_SCRL_ENCHANT_AM); + ENCHANT_TYPES.add(EtcItemType.BLESS_SCRL_ENCHANT_WP); + ENCHANT_TYPES.add(EtcItemType.SCRL_ENCHANT_AM); + ENCHANT_TYPES.add(EtcItemType.SCRL_ENCHANT_WP); + ENCHANT_TYPES.add(EtcItemType.SCRL_INC_ENCHANT_PROP_AM); + ENCHANT_TYPES.add(EtcItemType.SCRL_INC_ENCHANT_PROP_WP); + } + + private final int _id; + private final CrystalType _grade; + private final int _maxEnchantLevel; + private final double _bonusRate; + + public AbstractEnchantItem(StatSet set) + { + _id = set.getInt("id"); + if (getItem() == null) + { + throw new NullPointerException(); + } + else if (!ENCHANT_TYPES.contains(getItem().getItemType())) + { + throw new IllegalAccessError(); + } + + _grade = set.getEnum("targetGrade", CrystalType.class, CrystalType.NONE); + _maxEnchantLevel = set.getInt("maxEnchant", 65535); + _bonusRate = set.getDouble("bonusRate", 0); + } + + /** + * @return id of current item + */ + public int getId() + { + return _id; + } + + /** + * @return bonus chance that would be added + */ + public double getBonusRate() + { + return _bonusRate; + } + + /** + * @return {@link ItemTemplate} current item/scroll + */ + public ItemTemplate getItem() + { + return ItemData.getInstance().getTemplate(_id); + } + + /** + * @return grade of the item/scroll. + */ + public CrystalType getGrade() + { + return _grade; + } + + /** + * @return {@code true} if scroll is for weapon, {@code false} for armor + */ + public abstract boolean isWeapon(); + + /** + * @return the maximum enchant level that this scroll/item can be used with + */ + public int getMaxEnchantLevel() + { + return _maxEnchantLevel; + } + + /** + * @param itemToEnchant the item to be enchanted + * @return {@code true} if this support item can be used with the item to be enchanted, {@code false} otherwise + */ + public boolean isValid(Item itemToEnchant) + { + if (itemToEnchant == null) + { + return false; + } + else if (!itemToEnchant.isEnchantable()) + { + return false; + } + else if (!isValidItemType(itemToEnchant.getTemplate().getType2())) + { + return false; + } + else if ((_maxEnchantLevel != 0) && (itemToEnchant.getEnchantLevel() >= _maxEnchantLevel)) + { + return false; + } + else if (_grade != itemToEnchant.getTemplate().getCrystalTypePlus()) + { + return false; + } + + return true; + } + + /** + * @param type2 + * @return {@code true} if current type2 is valid to be enchanted, {@code false} otherwise + */ + private boolean isValidItemType(int type2) + { + if (type2 == ItemTemplate.TYPE2_WEAPON) + { + return isWeapon(); + } + else if ((type2 == ItemTemplate.TYPE2_SHIELD_ARMOR) || (type2 == ItemTemplate.TYPE2_ACCESSORY)) + { + return !isWeapon(); + } + + return false; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/enchant/EnchantItemGroup.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/enchant/EnchantItemGroup.java new file mode 100644 index 0000000..e853dfd --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/enchant/EnchantItemGroup.java @@ -0,0 +1,78 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.item.enchant; + +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.gameserver.data.holders.RangeChanceHolder; + +/** + * @author UnAfraid + */ +public class EnchantItemGroup +{ + private static final Logger LOGGER = Logger.getLogger(EnchantItemGroup.class.getName()); + private final List _chances = new ArrayList<>(); + private final String _name; + + public EnchantItemGroup(String name) + { + _name = name; + } + + /** + * @return name of current enchant item group. + */ + public String getName() + { + return _name; + } + + /** + * @param holder + */ + public void addChance(RangeChanceHolder holder) + { + _chances.add(holder); + } + + /** + * @param index + * @return chance for success rate for current enchant item group. + */ + public double getChance(int index) + { + if (!_chances.isEmpty()) + { + for (RangeChanceHolder holder : _chances) + { + if ((holder.getMin() <= index) && (holder.getMax() >= index)) + { + return holder.getChance(); + } + } + // LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Couldn't match proper chance for item group: " + _name); + return _chances.get(_chances.size() - 1).getChance(); + } + + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": item group: " + _name + " doesn't have any chances!"); + return -1; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/enchant/EnchantRateItem.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/enchant/EnchantRateItem.java new file mode 100644 index 0000000..0f69d54 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/enchant/EnchantRateItem.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.item.enchant; + +import java.util.HashSet; +import java.util.Set; + +import org.l2jmobius.gameserver.entity.item.ItemTemplate; + +/** + * @author UnAfraid, Mobius + */ +public class EnchantRateItem +{ + private final String _name; + private final Set _items = new HashSet<>(); + private int _slot; + private Boolean _isMagicWeapon = null; + + public EnchantRateItem(String name) + { + _name = name; + } + + /** + * @return name of enchant group. + */ + public String getName() + { + return _name; + } + + /** + * Adds item id verification. + * @param id + */ + public void addItemId(int id) + { + _items.add(id); + } + + /** + * Adds body slot verification. + * @param slot + */ + public void addSlot(int slot) + { + _slot |= slot; + } + + /** + * Adds magic weapon verification. + * @param magicWeapon + */ + public void setMagicWeapon(boolean magicWeapon) + { + _isMagicWeapon = Boolean.valueOf(magicWeapon); + } + + /** + * @param item + * @return {@code true} if item can be used with this rate group, {@code false} otherwise. + */ + public boolean validate(ItemTemplate item) + { + if (!_items.isEmpty() && !_items.contains(item.getId())) + { + return false; + } + else if ((_slot != 0) && ((item.getBodyPart().getMask() & _slot) == 0)) + { + return false; + } + else if ((_isMagicWeapon != null) && (item.isMagicWeapon() != _isMagicWeapon.booleanValue())) + { + return false; + } + + return true; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/enchant/EnchantResultType.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/enchant/EnchantResultType.java new file mode 100644 index 0000000..7b740af --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/enchant/EnchantResultType.java @@ -0,0 +1,27 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.item.enchant; + +/** + * @author UnAfraid + */ +public enum EnchantResultType +{ + ERROR, + SUCCESS, + FAILURE +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/enchant/EnchantScroll.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/enchant/EnchantScroll.java new file mode 100644 index 0000000..1a19b7c --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/enchant/EnchantScroll.java @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.item.enchant; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import org.l2jmobius.commons.util.Rnd; +import org.l2jmobius.gameserver.data.xml.EnchantItemData; +import org.l2jmobius.gameserver.data.xml.EnchantItemGroupsData; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.item.instance.Item; +import org.l2jmobius.gameserver.entity.item.type.EtcItemType; +import org.l2jmobius.gameserver.entity.item.type.ItemType; +import org.l2jmobius.gameserver.util.StatSet; + +/** + * @author UnAfraid, Mobius + */ +public class EnchantScroll extends AbstractEnchantItem +{ + private final boolean _isWeapon; + private final boolean _isBlessed; + private final boolean _isSafe; + private final int _scrollGroupId; + private final Map _items = new HashMap<>(); + + public EnchantScroll(StatSet set) + { + super(set); + _scrollGroupId = set.getInt("scrollGroupId", 0); + + final ItemType type = getItem().getItemType(); + _isWeapon = (type == EtcItemType.ANCIENT_CRYSTAL_ENCHANT_WP) || (type == EtcItemType.BLESS_SCRL_ENCHANT_WP) || (type == EtcItemType.SCRL_ENCHANT_WP); + _isBlessed = (type == EtcItemType.BLESS_SCRL_ENCHANT_AM) || (type == EtcItemType.BLESS_SCRL_ENCHANT_WP); + _isSafe = (type == EtcItemType.ANCIENT_CRYSTAL_ENCHANT_AM) || (type == EtcItemType.ANCIENT_CRYSTAL_ENCHANT_WP); + } + + @Override + public boolean isWeapon() + { + return _isWeapon; + } + + /** + * @return {@code true} for blessed scrolls (enchanted item will remain on failure), {@code false} otherwise + */ + public boolean isBlessed() + { + return _isBlessed; + } + + /** + * @return {@code true} for safe-enchant scrolls (enchant level will remain on failure), {@code false} otherwise + */ + public boolean isSafe() + { + return _isSafe; + } + + /** + * Enforces current scroll to use only those items as possible items to enchant + * @param itemId + * @param scrollGroupId + */ + public void addItem(int itemId, int scrollGroupId) + { + _items.put(itemId, scrollGroupId > -1 ? scrollGroupId : _scrollGroupId); + } + + public Collection getItems() + { + return _items.keySet(); + } + + /** + * @param itemToEnchant the item to be enchanted + * @return {@code true} if this scroll can be used with the specified support item and the item to be enchanted, {@code false} otherwise + */ + @Override + public boolean isValid(Item itemToEnchant) + { + if (!_items.isEmpty() && !_items.containsKey(itemToEnchant.getId())) + { + return false; + } + + if (_items.isEmpty()) + { + for (EnchantScroll scroll : EnchantItemData.getInstance().getScrolls()) + { + if (scroll.getId() == getId()) + { + continue; + } + + final Collection scrollItems = scroll.getItems(); + if (!scrollItems.isEmpty() && scrollItems.contains(itemToEnchant.getId())) + { + return false; + } + } + } + + return super.isValid(itemToEnchant); + } + + /** + * @param player + * @param enchantItem + * @return the chance of current scroll's group. + */ + public double getChance(Player player, Item enchantItem) + { + if (enchantItem == null) + { + return -1; + } + + final int scrollGroupId = _items.getOrDefault(enchantItem.getId(), _scrollGroupId); + if (EnchantItemGroupsData.getInstance().getScrollGroup(scrollGroupId) == null) + { + LOGGER.warning(getClass().getSimpleName() + ": Unexistent enchant scroll group specified for enchant scroll: " + getId()); + return -1; + } + + final EnchantItemGroup group = EnchantItemGroupsData.getInstance().getItemGroup(enchantItem.getTemplate(), scrollGroupId); + if (group == null) + { + LOGGER.warning(getClass().getSimpleName() + ": Couldn't find enchant item group for scroll: " + getId() + " requested by: " + player); + return -1; + } + + return group.getChance(enchantItem.getEnchantLevel()); + } + + /** + * @param player + * @param enchantItem + * @return the total chance for success rate of this scroll + */ + public EnchantResultType calculateSuccess(Player player, Item enchantItem) + { + if (!isValid(enchantItem)) + { + return EnchantResultType.ERROR; + } + + final double chance = getChance(player, enchantItem); + if (chance == -1) + { + return EnchantResultType.ERROR; + } + + final double bonusRate = getBonusRate(); + final double finalChance = Math.min(chance + bonusRate, 100); + final double random = 100 * Rnd.nextDouble(); + final boolean success = (random < finalChance); + return success ? EnchantResultType.SUCCESS : EnchantResultType.FAILURE; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/enchant/EnchantScrollGroup.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/enchant/EnchantScrollGroup.java new file mode 100644 index 0000000..7fd7792 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/enchant/EnchantScrollGroup.java @@ -0,0 +1,84 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.item.enchant; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.l2jmobius.gameserver.entity.item.ItemTemplate; + +/** + * @author UnAfraid + */ +public class EnchantScrollGroup +{ + private final int _id; + private List _rateGroups; + + public EnchantScrollGroup(int id) + { + _id = id; + } + + /** + * @return id of current enchant scroll group. + */ + public int getId() + { + return _id; + } + + /** + * Adds new rate group. + * @param group + */ + public void addRateGroup(EnchantRateItem group) + { + if (_rateGroups == null) + { + _rateGroups = new ArrayList<>(); + } + + _rateGroups.add(group); + } + + /** + * @return {@code List} of all enchant rate items, Empty list if none. + */ + public List getRateGroups() + { + return _rateGroups != null ? _rateGroups : Collections. emptyList(); + } + + /** + * @param item + * @return {@link EnchantRateItem}, {@code NULL} in case non of rate items can be used with. + */ + public EnchantRateItem getRateGroup(ItemTemplate item) + { + for (EnchantRateItem group : getRateGroups()) + { + if (group.validate(item)) + { + return group; + } + } + + return null; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/enchant/EnchantSupportItem.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/enchant/EnchantSupportItem.java new file mode 100644 index 0000000..905ddd8 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/enchant/EnchantSupportItem.java @@ -0,0 +1,40 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.item.enchant; + +import org.l2jmobius.gameserver.entity.item.type.EtcItemType; +import org.l2jmobius.gameserver.util.StatSet; + +/** + * @author UnAfraid + */ +public class EnchantSupportItem extends AbstractEnchantItem +{ + private final boolean _isWeapon; + + public EnchantSupportItem(StatSet set) + { + super(set); + _isWeapon = getItem().getItemType() == EtcItemType.SCRL_INC_ENCHANT_PROP_WP; + } + + @Override + public boolean isWeapon() + { + return _isWeapon; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/enums/BodyPart.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/enums/BodyPart.java new file mode 100644 index 0000000..7665025 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/enums/BodyPart.java @@ -0,0 +1,199 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.item.enums; + +import java.util.HashMap; +import java.util.Map; + +import org.l2jmobius.gameserver.entity.item.instance.Item; +import org.l2jmobius.gameserver.entity.itemcontainer.Inventory; + +/** + * @author Mobius + */ +public enum BodyPart +{ + // Items with no body part. + NONE(0x0000, Inventory.PAPERDOLL_TOTALSLOTS, "none"), + + // Basic body parts. + UNDERWEAR(0x0001, Inventory.PAPERDOLL_UNDER, "underwear", "shirt"), + R_EAR(0x0002, Inventory.PAPERDOLL_REAR, "rear", "rbracelet"), + L_EAR(0x0004, Inventory.PAPERDOLL_LEAR, "lear"), + LR_EAR(0x0006, -1, "rear;lear"), // Special case, no direct paperdoll slot. + NECK(0x0008, Inventory.PAPERDOLL_NECK, "neck"), + R_FINGER(0x0010, Inventory.PAPERDOLL_RFINGER, "rfinger"), + L_FINGER(0x0020, Inventory.PAPERDOLL_LFINGER, "lfinger"), + LR_FINGER(0x0030, -1, "rfinger;lfinger"), // Special case, no direct paperdoll slot. + HEAD(0x0040, Inventory.PAPERDOLL_HEAD, "head"), + R_HAND(0x0080, Inventory.PAPERDOLL_RHAND, "rhand"), + L_HAND(0x0100, Inventory.PAPERDOLL_LHAND, "lhand"), + GLOVES(0x0200, Inventory.PAPERDOLL_GLOVES, "gloves"), + CHEST(0x0400, Inventory.PAPERDOLL_CHEST, "chest"), + LEGS(0x0800, Inventory.PAPERDOLL_LEGS, "legs"), + FEET(0x1000, Inventory.PAPERDOLL_FEET, "feet"), + BACK(0x2000, Inventory.PAPERDOLL_CLOAK, "back"), + LR_HAND(0x4000, -1, "lrhand"), // Maps to RHAND slot. + FULL_ARMOR(0x8000, -1, "fullarmor", "onepiece"), // Maps to CHEST slot. + + // Extended body parts. + HAIR(0x010000, Inventory.PAPERDOLL_HAIR, "hair"), + ALLDRESS(0x020000, -1, "alldress"), // Maps to CHEST slot. + HAIR2(0x040000, Inventory.PAPERDOLL_HAIR2, "hair2"), + HAIRALL(0x080000, -1, "hairall", "dhair"), // Maps to HAIR slot. + + // Special slots for pets. + WOLF(-100, -1, "wolf"), + HATCHLING(-101, -1, "hatchling"), + STRIDER(-102, -1, "strider"), + BABYPET(-103, -1, "babypet"), + GREATWOLF(-104, -1, "greatwolf"); + + private final int _mask; + private final int _paperdollSlot; + private final String[] _names; + + private static final Map BODY_PARTS_BY_PAPERDOLL_SLOT = new HashMap<>(); + private static final Map BODY_PARTS_BY_NAME = new HashMap<>(); + static + { + for (BodyPart bodyPart : values()) + { + // Only map paperdoll slots that are valid. + if ((bodyPart._paperdollSlot >= 0) && (bodyPart._paperdollSlot < Inventory.PAPERDOLL_TOTALSLOTS)) + { + BODY_PARTS_BY_PAPERDOLL_SLOT.put(bodyPart._paperdollSlot, bodyPart); + } + + // Map all bodypart names. + for (String name : bodyPart._names) + { + BODY_PARTS_BY_NAME.put(name, bodyPart); + } + } + } + + /** + * Constructor for the body part enumeration. + * @param mask bit flag for the body part + * @param paperdollSlot the paperdoll slot index, -1 for special cases with no direct mapping + * @param names the body part names + */ + private BodyPart(int mask, int paperdollSlot, String... names) + { + _mask = mask; + _paperdollSlot = paperdollSlot; + _names = names; + } + + /** + * Gets the body part's bit flag value. + * @return the body part mask + */ + public int getMask() + { + return _mask; + } + + /** + * Gets the paperdoll slot index for this body part. + * @return the paperdoll slot index, or -1 if this body part doesn't map directly to a slot + */ + public int getPaperdollSlot() + { + return _paperdollSlot; + } + + /** + * Gets the paperdoll index from a body part.
        + * @param bodyPart the body part enum + * @return the paperdoll index, or -1 if not found + */ + public static int getPaperdollIndex(BodyPart bodyPart) + { + switch (bodyPart) // Handle special cases. + { + case LR_HAND: + { + return Inventory.PAPERDOLL_RHAND; + } + case FULL_ARMOR: + case ALLDRESS: + { + return Inventory.PAPERDOLL_CHEST; + } + case HAIRALL: + { + return Inventory.PAPERDOLL_HAIR; + } + default: + { + return bodyPart.getPaperdollSlot(); + } + } + } + + /** + * Gets a body part by the paperdoll slot index. + * @param slot the paperdoll slot index + * @return the body part corresponding to the paperdoll slot, or {@code null} if not found + */ + public static BodyPart fromPaperdollSlot(int slot) + { + return BODY_PARTS_BY_PAPERDOLL_SLOT.get(slot); + } + + /** + * Gets a body part for an equipped item. + * @param item the equipped item + * @return the body part corresponding to the equipped item, or {@code null} if not found + */ + public static BodyPart fromItem(Item item) + { + if (item == null) + { + return NONE; + } + + final int slot = item.getLocationSlot(); + switch (slot) + { + case Inventory.PAPERDOLL_CHEST: + { + return item.getTemplate().getBodyPart(); // Special case for chest slots. + } + default: + { + return BODY_PARTS_BY_PAPERDOLL_SLOT.get(slot); + } + } + } + + /** + * Gets a body part by the body part name. + * @param name the body part name + * @return the body part corresponding to the body part name, or {@code null} if not found + */ + public static BodyPart fromName(String name) + { + return BODY_PARTS_BY_NAME.get(name); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/enums/ItemGrade.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/enums/ItemGrade.java new file mode 100644 index 0000000..345d973 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/enums/ItemGrade.java @@ -0,0 +1,65 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.item.enums; + +import org.l2jmobius.gameserver.entity.item.type.CrystalType; + +/** + * @author UnAfraid + */ +public enum ItemGrade +{ + NONE, + D, + C, + B, + A, + S; + + public static ItemGrade valueOf(CrystalType type) + { + switch (type) + { + case NONE: + { + return NONE; + } + case D: + { + return D; + } + case C: + { + return C; + } + case B: + { + return B; + } + case A: + { + return A; + } + case S: + { + return S; + } + } + + return null; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/enums/ItemLocation.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/enums/ItemLocation.java new file mode 100644 index 0000000..a42fa3d --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/enums/ItemLocation.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.item.enums; + +public enum ItemLocation +{ + VOID, + INVENTORY, + PAPERDOLL, + WAREHOUSE, + CLANWH, + PET, + PET_EQUIP, + LEASE, + REFUND, + MAIL, + FREIGHT +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/enums/ItemProcessType.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/enums/ItemProcessType.java new file mode 100644 index 0000000..0ae2157 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/enums/ItemProcessType.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.item.enums; + +/** + * @author Mobius + */ +public enum ItemProcessType +{ + NONE, + DROP, + LOOT, + PICKUP, + SWEEP, + DEATH, + COMPENSATE, + TRANSFER, + CRAFT, + BUY, + SELL, + FEE, + REFUND, + RESTORE, + REWARD, + QUEST, + DESTROY +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/enums/ItemSkillType.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/enums/ItemSkillType.java new file mode 100644 index 0000000..6f46d82 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/enums/ItemSkillType.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.item.enums; + +/** + * @author Mobius + */ +public enum ItemSkillType +{ + NORMAL, // item_skill + ON_ENCHANT_4, // enchant4_skill + ON_UNEQUIP, // unequip_skill + ON_CRITICAL_SKILL, // oncrit_skill + ON_MAGIC_SKILL; // onmagic_skill +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/enums/ShotType.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/enums/ShotType.java new file mode 100644 index 0000000..c822d16 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/enums/ShotType.java @@ -0,0 +1,40 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.item.enums; + +/** + * @author UnAfraid + */ +public enum ShotType +{ + SOULSHOTS, + SPIRITSHOTS, + BLESSED_SPIRITSHOTS, + FISH_SOULSHOTS; + + private final int _mask; + + private ShotType() + { + _mask = 1 << ordinal(); + } + + public int getMask() + { + return _mask; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/holders/DropProtection.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/holders/DropProtection.java new file mode 100644 index 0000000..55a471a --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/holders/DropProtection.java @@ -0,0 +1,90 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.item.holders; + +import java.util.concurrent.ScheduledFuture; + +import org.l2jmobius.commons.threads.ThreadPool; +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.instance.Pet; + +/** + * @author DrHouse + */ +public class DropProtection implements Runnable +{ + private volatile boolean _isProtected = false; + private Creature _owner = null; + private ScheduledFuture _task = null; + + private static final long PROTECTED_MILLIS_TIME = 15000; + + @Override + public synchronized void run() + { + _isProtected = false; + _owner = null; + _task = null; + } + + public boolean isProtected() + { + return _isProtected; + } + + public Creature getOwner() + { + return _owner; + } + + public synchronized boolean tryPickUp(Player actor) + { + return !_isProtected || (_owner == actor) || ((_owner.getParty() != null) && (_owner.getParty() == actor.getParty())); + } + + public boolean tryPickUp(Pet pet) + { + return tryPickUp(pet.getOwner()); + } + + public synchronized void unprotect() + { + if (_task != null) + { + _task.cancel(false); + } + + _isProtected = false; + _owner = null; + _task = null; + } + + public synchronized void protect(Creature creature) + { + unprotect(); + + _isProtected = true; + _owner = creature; + if (_owner == null) + { + throw new NullPointerException("Trying to protect dropped item to null owner"); + } + + _task = ThreadPool.schedule(this, PROTECTED_MILLIS_TIME); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/holders/ExtractableProduct.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/holders/ExtractableProduct.java new file mode 100644 index 0000000..b0fb1be --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/holders/ExtractableProduct.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.item.holders; + +/** + * @author JIV, Mobius + */ +public class ExtractableProduct +{ + private final int _id; + private final int _min; + private final int _max; + private final int _chance; + private final int _minEnchant; + private final int _maxEnchant; + + /** + * Create Extractable product + * @param id create item id + * @param min item count max + * @param max item count min + * @param chance chance for creating + * @param minEnchant item min enchant + * @param maxEnchant item max enchant + */ + public ExtractableProduct(int id, int min, int max, double chance, int minEnchant, int maxEnchant) + { + _id = id; + _min = min; + _max = max; + _chance = (int) (chance * 1000); + _minEnchant = minEnchant; + _maxEnchant = maxEnchant; + } + + public int getId() + { + return _id; + } + + public int getMin() + { + return _min; + } + + public int getMax() + { + return _max; + } + + public int getChance() + { + return _chance; + } + + public int getMinEnchant() + { + return _minEnchant; + } + + public int getMaxEnchant() + { + return _maxEnchant; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/holders/ExtractableProductItem.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/holders/ExtractableProductItem.java new file mode 100644 index 0000000..d1bd0d7 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/holders/ExtractableProductItem.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.item.holders; + +import java.util.List; + +/** + * @author -Nemesiss-, Mobius + */ +public class ExtractableProductItem +{ + private final List _items; + private final double _chance; + + public ExtractableProductItem(List items, double chance) + { + _items = items; + _chance = chance; + } + + /** + * @return the the production list. + */ + public List getItems() + { + return _items; + } + + /** + * @return the chance of the production list. + */ + public double getChance() + { + return _chance; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/holders/InitialEquipment.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/holders/InitialEquipment.java new file mode 100644 index 0000000..9707c72 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/holders/InitialEquipment.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.item.holders; + +import org.l2jmobius.gameserver.util.StatSet; + +/** + * @author Mobius + */ +public class InitialEquipment extends ItemHolder +{ + private final boolean _equipped; + + public InitialEquipment(StatSet set) + { + super(set.getInt("id"), set.getInt("count")); + _equipped = set.getBoolean("equipped", false); + } + + public boolean isEquipped() + { + return _equipped; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/holders/ItemChanceHolder.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/holders/ItemChanceHolder.java new file mode 100644 index 0000000..6f49357 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/holders/ItemChanceHolder.java @@ -0,0 +1,52 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.item.holders; + +/** + * A DTO for items; contains item ID, count and chance. + * @author xban1x + */ +public class ItemChanceHolder extends ItemHolder +{ + private final double _chance; + + public ItemChanceHolder(int id, double chance) + { + this(id, chance, 1); + } + + public ItemChanceHolder(int id, double chance, int count) + { + super(id, count); + _chance = chance; + } + + /** + * Gets the chance. + * @return the drop chance of the item contained in this object + */ + public double getChance() + { + return _chance; + } + + @Override + public String toString() + { + return "[" + getClass().getSimpleName() + "] ID: " + getId() + ", count: " + getCount() + ", chance: " + _chance; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/holders/ItemEnchantHolder.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/holders/ItemEnchantHolder.java new file mode 100644 index 0000000..3519387 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/holders/ItemEnchantHolder.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.item.holders; + +/** + * @author Index, Mobius + */ +public class ItemEnchantHolder extends ItemHolder +{ + private final int _enchantLevel; + + public ItemEnchantHolder(int id, int count) + { + super(id, count); + _enchantLevel = 0; + } + + public ItemEnchantHolder(int id, int count, int enchantLevel) + { + super(id, count); + _enchantLevel = enchantLevel; + } + + /** + * @return enchant level of items contained in this object + */ + public int getEnchantLevel() + { + return _enchantLevel; + } + + @Override + public boolean equals(Object obj) + { + if (!(obj instanceof ItemEnchantHolder objInstance)) + { + return false; + } + else if (obj == this) + { + return true; + } + + return (getId() == objInstance.getId()) && ((getCount() == objInstance.getCount()) && (_enchantLevel == objInstance.getEnchantLevel())); + } + + @Override + public String toString() + { + return "[" + getClass().getSimpleName() + "] ID: " + getId() + ", count: " + getCount() + ", enchant level: " + _enchantLevel; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/holders/ItemHolder.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/holders/ItemHolder.java new file mode 100644 index 0000000..9f249ae --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/holders/ItemHolder.java @@ -0,0 +1,55 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.item.holders; + +/** + * A simple DTO for items; contains item ID and count. + * @author UnAfraid + */ +public class ItemHolder +{ + private final int _id; + private final int _count; + + public ItemHolder(int id, int count) + { + _id = id; + _count = count; + } + + /** + * @return the ID of the item contained in this object + */ + public int getId() + { + return _id; + } + + /** + * @return the count of items contained in this object + */ + public int getCount() + { + return _count; + } + + @Override + public String toString() + { + return "[" + getClass().getSimpleName() + "] ID: " + _id + ", count: " + _count; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/holders/ItemInfo.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/holders/ItemInfo.java new file mode 100644 index 0000000..0a4254d --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/holders/ItemInfo.java @@ -0,0 +1,233 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.item.holders; + +import org.l2jmobius.gameserver.entity.item.ItemTemplate; +import org.l2jmobius.gameserver.entity.item.instance.Item; +import org.l2jmobius.gameserver.network.holders.TradeItem; + +/** + * Get all information from Item to generate ItemInfo. + */ +public class ItemInfo +{ + /** Identifier of the Item */ + private int _objectId; + + /** The Item template of the Item */ + private ItemTemplate _item; + + /** The level of enchant on the Item */ + private int _enchant; + + /** The augmentation of the item */ + private int _augmentation; + + /** The quantity of Item */ + private int _count; + + /** The price of the Item */ + private int _price; + + /** The custom Item types (used loto, race tickets) */ + private int _type1; + private int _type2; + + /** If True the Item is equipped */ + private int _equipped; + + /** The action to do clientside (1=ADD, 2=MODIFY, 3=REMOVE) */ + private int _change; + + /** The mana of this item */ + private int _mana; + private int _time; + + private int _location; + + /** + * Get all information from Item to generate ItemInfo. + * @param item + */ + public ItemInfo(Item item) + { + if (item == null) + { + return; + } + + // Get the Identifier of the Item. + _objectId = item.getObjectId(); + + // Get the Item of the Item. + _item = item.getTemplate(); + + // Get the enchant level of the Item. + _enchant = item.getEnchantLevel(); + + // Get the augmentation bonus. + _augmentation = item.isAugmented() ? item.getAugmentation().getAugmentationId() : 0; + + // Get the quantity of the Item. + _count = item.getCount(); + + // Get custom item types (used loto, race tickets). + _type1 = item.getCustomType1(); + _type2 = item.getCustomType2(); + + // Verify if the Item is equipped. + _equipped = item.isEquipped() ? 1 : 0; + + // Get the action to do clientside. + switch (item.getLastChange()) + { + case Item.ADDED: + { + _change = 1; + break; + } + case Item.MODIFIED: + { + _change = 2; + break; + } + case Item.REMOVED: + { + _change = 3; + break; + } + } + + // Get shadow item mana. + _mana = item.getMana(); + _time = item.isTimeLimitedItem() ? (int) (item.getRemainingTime() / 1000) : -9999; + _location = item.getLocationSlot(); + } + + public ItemInfo(Item item, int change) + { + this(item); + _change = change; + } + + public ItemInfo(TradeItem item) + { + if (item == null) + { + return; + } + + // Get the Identifier of the Item. + _objectId = item.getObjectId(); + + // Get the Item of the Item. + _item = item.getItem(); + + // Get the enchant level of the Item. + _enchant = item.getEnchant(); + + // Get the augmentation bonus. + _augmentation = 0; + + // Get the quantity of the Item. + _count = item.getCount(); + + // Get custom item types (used loto, race tickets). + _type1 = item.getCustomType1(); + _type2 = item.getCustomType2(); + + // Verify if the Item is equipped. + _equipped = 0; + + // Get the action to do clientside. + _change = 0; + + // Get shadow item mana. + _mana = -1; + _time = -9999; + _location = item.getLocationSlot(); + } + + public int getObjectId() + { + return _objectId; + } + + public ItemTemplate getItem() + { + return _item; + } + + public int getEnchant() + { + return _enchant; + } + + public int getAugmentationBonus() + { + return _augmentation; + } + + public int getCount() + { + return _count; + } + + public int getPrice() + { + return _price; + } + + public int getCustomType1() + { + return _type1; + } + + public int getCustomType2() + { + return _type2; + } + + public int getEquipped() + { + return _equipped; + } + + public int getChange() + { + return _change; + } + + public int getMana() + { + return _mana; + } + + public int getTime() + { + return _time; + } + + public int getLocation() + { + return _location; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/holders/PetItemHolder.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/holders/PetItemHolder.java new file mode 100644 index 0000000..38e246c --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/holders/PetItemHolder.java @@ -0,0 +1,37 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.item.holders; + +import org.l2jmobius.gameserver.entity.item.instance.Item; + +/** + * @author UnAfraid + */ +public class PetItemHolder +{ + private final Item _item; + + public PetItemHolder(Item item) + { + _item = item; + } + + public Item getItem() + { + return _item; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/holders/RestorationItemHolder.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/holders/RestorationItemHolder.java new file mode 100644 index 0000000..4ca1744 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/holders/RestorationItemHolder.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.item.holders; + +/** + * @author Mobius + */ +public class RestorationItemHolder +{ + private final int _id; + private final int _count; + private final int _minEnchant; + private final int _maxEnchant; + + public RestorationItemHolder(int id, int count, int minEnchant, int maxEnchant) + { + _id = id; + _count = count; + _minEnchant = minEnchant; + _maxEnchant = maxEnchant; + } + + public int getId() + { + return _id; + } + + public int getCount() + { + return _count; + } + + public int getMinEnchant() + { + return _minEnchant; + } + + public int getMaxEnchant() + { + return _maxEnchant; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/holders/UniqueItemHolder.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/holders/UniqueItemHolder.java new file mode 100644 index 0000000..f09cae1 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/holders/UniqueItemHolder.java @@ -0,0 +1,48 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.item.holders; + +/** + * A DTO for items; contains item ID, object ID and count. + * @author xban1x + */ +public class UniqueItemHolder extends ItemHolder +{ + private final int _objectId; + + public UniqueItemHolder(int id, int objectId) + { + this(id, objectId, 1); + } + + public UniqueItemHolder(int id, int objectId, int count) + { + super(id, count); + _objectId = objectId; + } + + public int getObjectId() + { + return _objectId; + } + + @Override + public String toString() + { + return "[" + getClass().getSimpleName() + "] ID: " + getId() + ", object ID: " + _objectId + ", count: " + getCount(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/instance/Item.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/instance/Item.java new file mode 100644 index 0000000..5f4786b --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/instance/Item.java @@ -0,0 +1,1973 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.item.instance; + +import static org.l2jmobius.gameserver.entity.itemcontainer.Inventory.ADENA_ID; +import static org.l2jmobius.gameserver.entity.itemcontainer.Inventory.MAX_ADENA; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.locks.ReentrantLock; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.commons.database.DatabaseFactory; +import org.l2jmobius.commons.util.StringUtil; +import org.l2jmobius.gameserver.config.GeneralConfig; +import org.l2jmobius.gameserver.config.OlympiadConfig; +import org.l2jmobius.gameserver.config.PlayerConfig; +import org.l2jmobius.gameserver.config.custom.TransmogConfig; +import org.l2jmobius.gameserver.data.xml.ItemData; +import org.l2jmobius.gameserver.entity.Location; +import org.l2jmobius.gameserver.entity.World; +import org.l2jmobius.gameserver.entity.WorldObject; +import org.l2jmobius.gameserver.entity.WorldRegion; +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.enums.creature.InstanceType; +import org.l2jmobius.gameserver.entity.item.Armor; +import org.l2jmobius.gameserver.entity.item.EtcItem; +import org.l2jmobius.gameserver.entity.item.ItemTemplate; +import org.l2jmobius.gameserver.entity.item.Weapon; +import org.l2jmobius.gameserver.entity.item.enums.BodyPart; +import org.l2jmobius.gameserver.entity.item.enums.ItemLocation; +import org.l2jmobius.gameserver.entity.item.enums.ItemProcessType; +import org.l2jmobius.gameserver.entity.item.enums.ShotType; +import org.l2jmobius.gameserver.entity.item.holders.DropProtection; +import org.l2jmobius.gameserver.entity.item.type.EtcItemType; +import org.l2jmobius.gameserver.entity.item.type.ItemType; +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.managers.IdManager; +import org.l2jmobius.gameserver.managers.ItemsOnGroundManager; +import org.l2jmobius.gameserver.managers.MercTicketManager; +import org.l2jmobius.gameserver.mechanics.events.EventDispatcher; +import org.l2jmobius.gameserver.mechanics.events.EventType; +import org.l2jmobius.gameserver.mechanics.events.holders.actor.player.OnPlayerAugment; +import org.l2jmobius.gameserver.mechanics.events.holders.actor.player.inventory.OnPlayerItemDrop; +import org.l2jmobius.gameserver.mechanics.events.holders.actor.player.inventory.OnPlayerItemPickup; +import org.l2jmobius.gameserver.mechanics.events.holders.item.OnItemBypassEvent; +import org.l2jmobius.gameserver.mechanics.events.holders.item.OnItemTalk; +import org.l2jmobius.gameserver.mechanics.options.Augmentation; +import org.l2jmobius.gameserver.mechanics.options.Options; +import org.l2jmobius.gameserver.mechanics.script.QuestState; +import org.l2jmobius.gameserver.mechanics.skill.Skill; +import org.l2jmobius.gameserver.mechanics.skill.holders.SkillHolder; +import org.l2jmobius.gameserver.mechanics.stats.functions.AbstractFunction; +import org.l2jmobius.gameserver.mechanics.variables.ItemVariables; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.serverpackets.DropItem; +import org.l2jmobius.gameserver.network.serverpackets.GetItem; +import org.l2jmobius.gameserver.network.serverpackets.InventoryUpdate; +import org.l2jmobius.gameserver.network.serverpackets.SpawnItem; +import org.l2jmobius.gameserver.network.serverpackets.StatusUpdate; +import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; +import org.l2jmobius.gameserver.taskmanagers.ItemLifeTimeTaskManager; +import org.l2jmobius.gameserver.taskmanagers.ItemManaTaskManager; +import org.l2jmobius.gameserver.util.GMAudit; + +/** + * This class manages items. + * @version $Revision: 1.4.2.1.2.11 $ $Date: 2005/03/31 16:07:50 $ + */ +public class Item extends WorldObject +{ + private static final Logger LOGGER = Logger.getLogger(Item.class.getName()); + private static final Logger LOG_ITEMS = Logger.getLogger("item"); + + /** Owner */ + private int _ownerId; + private Player _owner; + + /** ID of who dropped the item last, used for knownlist */ + private int _dropperObjectId = 0; + + /** Quantity of the item */ + private int _count; + /** Initial Quantity of the item */ + private int _initCount; + /** Remaining time (in miliseconds) */ + private long _time; + /** Quantity of the item can decrease */ + private boolean _decrease = false; + + /** ID of the item */ + private final int _itemId; + + /** ItemTemplate associated to the item */ + private final ItemTemplate _itemTemplate; + + /** Location of the item : Inventory, PaperDoll, WareHouse */ + private ItemLocation _loc; + + /** Slot where item is stored : Paperdoll slot, inventory order ... */ + private int _locData; + + /** Level of enchantment of the item */ + private int _enchantLevel; + + /** Wear Item */ + private boolean _wear; + + /** Augmented Item */ + private Augmentation _augmentation = null; + + /** Shadow item */ + private int _mana = -1; + private boolean _consumingMana = false; + + /** Custom item types (used loto, race tickets) */ + private int _type1; + private int _type2; + + private long _dropTime; + + private boolean _published = false; + + private boolean _protected; + + public static final int UNCHANGED = 0; + public static final int ADDED = 1; + public static final int REMOVED = 3; + public static final int MODIFIED = 2; + + private int _lastChange = 2; // 1 ??, 2 modified, 3 removed + private boolean _existsInDb; // If a record exists in DB. + private boolean _storedInDb; // If DB data is up-to-date. + + private final ReentrantLock _dbLock = new ReentrantLock(); + + private ScheduledFuture _itemLootShedule = null; + + private final DropProtection _dropProtection = new DropProtection(); + + private int _shotsMask = 0; + + private final List _enchantOptions = new ArrayList<>(); + + /** + * Constructor of the Item from the objectId and the itemId. + * @param objectId : int designating the ID of the object in the world + * @param itemId : int designating the ID of the item + */ + public Item(int objectId, int itemId) + { + super(objectId); + setInstanceType(InstanceType.Item); + _itemId = itemId; + _itemTemplate = ItemData.getInstance().getTemplate(itemId); + if ((_itemId == 0) || (_itemTemplate == null)) + { + throw new IllegalArgumentException(); + } + + super.setName(_itemTemplate.getName()); + setCount(1); + _loc = ItemLocation.VOID; + _type1 = 0; + _type2 = 0; + _dropTime = 0; + _mana = _itemTemplate.getDuration(); + _time = _itemTemplate.getTime() == -1 ? -1 : System.currentTimeMillis() + (_itemTemplate.getTime() * 60 * 1000); + _enchantLevel = 0; + scheduleLifeTimeTask(); + } + + /** + * Constructor of the Item from the objetId and the description of the item given by the Item. + * @param objectId : int designating the ID of the object in the world + * @param itemTemplate : Item containing informations of the item + */ + public Item(int objectId, ItemTemplate itemTemplate) + { + super(objectId); + setInstanceType(InstanceType.Item); + _itemId = itemTemplate.getId(); + _itemTemplate = itemTemplate; + if (_itemId == 0) + { + throw new IllegalArgumentException(); + } + + super.setName(_itemTemplate.getName()); + setCount(1); + _loc = ItemLocation.VOID; + _mana = _itemTemplate.getDuration(); + _time = _itemTemplate.getTime() == -1 ? -1 : System.currentTimeMillis() + (_itemTemplate.getTime() * 60 * 1000); + scheduleLifeTimeTask(); + } + + /** + * Constructor overload.
        + * Sets the next free object ID in the ID factory. + * @param itemId the item template ID + */ + public Item(int itemId) + { + this(IdManager.getInstance().getNextId(), itemId); + } + + /** + * Remove a Item from the world and send server->client GetItem packets.
        + *
        + * Actions:
        + *
      • Send a Server->Client Packet GetItem to player that pick up and its _knowPlayers member
      • + *
      • Remove the WorldObject from the world

      • + * Caution: This method DOESN'T REMOVE the object from _allObjects of World
        + *
        + * Example of use:
        + *
      • Do Pickup Item : Player and Pet

      • + * @param creature Character that pick up the item + */ + public void pickupMe(Creature creature) + { + final WorldRegion oldregion = getWorldRegion(); + + // Create a server->client GetItem packet to pick up the Item. + creature.broadcastPacket(new GetItem(this, creature.getObjectId())); + + synchronized (this) + { + setSpawned(false); + } + + // If this item is a mercenary ticket, remove the spawns! + + if (MercTicketManager.getInstance().getTicketCastleId(_itemId) > 0) + { + MercTicketManager.getInstance().removeTicket(this); + ItemsOnGroundManager.getInstance().removeObject(this); + } + + if (!PlayerConfig.DISABLE_TUTORIAL && ((_itemId == ADENA_ID) || (_itemId == 6353))) + { + // Note from UnAfraid: + // Unhardcode this? + final Player actor = creature.asPlayer(); + if (actor != null) + { + final QuestState qs = actor.getQuestState("Q00255_Tutorial"); + if ((qs != null) && (qs.getQuest() != null)) + { + qs.getQuest().notifyEvent("CE" + _itemId, null, actor); + } + } + } + + // Outside of synchronized to avoid deadlocks. + // Remove the Item from the world. + World.removeVisibleObject(this, oldregion); + setWorldRegion(null); + + // Notify to scripts + if (creature.isPlayer() && EventDispatcher.getInstance().hasListener(EventType.ON_PLAYER_ITEM_PICKUP, getTemplate())) + { + EventDispatcher.getInstance().notifyEventAsync(new OnPlayerItemPickup(creature.asPlayer(), this), getTemplate()); + } + } + + /** + * Sets the ownerID of the item + * @param process : ItemProcessType identifier of process triggering this action + * @param ownerId : int designating the ID of the owner + * @param creator : Player Player requesting the item creation + * @param reference : Object Object referencing current action like NPC selling item or previous item in transformation + */ + public void setOwnerId(ItemProcessType process, int ownerId, Player creator, Object reference) + { + setOwnerId(ownerId); + + if ((GeneralConfig.LOG_ITEMS && ((!GeneralConfig.LOG_ITEMS_SMALL_LOG) && (!GeneralConfig.LOG_ITEMS_IDS_ONLY))) || (GeneralConfig.LOG_ITEMS_SMALL_LOG && (_itemTemplate.isEquipable() || (_itemTemplate.getId() == ADENA_ID))) || (GeneralConfig.LOG_ITEMS_IDS_ONLY && GeneralConfig.LOG_ITEMS_IDS_LIST.contains(_itemTemplate.getId()))) + { + if (_enchantLevel > 0) + { + LOG_ITEMS.info(StringUtil.concat("SETOWNER:", String.valueOf(process), ", item ", String.valueOf(getObjectId()), ":+", String.valueOf(_enchantLevel), " ", _itemTemplate.getName(), "(", String.valueOf(_count), "), ", String.valueOf(creator), ", ", String.valueOf(reference))); + } + else + { + LOG_ITEMS.info(StringUtil.concat("SETOWNER:", String.valueOf(process), ", item ", String.valueOf(getObjectId()), ":", _itemTemplate.getName(), "(", String.valueOf(_count), "), ", String.valueOf(creator), ", ", String.valueOf(reference))); + } + } + + if ((creator != null) && creator.isGM() && GeneralConfig.GMAUDIT) + { + final String targetName = (creator.getTarget() != null ? creator.getTarget().getName() : "no-target"); + String referenceName = "no-reference"; + if (reference instanceof WorldObject) + { + referenceName = ((WorldObject) reference).getName() != null ? ((WorldObject) reference).getName() : "no-name"; + } + else if (reference instanceof String) + { + referenceName = (String) reference; + } + + GMAudit.logAction(creator.toString(), StringUtil.concat(String.valueOf(process), "(id: ", String.valueOf(_itemId), " name: ", getName(), ")"), targetName, StringUtil.concat("Object referencing this action is: ", referenceName)); + } + } + + /** + * Sets the ownerID of the item + * @param ownerId : int designating the ID of the owner + */ + public void setOwnerId(int ownerId) + { + if (ownerId == _ownerId) + { + return; + } + + // Remove any inventory skills from the old owner. + removeSkillsFromOwner(); + + _owner = null; + _ownerId = ownerId; + _storedInDb = false; + + // Give any inventory skills to the new owner only if the item is in inventory. + // else the skills will be given when location is set to inventory. + giveSkillsToOwner(); + } + + /** + * Returns the ownerID of the item + * @return int : ownerID of the item + */ + public int getOwnerId() + { + return _ownerId; + } + + /** + * Sets the location of the item + * @param loc : ItemLocation (enumeration) + */ + public void setItemLocation(ItemLocation loc) + { + setItemLocation(loc, 0, true); + } + + /** + * Sets the location of the item + * @param loc : ItemLocation (enumeration) + * @param locData : int designating the slot where the item is stored or the village for freights + */ + public void setItemLocation(ItemLocation loc, int locData) + { + setItemLocation(loc, locData, true); + } + + /** + * Sets the location of the item.
        + * Remark : If loc and loc_data different from database, say datas not up-to-date + * @param loc : ItemLocation (enumeration) + * @param locData : int designating the slot where the item is stored or the village for freights + * @param checkSkills : boolean used to remove or give skills to owner. + */ + public void setItemLocation(ItemLocation loc, int locData, boolean checkSkills) + { + if ((loc == _loc) && (locData == _locData)) + { + return; + } + + if (checkSkills) + { + // Remove any inventory skills from the old owner. + removeSkillsFromOwner(); + } + + _loc = loc; + _locData = locData; + _storedInDb = false; + + if (checkSkills) + { + // Give any inventory skills to the new owner only if the item is in inventory. + // else the skills will be given when location is set to inventory. + giveSkillsToOwner(); + } + } + + public ItemLocation getItemLocation() + { + return _loc; + } + + /** + * Sets the quantity of the item. + * @param count the new count to set + */ + public void setCount(int count) + { + if (_count == count) + { + return; + } + + _count = count >= -1 ? count : 0; + _storedInDb = false; + } + + /** + * @return Returns the count. + */ + public int getCount() + { + return _count; + } + + /** + * Sets the quantity of the item.
        + * Remark : If loc and loc_data different from database, say datas not up-to-date + * @param process : ItemProcessType identifier of process triggering this action + * @param count : int + * @param creator : Player Player requesting the item creation + * @param reference : Object Object referencing current action like NPC selling item or previous item in transformation + */ + public void changeCount(ItemProcessType process, int count, Player creator, Object reference) + { + if (count == 0) + { + return; + } + + final int old = _count; + final int max = _itemId == ADENA_ID ? MAX_ADENA : Integer.MAX_VALUE; + + if ((count > 0) && (_count > (max - count))) + { + setCount(max); + } + else + { + setCount(_count + count); + } + + if (_count < 0) + { + setCount(0); + } + + _storedInDb = false; + + if ((process != null) && (process != ItemProcessType.NONE)) + { + if ((GeneralConfig.LOG_ITEMS && ((!GeneralConfig.LOG_ITEMS_SMALL_LOG) && (!GeneralConfig.LOG_ITEMS_IDS_ONLY))) || (GeneralConfig.LOG_ITEMS_SMALL_LOG && (_itemTemplate.isEquipable() || (_itemTemplate.getId() == ADENA_ID))) || (GeneralConfig.LOG_ITEMS_IDS_ONLY && GeneralConfig.LOG_ITEMS_IDS_LIST.contains(_itemTemplate.getId()))) + { + if (_enchantLevel > 0) + { + LOG_ITEMS.info(StringUtil.concat("CHANGE:", String.valueOf(process), ", item ", String.valueOf(getObjectId()), ":+", String.valueOf(_enchantLevel), " ", _itemTemplate.getName(), "(", String.valueOf(_count), "), PrevCount(", String.valueOf(old), "), ", String.valueOf(creator), ", ", String.valueOf(reference))); + } + else + { + LOG_ITEMS.info(StringUtil.concat("CHANGE:", String.valueOf(process), ", item ", String.valueOf(getObjectId()), ":", _itemTemplate.getName(), "(", String.valueOf(_count), "), PrevCount(", String.valueOf(old), "), ", String.valueOf(creator), ", ", String.valueOf(reference))); + } + } + + if ((creator != null) && creator.isGM() && GeneralConfig.GMAUDIT) + { + final String targetName = (creator.getTarget() != null ? creator.getTarget().getName() : "no-target"); + String referenceName = "no-reference"; + if (reference instanceof WorldObject) + { + referenceName = ((WorldObject) reference).getName() != null ? ((WorldObject) reference).getName() : "no-name"; + } + else if (reference instanceof String) + { + referenceName = (String) reference; + } + + GMAudit.logAction(creator.toString(), StringUtil.concat(String.valueOf(process), "(id: ", String.valueOf(_itemId), " objId: ", String.valueOf(getObjectId()), " name: ", getName(), " count: ", String.valueOf(count), ")"), targetName, StringUtil.concat("Object referencing this action is: ", referenceName)); + } + } + } + + /** + * Return true if item can be enchanted + * @return boolean + */ + public boolean isEnchantable() + { + if ((_loc == ItemLocation.INVENTORY) || (_loc == ItemLocation.PAPERDOLL)) + { + return _itemTemplate.isEnchantable(); + } + + return false; + } + + /** + * Returns if item is equipable + * @return boolean + */ + public boolean isEquipable() + { + return (_itemTemplate.getBodyPart() != BodyPart.NONE) && (_itemTemplate.getItemType() != EtcItemType.ARROW) && (_itemTemplate.getItemType() != EtcItemType.LURE); + } + + /** + * Returns if item is equipped + * @return boolean + */ + public boolean isEquipped() + { + return (_loc == ItemLocation.PAPERDOLL) || (_loc == ItemLocation.PET_EQUIP); + } + + /** + * Returns the slot where the item is stored + * @return int + */ + public int getLocationSlot() + { + return _locData; + } + + /** + * Returns the characteristics of the item. + * @return ItemTemplate + */ + public ItemTemplate getTemplate() + { + return _itemTemplate; + } + + public int getCustomType1() + { + return _type1; + } + + public int getCustomType2() + { + return _type2; + } + + public void setCustomType1(int newtype) + { + _type1 = newtype; + } + + public void setCustomType2(int newtype) + { + _type2 = newtype; + } + + public void setDropTime(long time) + { + _dropTime = time; + } + + public long getDropTime() + { + return _dropTime; + } + + /** + * @return the type of item. + */ + public ItemType getItemType() + { + return _itemTemplate.getItemType(); + } + + /** + * Gets the item ID. + * @return the item ID + */ + @Override + public int getId() + { + return _itemId; + } + + /** + * @return the display Id of the item. + */ + public int getDisplayId() + { + return _itemTemplate.getDisplayId(); + } + + /** + * @return {@code true} if item is an EtcItem, {@code false} otherwise. + */ + public boolean isEtcItem() + { + return _itemTemplate instanceof EtcItem; + } + + /** + * @return {@code true} if item is a Weapon/Shield, {@code false} otherwise. + */ + public boolean isWeapon() + { + return _itemTemplate instanceof Weapon; + } + + /** + * @return {@code true} if item is an Armor, {@code false} otherwise. + */ + public boolean isArmor() + { + return _itemTemplate instanceof Armor; + } + + /** + * @return the characteristics of the EtcItem, {@code false} otherwise. + */ + public EtcItem getEtcItem() + { + if (_itemTemplate instanceof EtcItem) + { + return (EtcItem) _itemTemplate; + } + + return null; + } + + /** + * @return the characteristics of the Weapon. + */ + public Weapon getWeaponItem() + { + if (_itemTemplate instanceof Weapon) + { + return (Weapon) _itemTemplate; + } + + return null; + } + + /** + * @return the characteristics of the Armor. + */ + public Armor getArmorItem() + { + if (_itemTemplate instanceof Armor) + { + return (Armor) _itemTemplate; + } + + return null; + } + + /** + * @return the quantity of crystals for crystallization. + */ + public int getCrystalCount() + { + return _itemTemplate.getCrystalCount(_enchantLevel); + } + + /** + * @return the reference price of the item. + */ + public int getReferencePrice() + { + return _itemTemplate.getReferencePrice(); + } + + /** + * @return the name of the item. + */ + public String getItemName() + { + return _itemTemplate.getName(); + } + + /** + * @return the reuse delay of this item. + */ + public int getReuseDelay() + { + return _itemTemplate.getReuseDelay(); + } + + /** + * @return the shared reuse item group. + */ + public int getSharedReuseGroup() + { + return _itemTemplate.getSharedReuseGroup(); + } + + /** + * @return the last change of the item + */ + public int getLastChange() + { + return _lastChange; + } + + /** + * Sets the last change of the item + * @param lastChange : int + */ + public void setLastChange(int lastChange) + { + _lastChange = lastChange; + } + + /** + * Returns if item is stackable + * @return boolean + */ + public boolean isStackable() + { + return _itemTemplate.isStackable(); + } + + /** + * Returns if item is dropable + * @return boolean + */ + public boolean isDropable() + { + if (!_itemTemplate.isDropable()) + { + return false; + } + + if (isEquipable() && (getTransmogId() > 0)) + { + return false; + } + + if (isAugmented()) + { + return PlayerConfig.ALT_ALLOW_AUGMENT_TRADE; + } + + return true; + } + + /** + * Returns if item is destroyable + * @return boolean + */ + public boolean isDestroyable() + { + if (!_itemTemplate.isDestroyable()) + { + return false; + } + + if (isAugmented()) + { + return PlayerConfig.ALT_ALLOW_AUGMENT_DESTROY; + } + + return true; + } + + /** + * Returns if item is tradeable + * @return boolean + */ + public boolean isTradeable() + { + if (!_itemTemplate.isTradeable()) + { + return false; + } + + if (isEquipable() && (getTransmogId() > 0)) + { + return false; + } + + if (isAugmented()) + { + return PlayerConfig.ALT_ALLOW_AUGMENT_TRADE; + } + + return true; + } + + /** + * Returns if item is sellable + * @return boolean + */ + public boolean isSellable() + { + if (!_itemTemplate.isSellable()) + { + return false; + } + + if (isEquipable() && (getTransmogId() > 0)) + { + return false; + } + + if (isAugmented()) + { + return PlayerConfig.ALT_ALLOW_AUGMENT_TRADE; + } + + return true; + } + + /** + * @param isPrivateWareHouse + * @return if item can be deposited in warehouse or freight + */ + public boolean isDepositable(boolean isPrivateWareHouse) + { + if (!_itemTemplate.isDepositable() || isEquipped()) + { + return false; + } + + if (!isPrivateWareHouse && (!isTradeable() || isShadowItem())) + { + return false; + } + + return true; + } + + public boolean isPotion() + { + return _itemTemplate.isPotion(); + } + + public boolean isElixir() + { + return _itemTemplate.isElixir(); + } + + public boolean isScroll() + { + return _itemTemplate.isScroll(); + } + + public boolean isHeroItem() + { + return _itemTemplate.isHeroItem(); + } + + public boolean isCommonItem() + { + return _itemTemplate.isCommon(); + } + + /** + * Returns whether this item is pvp or not + * @return boolean + */ + public boolean isPvp() + { + return _itemTemplate.isPvpItem(); + } + + public boolean isOlyRestrictedItem() + { + return _itemTemplate.isOlyRestrictedItem(); + } + + /** + * @param player + * @param allowAdena + * @param allowNonTradeable + * @return if item is available for manipulation + */ + public boolean isAvailable(Player player, boolean allowAdena, boolean allowNonTradeable) + { + return ((!isEquipped()) // Not equipped + && (_itemTemplate.getType2() != ItemTemplate.TYPE2_QUEST) // Not Quest Item + && ((_itemTemplate.getType2() != ItemTemplate.TYPE2_MONEY) || (_itemTemplate.getType1() != ItemTemplate.TYPE1_SHIELD_ARMOR)) // not money, not shield + && (!player.hasSummon() || (getObjectId() != player.getSummon().getControlObjectId())) // Not Control item of currently summoned pet. + && (player.getActiveEnchantItemId() != getObjectId()) // Not momentarily used enchant scroll. + && (allowAdena || (_itemId != ADENA_ID)) // Not Adena + && ((player.getCurrentSkill() == null) || (player.getCurrentSkill().getSkill().getItemConsumeId() != _itemId)) && (!player.isCastingSimultaneouslyNow() || (player.getLastSimultaneousSkillCast() == null) || (player.getLastSimultaneousSkillCast().getItemConsumeId() != _itemId)) && (allowNonTradeable || (isTradeable() && (!((_itemTemplate.getItemType() == EtcItemType.PET_COLLAR) && player.havePetInvItems()))))); + } + + /** + * Returns the level of enchantment of the item + * @return int + */ + public int getEnchantLevel() + { + return _enchantLevel; + } + + /** + * @return {@code true} if item is enchanted, {@code false} otherwise + */ + public boolean isEnchanted() + { + return _enchantLevel > 0; + } + + /** + * @param level the enchant value to set + */ + public void setEnchantLevel(int level) + { + final int newLevel = Math.max(0, level); + if (_enchantLevel == newLevel) + { + return; + } + + clearEnchantStats(); + _enchantLevel = newLevel; + _storedInDb = false; + } + + /** + * Returns whether this item is augmented or not + * @return true if augmented + */ + public boolean isAugmented() + { + return _augmentation != null; + } + + /** + * Returns the augmentation object for this item + * @return augmentation + */ + public Augmentation getAugmentation() + { + return _augmentation; + } + + /** + * Sets a new augmentation + * @param augmentation + * @return return true if sucessfull + */ + public boolean setAugmentation(Augmentation augmentation) + { + // there shall be no previous augmentation.. + if (_augmentation != null) + { + LOGGER.info("Warning: Augment set for (" + getObjectId() + ") " + getName() + " owner: " + _ownerId); + return false; + } + + _augmentation = augmentation; + try (Connection con = DatabaseFactory.getConnection()) + { + updateItemAttributes(con); + } + catch (SQLException e) + { + LOGGER.log(Level.SEVERE, "Could not update atributes for item: " + this + " from DB:", e); + } + + // Notify to scripts. + if (EventDispatcher.getInstance().hasListener(EventType.ON_PLAYER_AUGMENT, getTemplate())) + { + EventDispatcher.getInstance().notifyEventAsync(new OnPlayerAugment(asPlayer(), this, augmentation, true), getTemplate()); + } + + return true; + } + + /** + * Remove the augmentation + */ + public void removeAugmentation() + { + if (_augmentation == null) + { + return; + } + + // Copy augmentation before removing it. + final Augmentation augment = _augmentation; + _augmentation = null; + + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement("DELETE FROM item_attributes WHERE itemId = ?")) + { + ps.setInt(1, getObjectId()); + ps.executeUpdate(); + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "Could not remove augmentation for item: " + this + " from DB:", e); + } + + // Notify to scripts. + if (EventDispatcher.getInstance().hasListener(EventType.ON_PLAYER_AUGMENT, getTemplate())) + { + EventDispatcher.getInstance().notifyEventAsync(new OnPlayerAugment(asPlayer(), this, augment, false), getTemplate()); + } + } + + public void restoreAttributes() + { + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps1 = con.prepareStatement("SELECT augAttributes FROM item_attributes WHERE itemId=?")) + { + ps1.setInt(1, getObjectId()); + try (ResultSet rs = ps1.executeQuery()) + { + if (rs.next()) + { + final int aug_attributes = rs.getInt(1); + if (aug_attributes != -1) + { + _augmentation = new Augmentation(rs.getInt("augAttributes")); + } + } + } + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "Could not restore augmentation and elemental data for item " + this + " from DB: " + e.getMessage(), e); + } + } + + private void updateItemAttributes(Connection con) + { + try (PreparedStatement ps = con.prepareStatement("REPLACE INTO item_attributes VALUES(?,?)")) + { + ps.setInt(1, getObjectId()); + ps.setInt(2, _augmentation != null ? _augmentation.getAugmentationId() : -1); + ps.executeUpdate(); + } + catch (SQLException e) + { + LOGGER.log(Level.SEVERE, "Could not update atributes for item: " + this + " from DB:", e); + } + } + + /** + * Returns true if this item is a shadow item Shadow items have a limited life-time + * @return + */ + public boolean isShadowItem() + { + return _mana >= 0; + } + + /** + * Returns the remaining mana of this shadow item + * @return lifeTime + */ + public int getMana() + { + return _mana; + } + + /** + * Decreases the mana of this shadow item, sends a inventory update schedules a new consumption task if non is running optionally one could force a new task + * @param resetConsumingMana if true forces a new consumption task if item is equipped + */ + public void decreaseMana(boolean resetConsumingMana) + { + decreaseMana(resetConsumingMana, 1); + } + + /** + * Decreases the mana of this shadow item, sends a inventory update schedules a new consumption task if non is running optionally one could force a new task + * @param resetConsumingMana if forces a new consumption task if item is equipped + * @param count how much mana decrease + */ + public void decreaseMana(boolean resetConsumingMana, int count) + { + if (!isShadowItem()) + { + return; + } + + if ((_mana - count) >= 0) + { + _mana -= count; + } + else + { + _mana = 0; + } + + if (_storedInDb) + { + _storedInDb = false; + } + + if (resetConsumingMana) + { + _consumingMana = false; + } + + final Player player = asPlayer(); + if (player == null) + { + return; + } + + SystemMessage sm; + switch (_mana) + { + case 10: + { + sm = new SystemMessage(SystemMessageId.S1_S_REMAINING_MANA_IS_NOW_10); + sm.addItemName(_itemTemplate); + player.sendPacket(sm); + break; + } + case 5: + { + sm = new SystemMessage(SystemMessageId.S1_S_REMAINING_MANA_IS_NOW_5); + sm.addItemName(_itemTemplate); + player.sendPacket(sm); + break; + } + case 1: + { + sm = new SystemMessage(SystemMessageId.S1_S_REMAINING_MANA_IS_NOW_1_IT_WILL_DISAPPEAR_SOON); + sm.addItemName(_itemTemplate); + player.sendPacket(sm); + break; + } + } + + if (_mana == 0) // The life time has expired. + { + sm = new SystemMessage(SystemMessageId.S1_S_REMAINING_MANA_IS_NOW_0_AND_THE_ITEM_HAS_DISAPPEARED); + sm.addItemName(_itemTemplate); + player.sendPacket(sm); + + // Unequip. + if (isEquipped()) + { + final InventoryUpdate iu = new InventoryUpdate(); + for (Item item : player.getInventory().unEquipItemInSlotAndRecord(getLocationSlot())) + { + item.unChargeAllShots(); + iu.addModifiedItem(item); + } + + player.sendInventoryUpdate(iu); + player.broadcastUserInfo(); + } + + if (_loc != ItemLocation.WAREHOUSE) + { + // Destroy. + player.getInventory().destroyItem(ItemProcessType.DESTROY, this, player, null); + + // Send update. + final InventoryUpdate iu = new InventoryUpdate(); + iu.addRemovedItem(this); + player.sendInventoryUpdate(iu); + + final StatusUpdate su = new StatusUpdate(player); + su.addAttribute(StatusUpdate.CUR_LOAD, player.getCurrentLoad()); + player.sendPacket(su); + } + else + { + player.getWarehouse().destroyItem(ItemProcessType.DESTROY, this, player, null); + } + + // Delete from world. (redundant - already removed by destroyItem) + // World.removeObject(this); + } + else + { + // Reschedule if still equipped. + if (!_consumingMana && isEquipped()) + { + scheduleConsumeManaTask(); + } + + if (_loc != ItemLocation.WAREHOUSE) + { + final InventoryUpdate iu = new InventoryUpdate(); + iu.addModifiedItem(this); + player.sendInventoryUpdate(iu); + } + } + } + + public void scheduleConsumeManaTask() + { + if (_consumingMana) + { + return; + } + + _consumingMana = true; + ItemManaTaskManager.getInstance().add(this); + } + + /** + * Returns false cause item can't be attacked + * @return boolean false + */ + @Override + public boolean isAutoAttackable(Creature attacker) + { + return false; + } + + /** + * This function basically returns a set of functions from Item/Armor/Weapon, but may add additional functions, if this particular item instance is enhanced for a particular player. + * @param creature the player + * @return the functions list + */ + public List getStatFuncs(Creature creature) + { + return _itemTemplate.getStatFuncs(this, creature); + } + + /** + * Updates the database. + */ + public void updateDatabase() + { + updateDatabase(false); + } + + /** + * Updates the database. + * @param force if the update should necessarily be done. + */ + public void updateDatabase(boolean force) + { + _dbLock.lock(); + + try + { + if (_existsInDb) + { + if ((_ownerId == 0) || (_loc == ItemLocation.VOID) || (_loc == ItemLocation.REFUND) || ((_count == 0) && (_loc != ItemLocation.LEASE))) + { + removeFromDb(); + } + else if (!GeneralConfig.LAZY_ITEMS_UPDATE || force) + { + updateInDb(); + } + } + else + { + if ((_ownerId == 0) || (_loc == ItemLocation.VOID) || (_loc == ItemLocation.REFUND) || ((_count == 0) && (_loc != ItemLocation.LEASE))) + { + return; + } + + insertIntoDb(); + } + } + finally + { + _dbLock.unlock(); + } + } + + /** + * Returns a Item stored in database from its objectID + * @param ownerId + * @param rs + * @return Item + */ + public static Item restoreFromDb(int ownerId, ResultSet rs) + { + Item inst = null; + int objectId; + int itemId; + int locData; + int enchantLevel; + int customType1; + int customType2; + int manaLeft; + long time; + int count; + ItemLocation loc; + try + { + objectId = rs.getInt(1); + itemId = rs.getInt("item_id"); + count = rs.getInt("count"); + loc = ItemLocation.valueOf(rs.getString("loc")); + locData = rs.getInt("loc_data"); + enchantLevel = rs.getInt("enchant_level"); + customType1 = rs.getInt("custom_type1"); + customType2 = rs.getInt("custom_type2"); + manaLeft = rs.getInt("mana_left"); + time = rs.getLong("time"); + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "Could not restore an item owned by " + ownerId + " from DB:", e); + return null; + } + + final ItemTemplate item = ItemData.getInstance().getTemplate(itemId); + if (item == null) + { + LOGGER.severe("Item item_id=" + itemId + " not known, object_id=" + objectId); + return null; + } + + inst = new Item(objectId, item); + inst._ownerId = ownerId; + inst.setCount(count); + inst._enchantLevel = enchantLevel; + inst._type1 = customType1; + inst._type2 = customType2; + inst._loc = loc; + inst._locData = locData; + inst._existsInDb = true; + inst._storedInDb = true; + + // Setup life time for shadow weapons. + inst._mana = manaLeft; + inst._time = time; + + // load augmentation and elemental enchant + if (inst.isEquipable()) + { + inst.restoreAttributes(); + } + + return inst; + } + + /** + * Init a dropped Item and add it in the world as a visible object.
        + *
        + * Actions:
        + *
      • Set the x,y,z position of the Item dropped and update its _worldregion
      • + *
      • Add the Item dropped to _visibleObjects of its WorldRegion
      • + *
      • Add the Item dropped in the world as a visible object

      • + * Caution: This method DOESN'T ADD the object to _allObjects of World
        + *
        + * Example of use:
        + *
      • Drop item
      • + *
      • Call Pet
      • + * @param dropper + * @param locX + * @param locY + * @param locZ + */ + public void dropMe(Creature dropper, int locX, int locY, int locZ) + { + int x = locX; + int y = locY; + int z = locZ; + + if (dropper != null) + { + final Location dropDest = GeoEngine.getInstance().getValidLocation(dropper.getX(), dropper.getY(), dropper.getZ(), x, y, z, dropper.getInstanceId()); + x = dropDest.getX(); + y = dropDest.getY(); + z = dropDest.getZ(); + } + + if (dropper != null) + { + setInstanceId(dropper.getInstanceId()); // Inherit instancezone when dropped in visible world. + } + else + { + setInstanceId(0); // No dropper? Make it a global item... + } + + // Set the x,y,z position of the Item dropped and update its world region. + setSpawned(true); + setXYZ(x, y, z); + + setDropTime(System.currentTimeMillis()); + setDropperObjectId(dropper != null ? dropper.getObjectId() : 0); // Set the dropper Id for the knownlist packets in sendInfo. + + // Add the Item dropped in the world as a visible object. + final WorldRegion region = getWorldRegion(); + region.addVisibleObject(this); + World.addVisibleObject(this, region); + if (GeneralConfig.SAVE_DROPPED_ITEM) + { + ItemsOnGroundManager.getInstance().save(this); + } + + setDropperObjectId(0); // Set the dropper Id back to 0 so it no longer shows the drop packet. + + if ((dropper != null) && dropper.isPlayer()) + { + _owner = null; + + // Notify to scripts + if (EventDispatcher.getInstance().hasListener(EventType.ON_PLAYER_ITEM_DROP, getTemplate())) + { + EventDispatcher.getInstance().notifyEventAsync(new OnPlayerItemDrop(dropper.asPlayer(), this, new Location(x, y, z)), getTemplate()); + } + } + } + + /** + * Update the database with values of the item + */ + private void updateInDb() + { + if (!_existsInDb || _wear || _storedInDb) + { + return; + } + + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement("UPDATE items SET owner_id=?,count=?,loc=?,loc_data=?,enchant_level=?,custom_type1=?,custom_type2=?,mana_left=?,time=? WHERE object_id = ?")) + { + ps.setInt(1, _ownerId); + ps.setInt(2, _count); + ps.setString(3, _loc.name()); + ps.setInt(4, _locData); + ps.setInt(5, _enchantLevel); + ps.setInt(6, _type1); + ps.setInt(7, _type2); + ps.setInt(8, _mana); + ps.setLong(9, _time); + ps.setInt(10, getObjectId()); + ps.executeUpdate(); + _existsInDb = true; + _storedInDb = true; + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "Could not update item " + this + " in DB: Reason: " + e.getMessage(), e); + } + } + + /** + * Insert the item in database + */ + private void insertIntoDb() + { + if (_existsInDb || (getObjectId() == 0) || _wear) + { + return; + } + + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement("INSERT INTO items (owner_id,item_id,count,loc,loc_data,enchant_level,object_id,custom_type1,custom_type2,mana_left,time) VALUES (?,?,?,?,?,?,?,?,?,?,?)")) + { + ps.setInt(1, _ownerId); + ps.setInt(2, _itemId); + ps.setInt(3, _count); + ps.setString(4, _loc.name()); + ps.setInt(5, _locData); + ps.setInt(6, _enchantLevel); + ps.setInt(7, getObjectId()); + ps.setInt(8, _type1); + ps.setInt(9, _type2); + ps.setInt(10, _mana); + ps.setLong(11, _time); + + ps.executeUpdate(); + _existsInDb = true; + _storedInDb = true; + + if (_augmentation != null) + { + updateItemAttributes(con); + } + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "Could not insert item " + this + " into DB: Reason: " + e.getMessage(), e); + } + } + + /** + * Delete item from database + */ + private void removeFromDb() + { + if (!_existsInDb || _wear) + { + return; + } + + try (Connection con = DatabaseFactory.getConnection()) + { + try (PreparedStatement ps = con.prepareStatement("DELETE FROM items WHERE object_id = ?")) + { + ps.setInt(1, getObjectId()); + ps.executeUpdate(); + _existsInDb = false; + _storedInDb = false; + } + + try (PreparedStatement ps = con.prepareStatement("DELETE FROM item_attributes WHERE itemId = ?")) + { + ps.setInt(1, getObjectId()); + ps.executeUpdate(); + } + + try (PreparedStatement ps = con.prepareStatement("DELETE FROM item_variables WHERE id = ?")) + { + ps.setInt(1, getObjectId()); + ps.executeUpdate(); + } + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "Could not delete item " + this + " in DB: " + e.getMessage(), e); + } + } + + public void resetOwnerTimer() + { + if (_itemLootShedule != null) + { + _itemLootShedule.cancel(true); + _itemLootShedule = null; + } + } + + public void setItemLootShedule(ScheduledFuture sf) + { + _itemLootShedule = sf; + } + + public ScheduledFuture getItemLootShedule() + { + return _itemLootShedule; + } + + public void setProtected(boolean isProtected) + { + _protected = isProtected; + } + + public boolean isProtected() + { + return _protected; + } + + public boolean isNightLure() + { + return (((_itemId >= 8505) && (_itemId <= 8513)) || (_itemId == 8485)); + } + + public void setCountDecrease(boolean decrease) + { + _decrease = decrease; + } + + public boolean getCountDecrease() + { + return _decrease; + } + + public void setInitCount(int initCount) + { + _initCount = initCount; + } + + public long getInitCount() + { + return _initCount; + } + + public void restoreInitCount() + { + if (_decrease) + { + setCount(_initCount); + } + } + + public boolean isTimeLimitedItem() + { + return _time > 0; + } + + /** + * Returns (current system time + time) of this time limited item + * @return Time + */ + public long getTime() + { + return _time; + } + + public long getRemainingTime() + { + return _time - System.currentTimeMillis(); + } + + public void endOfLife() + { + final Player player = asPlayer(); + if (player == null) + { + return; + } + + if (isEquipped()) + { + final InventoryUpdate iu = new InventoryUpdate(); + for (Item item : player.getInventory().unEquipItemInSlotAndRecord(getLocationSlot())) + { + item.unChargeAllShots(); + iu.addModifiedItem(item); + } + + player.sendInventoryUpdate(iu); + player.broadcastUserInfo(); + } + + if (_loc != ItemLocation.WAREHOUSE) + { + // Destroy. + player.getInventory().destroyItem(ItemProcessType.DESTROY, this, player, null); + + // Send update. + final InventoryUpdate iu = new InventoryUpdate(); + iu.addRemovedItem(this); + player.sendInventoryUpdate(iu); + + final StatusUpdate su = new StatusUpdate(player); + su.addAttribute(StatusUpdate.CUR_LOAD, player.getCurrentLoad()); + player.sendPacket(su); + } + else + { + player.getWarehouse().destroyItem(ItemProcessType.DESTROY, this, player, null); + } + + player.sendMessage("The limited-time item has disappeared because the remaining time ran out."); + + // Delete from world. (redundant - already removed by destroyItem) + // World.removeObject(this); + } + + public void scheduleLifeTimeTask() + { + if (!isTimeLimitedItem()) + { + return; + } + + if (getRemainingTime() <= 0) + { + endOfLife(); + } + else + { + ItemLifeTimeTaskManager.getInstance().add(this, getTime()); + } + } + + public void setDropperObjectId(int id) + { + _dropperObjectId = id; + } + + @Override + public void sendInfo(Player player) + { + if (_dropperObjectId != 0) + { + player.sendPacket(new DropItem(this, _dropperObjectId)); + } + else + { + player.sendPacket(new SpawnItem(this)); + } + } + + public DropProtection getDropProtection() + { + return _dropProtection; + } + + public boolean isPublished() + { + return _published; + } + + public void publish() + { + _published = true; + } + + @Override + public boolean decayMe() + { + if (GeneralConfig.SAVE_DROPPED_ITEM) + { + ItemsOnGroundManager.getInstance().removeObject(this); + } + + return super.decayMe(); + } + + public boolean isQuestItem() + { + return _itemTemplate.isQuestItem(); + } + + public boolean isElementable() + { + return ((_loc == ItemLocation.INVENTORY) || (_loc == ItemLocation.PAPERDOLL)) && _itemTemplate.isElementable(); + } + + public boolean isFreightable() + { + return _itemTemplate.isFreightable(); + } + + public int useSkillDisTime() + { + return _itemTemplate.useSkillDisTime(); + } + + public int getOlyEnchantLevel() + { + final Player player = asPlayer(); + int enchant = _enchantLevel; + + if (player == null) + { + return enchant; + } + + if (player.isInOlympiadMode() && (OlympiadConfig.OLYMPIAD_ENCHANT_LIMIT >= 0) && (enchant > OlympiadConfig.OLYMPIAD_ENCHANT_LIMIT)) + { + enchant = OlympiadConfig.OLYMPIAD_ENCHANT_LIMIT; + } + + return enchant; + } + + public int getDefaultEnchantLevel() + { + return _itemTemplate.getDefaultEnchantLevel(); + } + + public boolean hasPassiveSkills() + { + return (_itemTemplate.getItemType() == EtcItemType.RUNE) && (_loc == ItemLocation.INVENTORY) && (_ownerId > 0) && _itemTemplate.hasSkills(); + } + + public void giveSkillsToOwner() + { + if (!_itemTemplate.hasSkills()) + { + return; + } + + if (!isEquipped() && !hasPassiveSkills()) + { + return; + } + + final Player player = asPlayer(); + if (player == null) + { + return; + } + + for (SkillHolder holder : _itemTemplate.getSkills()) + { + final Skill skill = holder.getSkill(); + if (skill.isPassive()) + { + player.addSkill(skill, false); + } + } + } + + public void removeSkillsFromOwner() + { + if (!hasPassiveSkills()) + { + return; + } + + final Player player = asPlayer(); + if (player != null) + { + for (SkillHolder holder : _itemTemplate.getSkills()) + { + final Skill skill = holder.getSkill(); + if (skill.isPassive()) + { + player.removeSkill(skill, false, true); + } + } + } + } + + @Override + public boolean isItem() + { + return true; + } + + @Override + public Player asPlayer() + { + if ((_owner == null) && (_ownerId != 0)) + { + _owner = World.getPlayer(_ownerId); + } + + return _owner; + } + + public int getEquipReuseDelay() + { + return _itemTemplate.getEquipReuseDelay(); + } + + /** + * @param player + * @param command + */ + public void onBypassFeedback(Player player, String command) + { + if (!command.startsWith("Script")) + { + return; + } + + final String questName = command.substring(6); + String event = null; + final int idx = questName.indexOf(' '); + if (idx > 0) + { + event = questName.substring(idx).trim(); + } + + if (event != null) + { + if (EventDispatcher.getInstance().hasListener(EventType.ON_ITEM_BYPASS_EVENT, getTemplate())) + { + EventDispatcher.getInstance().notifyEventAsync(new OnItemBypassEvent(this, player, event), getTemplate()); + } + } + else if (EventDispatcher.getInstance().hasListener(EventType.ON_ITEM_TALK, getTemplate())) + { + EventDispatcher.getInstance().notifyEventAsync(new OnItemTalk(this, player), getTemplate()); + } + } + + @Override + public boolean isChargedShot(ShotType type) + { + return (_shotsMask & type.getMask()) == type.getMask(); + } + + @Override + public void setChargedShot(ShotType type, boolean charged) + { + if (charged) + { + _shotsMask |= type.getMask(); + } + else + { + _shotsMask &= ~type.getMask(); + } + } + + public void unChargeAllShots() + { + _shotsMask = 0; + } + + /** + * Clears all the enchant bonuses if item is enchanted and containing bonuses for enchant value. + */ + public void clearEnchantStats() + { + final Player player = asPlayer(); + if (player == null) + { + _enchantOptions.clear(); + return; + } + + for (Options op : _enchantOptions) + { + op.remove(player); + } + + _enchantOptions.clear(); + } + + @Override + public void setHeading(int heading) + { + } + + public void stopAllTasks() + { + ItemLifeTimeTaskManager.getInstance().remove(this); + } + + public ItemVariables getVariables() + { + final ItemVariables vars = getScript(ItemVariables.class); + return vars != null ? vars : addScript(new ItemVariables(getObjectId())); + } + + public int getTransmogId() + { + if (!TransmogConfig.ENABLE_TRANSMOG) + { + return 0; + } + + return getVariables().getInt(ItemVariables.TRANSMOG_ID, 0); + } + + public void setTransmogId(int transmogId) + { + getVariables().set(ItemVariables.TRANSMOG_ID, transmogId); + getVariables().storeMe(); + } + + public void removeTransmog() + { + getVariables().remove(ItemVariables.TRANSMOG_ID); + getVariables().storeMe(); + } + + /** + * Returns the item in String format + * @return String + */ + @Override + public String toString() + { + final StringBuilder sb = new StringBuilder(); + sb.append(_itemTemplate); + sb.append('['); + sb.append(getObjectId()); + sb.append(']'); + return sb.toString(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/recipe/ManufactureItem.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/recipe/ManufactureItem.java new file mode 100644 index 0000000..4fe470e --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/recipe/ManufactureItem.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.item.recipe; + +import org.l2jmobius.gameserver.data.xml.RecipeData; + +public class ManufactureItem +{ + private final int _recipeId; + private final int _cost; + private final boolean _isDwarven; + + public ManufactureItem(int recipeId, int cost) + { + _recipeId = recipeId; + _cost = cost; + _isDwarven = RecipeData.getInstance().getRecipeList(_recipeId).isDwarvenRecipe(); + } + + public int getRecipeId() + { + return _recipeId; + } + + public int getCost() + { + return _cost; + } + + public boolean isDwarven() + { + return _isDwarven; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/recipe/RecipeItemInfo.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/recipe/RecipeItemInfo.java new file mode 100644 index 0000000..9908aec --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/recipe/RecipeItemInfo.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.item.recipe; + +import org.l2jmobius.gameserver.entity.item.instance.Item; + +/** + * Class explanation:
        + * For item counting or checking purposes. When you don't want to modify inventory
        + * class contains itemId, quantity, ownerId, referencePrice, but not objectId
        + * is stored, this will be only "list" of items with it's owner + */ +public class RecipeItemInfo +{ + private final int _itemId; + private int _quantity; + private final int _referencePrice; + private final String _itemName; + + /** + * @param item + * @param quantity of that item + */ + public RecipeItemInfo(Item item, int quantity) + { + super(); + _itemId = item.getId(); + _quantity = quantity; + _itemName = item.getTemplate().getName(); + _referencePrice = item.getReferencePrice(); + } + + /** + * @return the quantity. + */ + public int getQuantity() + { + return _quantity; + } + + /** + * @param quantity The quantity to set. + */ + public void setQuantity(int quantity) + { + _quantity = quantity; + } + + public int getReferencePrice() + { + return _referencePrice; + } + + /** + * @return the itemId. + */ + public int getItemId() + { + return _itemId; + } + + /** + * @return the itemName. + */ + public String getItemName() + { + return _itemName; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/recipe/RecipeList.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/recipe/RecipeList.java new file mode 100644 index 0000000..ea06cb8 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/recipe/RecipeList.java @@ -0,0 +1,210 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.item.recipe; + +import org.l2jmobius.gameserver.data.holders.RecipeHolder; +import org.l2jmobius.gameserver.data.holders.RecipeStatHolder; +import org.l2jmobius.gameserver.util.StatSet; + +/** + * This class describes a Recipe used by Dwarf to craft Item. All RecipeList are made of RecipeHolder (1 line of the recipe : Item-Quantity needed). + */ +public class RecipeList +{ + /** The table containing all RecipeHolder (1 line of the recipe : Item-Quantity needed) of the RecipeList */ + private RecipeHolder[] _recipes; + + /** The table containing all RecipeStatHolder for the statUse parameter of the RecipeList */ + private RecipeStatHolder[] _statUse; + + /** The table containing all RecipeStatHolder for the altStatChange parameter of the RecipeList */ + private RecipeStatHolder[] _altStatChange; + + /** The Identifier of the Instance */ + private final int _id; + + /** The crafting level needed to use this RecipeList */ + private final int _level; + + /** The Identifier of the RecipeList */ + private final int _recipeId; + + /** The name of the RecipeList */ + private final String _recipeName; + + /** The crafting success rate when using the RecipeList */ + private final int _successRate; + + /** The Identifier of the Item crafted with this RecipeList */ + private final int _itemId; + + /** The quantity of Item crafted when using this RecipeList */ + private final int _count; + + /** If this a common or a dwarven recipe */ + private final boolean _isDwarvenRecipe; + + /** + * Constructor of RecipeList (create a new Recipe). + * @param set + */ + public RecipeList(StatSet set) + { + _recipes = new RecipeHolder[0]; + _statUse = new RecipeStatHolder[0]; + _altStatChange = new RecipeStatHolder[0]; + _id = set.getInt("id"); + _level = set.getInt("craftLevel"); + _recipeId = set.getInt("recipeId"); + _recipeName = set.getString("recipeName"); + _successRate = set.getInt("successRate"); + _itemId = set.getInt("itemId"); + _count = set.getInt("count"); + _isDwarvenRecipe = set.getBoolean("isDwarvenRecipe"); + } + + /** + * Add a RecipeHolder to the RecipeList (add a line Item-Quantity needed to the Recipe). + * @param recipe + */ + public void addRecipe(RecipeHolder recipe) + { + final int len = _recipes.length; + final RecipeHolder[] tmp = new RecipeHolder[len + 1]; + System.arraycopy(_recipes, 0, tmp, 0, len); + tmp[len] = recipe; + _recipes = tmp; + } + + /** + * Add a RecipeStatHolder of the statUse parameter to the RecipeList. + * @param statUse + */ + public void addStatUse(RecipeStatHolder statUse) + { + final int len = _statUse.length; + final RecipeStatHolder[] tmp = new RecipeStatHolder[len + 1]; + System.arraycopy(_statUse, 0, tmp, 0, len); + tmp[len] = statUse; + _statUse = tmp; + } + + /** + * Add a RecipeStatHolder of the altStatChange parameter to the RecipeList. + * @param statChange + */ + public void addAltStatChange(RecipeStatHolder statChange) + { + final int len = _altStatChange.length; + final RecipeStatHolder[] tmp = new RecipeStatHolder[len + 1]; + System.arraycopy(_altStatChange, 0, tmp, 0, len); + tmp[len] = statChange; + _altStatChange = tmp; + } + + /** + * @return the Identifier of the Instance. + */ + public int getId() + { + return _id; + } + + /** + * @return the crafting level needed to use this RecipeList. + */ + public int getLevel() + { + return _level; + } + + /** + * @return the Identifier of the RecipeList. + */ + public int getRecipeId() + { + return _recipeId; + } + + /** + * @return the name of the RecipeList. + */ + public String getRecipeName() + { + return _recipeName; + } + + /** + * @return the crafting success rate when using the RecipeList. + */ + public int getSuccessRate() + { + return _successRate; + } + + /** + * @return the Identifier of the Item crafted with this RecipeList. + */ + public int getItemId() + { + return _itemId; + } + + /** + * @return the quantity of Item crafted when using this RecipeList. + */ + public int getCount() + { + return _count; + } + + /** + * @return {@code true} if this a Dwarven recipe or {@code false} if it is a Common recipe + */ + public boolean isDwarvenRecipe() + { + return _isDwarvenRecipe; + } + + /** + * @return the table containing all RecipeHolder (1 line of the recipe : Item-Quantity needed) of the RecipeList. + */ + public RecipeHolder[] getRecipes() + { + return _recipes; + } + + /** + * @return the table containing all RecipeStatHolder of the statUse parameter of the RecipeList. + */ + public RecipeStatHolder[] getStatUse() + { + return _statUse; + } + + /** + * @return the table containing all RecipeStatHolder of the AltStatChange parameter of the RecipeList. + */ + public RecipeStatHolder[] getAltStatChange() + { + return _altStatChange; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/type/ActionType.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/type/ActionType.java new file mode 100644 index 0000000..034a744 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/type/ActionType.java @@ -0,0 +1,51 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.item.type; + +/** + * Action Type enumerated. + * @author nBd + */ +public enum ActionType +{ + CALC, + CALL_SKILL, + CAPSULE, + CREATE_MPCC, + DICE, + EQUIP, + FISHINGSHOT, + HARVEST, + HIDE_NAME, + KEEP_EXP, + NICK_COLOR, + NONE, + PEEL, + RECIPE, + SEED, + SHOW_ADVENTURER_GUIDE_BOOK, + SHOW_HTML, + SHOW_SSQ_STATUS, + SKILL_MAINTAIN, + SKILL_REDUCE, + SOULSHOT, + SPIRITSHOT, + START_QUEST, + SUMMON_SOULSHOT, + SUMMON_SPIRITSHOT, + XMAS_OPEN, +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/type/ArmorType.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/type/ArmorType.java new file mode 100644 index 0000000..842bdb9 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/type/ArmorType.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.item.type; + +/** + * Armor Type enumerated. + */ +public enum ArmorType implements ItemType +{ + NONE, + LIGHT, + HEAVY, + MAGIC, + SIGIL, + + // L2J CUSTOM + SHIELD; + + final int _mask; + + /** + * Constructor of the ArmorType. + */ + ArmorType() + { + _mask = 1 << (ordinal() + WeaponType.values().length); + } + + /** + * @return the ID of the ArmorType after applying a mask. + */ + @Override + public int mask() + { + return _mask; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/type/CrystalType.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/type/CrystalType.java new file mode 100644 index 0000000..7e9369b --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/type/CrystalType.java @@ -0,0 +1,82 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.item.type; + +/** + * Crystal Type enumerated. + * @author Adry_85 + */ +public enum CrystalType +{ + NONE(0, 0, 0, 0), + D(1, 1458, 11, 90), + C(2, 1459, 6, 45), + B(3, 1460, 11, 67), + A(4, 1461, 20, 145), + S(5, 1462, 25, 250); + + private final int _level; + private final int _crystalId; + private final int _crystalEnchantBonusArmor; + private final int _crystalEnchantBonusWeapon; + + CrystalType(int level, int crystalId, int crystalEnchantBonusArmor, int crystalEnchantBonusWeapon) + { + _level = level; + _crystalId = crystalId; + _crystalEnchantBonusArmor = crystalEnchantBonusArmor; + _crystalEnchantBonusWeapon = crystalEnchantBonusWeapon; + } + + /** + * Gets the crystal type ID. + * @return the crystal type ID + */ + public int getLevel() + { + return _level; + } + + /** + * Gets the item ID of the crystal. + * @return the item ID of the crystal + */ + public int getCrystalId() + { + return _crystalId; + } + + public int getCrystalEnchantBonusArmor() + { + return _crystalEnchantBonusArmor; + } + + public int getCrystalEnchantBonusWeapon() + { + return _crystalEnchantBonusWeapon; + } + + public boolean isGreater(CrystalType crystalType) + { + return getLevel() > crystalType.getLevel(); + } + + public boolean isLesser(CrystalType crystalType) + { + return getLevel() < crystalType.getLevel(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/type/EtcItemType.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/type/EtcItemType.java new file mode 100644 index 0000000..caa1b81 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/type/EtcItemType.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.item.type; + +/** + * EtcItem Type enumerated. + */ +public enum EtcItemType implements ItemType +{ + NONE, + ARROW, + POTION, + SCRL_ENCHANT_WP, + SCRL_ENCHANT_AM, + SCROLL, + RECIPE, + MATERIAL, + PET_COLLAR, + CASTLE_GUARD, + LOTTO, + RACE_TICKET, + DYE, + SEED, + CROP, + MATURECROP, + HARVEST, + SEED2, + TICKET_OF_LORD, + LURE, + BLESS_SCRL_ENCHANT_WP, + BLESS_SCRL_ENCHANT_AM, + COUPON, + ELIXIR, + SCRL_ENCHANT_ATTR, + SCRL_INC_ENCHANT_PROP_WP, + SCRL_INC_ENCHANT_PROP_AM, + ANCIENT_CRYSTAL_ENCHANT_WP, + ANCIENT_CRYSTAL_ENCHANT_AM, + RUNE_SELECT, + RUNE, + + // L2J CUSTOM, BACKWARD COMPATIBILITY + SHOT; + + /** + * @return the ID of the item after applying the mask. + */ + @Override + public int mask() + { + return 0; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/type/ItemType.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/type/ItemType.java new file mode 100644 index 0000000..6eac454 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/type/ItemType.java @@ -0,0 +1,26 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.item.type; + +/** + * Created for allow comparing different item types + * @author DS + */ +public interface ItemType +{ + int mask(); +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/type/MaterialType.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/type/MaterialType.java new file mode 100644 index 0000000..6a45070 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/type/MaterialType.java @@ -0,0 +1,53 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.item.type; + +/** + * Material Type enumerated. + * @author Adry_85 + */ +public enum MaterialType +{ + STEEL, + FINE_STEEL, + COTTON, + BLOOD_STEEL, + BRONZE, + SILVER, + GOLD, + MITHRIL, + ORIHARUKON, + PAPER, + WOOD, + CLOTH, + LEATHER, + BONE, + HORN, + DAMASCUS, + ADAMANTAITE, + CHRYSOLITE, + CRYSTAL, + LIQUID, + SCALE_OF_DRAGON, + DYESTUFF, + COBWEB, + SEED, + FISH, + RUNE_XP, + RUNE_SP, + RUNE_REMOVE_PENALTY; +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/type/WeaponType.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/type/WeaponType.java new file mode 100644 index 0000000..357ee90 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/item/type/WeaponType.java @@ -0,0 +1,76 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.item.type; + +import org.l2jmobius.gameserver.mechanics.stats.TraitType; + +/** + * Weapon Type enumerated. + * @author mkizub + */ +public enum WeaponType implements ItemType +{ + SWORD(TraitType.SWORD), + BLUNT(TraitType.BLUNT), + DAGGER(TraitType.DAGGER), + BOW(TraitType.BOW), + POLE(TraitType.POLE), + NONE(TraitType.NONE), + DUAL(TraitType.DUAL), + ETC(TraitType.ETC), + FIST(TraitType.FIST), + DUALFIST(TraitType.DUALFIST), + FISHINGROD(TraitType.NONE), + FLAG(TraitType.NONE), + OWNTHING(TraitType.NONE), + DUALDAGGER(TraitType.DUALDAGGER); + + private final int _mask; + private final TraitType _traitType; + + /** + * Constructor of the WeaponType. + * @param traitType + */ + WeaponType(TraitType traitType) + { + _mask = 1 << ordinal(); + _traitType = traitType; + } + + /** + * @return the ID of the item after applying the mask. + */ + @Override + public int mask() + { + return _mask; + } + + /** + * @return TraitType the type of the WeaponType + */ + public TraitType getTraitType() + { + return _traitType; + } + + public boolean isRanged() + { + return (this == BOW); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/itemcontainer/ClanWarehouse.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/itemcontainer/ClanWarehouse.java new file mode 100644 index 0000000..2f44ea3 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/itemcontainer/ClanWarehouse.java @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.itemcontainer; + +import org.l2jmobius.gameserver.config.PlayerConfig; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.clan.Clan; +import org.l2jmobius.gameserver.entity.item.enums.ItemLocation; +import org.l2jmobius.gameserver.entity.item.enums.ItemProcessType; +import org.l2jmobius.gameserver.entity.item.instance.Item; +import org.l2jmobius.gameserver.mechanics.events.EventDispatcher; +import org.l2jmobius.gameserver.mechanics.events.EventType; +import org.l2jmobius.gameserver.mechanics.events.holders.actor.player.clanwh.OnPlayerClanWHItemAdd; +import org.l2jmobius.gameserver.mechanics.events.holders.actor.player.clanwh.OnPlayerClanWHItemDestroy; +import org.l2jmobius.gameserver.mechanics.events.holders.actor.player.clanwh.OnPlayerClanWHItemTransfer; + +public class ClanWarehouse extends Warehouse +{ + private final Clan _clan; + + public ClanWarehouse(Clan clan) + { + _clan = clan; + } + + @Override + public String getName() + { + return "ClanWarehouse"; + } + + @Override + public int getOwnerId() + { + return _clan.getId(); + } + + @Override + public Player getOwner() + { + return _clan.getLeader().getPlayer(); + } + + @Override + public ItemLocation getBaseLocation() + { + return ItemLocation.CLANWH; + } + + public String getLocationId() + { + return "0"; + } + + public int getLocationId(boolean dummy) + { + return 0; + } + + public void setLocationId(Player dummy) + { + } + + @Override + public boolean validateCapacity(long slots) + { + return (_items.size() + slots) <= PlayerConfig.WAREHOUSE_SLOTS_CLAN; + } + + @Override + public Item addItem(ItemProcessType process, int itemId, int count, Player actor, Object reference) + { + final Item item = super.addItem(process, itemId, count, actor, reference); + + // Notify to scripts + if (EventDispatcher.getInstance().hasListener(EventType.ON_PLAYER_CLAN_WH_ITEM_ADD, item.getTemplate())) + { + EventDispatcher.getInstance().notifyEventAsync(new OnPlayerClanWHItemAdd(actor, item, this), item.getTemplate()); + } + + return item; + } + + @Override + public Item addItem(ItemProcessType process, Item item, Player actor, Object reference) + { + // Notify to scripts + if (EventDispatcher.getInstance().hasListener(EventType.ON_PLAYER_CLAN_WH_ITEM_ADD, item.getTemplate())) + { + EventDispatcher.getInstance().notifyEventAsync(new OnPlayerClanWHItemAdd(actor, item, this), item.getTemplate()); + } + + return super.addItem(process, item, actor, reference); + } + + @Override + public Item destroyItem(ItemProcessType process, Item item, int count, Player actor, Object reference) + { + // Notify to scripts + if (EventDispatcher.getInstance().hasListener(EventType.ON_PLAYER_CLAN_WH_ITEM_DESTROY, item.getTemplate())) + { + EventDispatcher.getInstance().notifyEventAsync(new OnPlayerClanWHItemDestroy(actor, item, count, this), item.getTemplate()); + } + + return super.destroyItem(process, item, count, actor, reference); + } + + @Override + public Item transferItem(ItemProcessType process, int objectId, int count, ItemContainer target, Player actor, Object reference) + { + final Item item = getItemByObjectId(objectId); + + // Notify to scripts + if (EventDispatcher.getInstance().hasListener(EventType.ON_PLAYER_CLAN_WH_ITEM_TRANSFER, item.getTemplate())) + { + EventDispatcher.getInstance().notifyEventAsync(new OnPlayerClanWHItemTransfer(actor, item, count, target), item.getTemplate()); + } + + return super.transferItem(process, objectId, count, target, actor, reference); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/itemcontainer/Inventory.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/itemcontainer/Inventory.java new file mode 100644 index 0000000..80d23a0 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/itemcontainer/Inventory.java @@ -0,0 +1,1570 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.itemcontainer; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.function.Predicate; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.commons.database.DatabaseFactory; +import org.l2jmobius.gameserver.config.PlayerConfig; +import org.l2jmobius.gameserver.config.custom.TransmogConfig; +import org.l2jmobius.gameserver.data.holders.ArmorSet; +import org.l2jmobius.gameserver.data.xml.ArmorSetData; +import org.l2jmobius.gameserver.entity.World; +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.item.ItemTemplate; +import org.l2jmobius.gameserver.entity.item.enums.BodyPart; +import org.l2jmobius.gameserver.entity.item.enums.ItemLocation; +import org.l2jmobius.gameserver.entity.item.enums.ItemProcessType; +import org.l2jmobius.gameserver.entity.item.instance.Item; +import org.l2jmobius.gameserver.entity.item.type.EtcItemType; +import org.l2jmobius.gameserver.entity.item.type.WeaponType; +import org.l2jmobius.gameserver.managers.ItemManager; +import org.l2jmobius.gameserver.mechanics.events.EventDispatcher; +import org.l2jmobius.gameserver.mechanics.events.EventType; +import org.l2jmobius.gameserver.mechanics.events.holders.actor.player.OnPlayerItemUnequip; +import org.l2jmobius.gameserver.mechanics.skill.Skill; +import org.l2jmobius.gameserver.mechanics.skill.holders.SkillHolder; +import org.l2jmobius.gameserver.network.serverpackets.SkillCoolTime; + +/** + * This class manages inventory + * @version $Revision: 1.13.2.9.2.12 $ $Date: 2005/03/29 23:15:15 $ rewritten 23.2.2006 by Advi + */ +public abstract class Inventory extends ItemContainer +{ + protected static final Logger LOGGER = Logger.getLogger(Inventory.class.getName()); + + public interface PaperdollListener + { + void notifyEquiped(int slot, Item inst, Inventory inventory); + + void notifyUnequiped(int slot, Item inst, Inventory inventory); + } + + // Common Items + public static final int ADENA_ID = 57; + public static final int ANCIENT_ADENA_ID = 5575; + + public static final int MAX_ADENA = PlayerConfig.MAX_ADENA; + + public static final int PAPERDOLL_UNDER = 0; + public static final int PAPERDOLL_LEAR = 1; + public static final int PAPERDOLL_REAR = 2; + public static final int PAPERDOLL_NECK = 3; + public static final int PAPERDOLL_LFINGER = 4; + public static final int PAPERDOLL_RFINGER = 5; + public static final int PAPERDOLL_HEAD = 6; + public static final int PAPERDOLL_RHAND = 7; + public static final int PAPERDOLL_LHAND = 8; + public static final int PAPERDOLL_GLOVES = 9; + public static final int PAPERDOLL_CHEST = 10; + public static final int PAPERDOLL_LEGS = 11; + public static final int PAPERDOLL_FEET = 12; + public static final int PAPERDOLL_CLOAK = 13; + public static final int PAPERDOLL_FACE = 14; + public static final int PAPERDOLL_HAIR = 15; + public static final int PAPERDOLL_HAIR2 = 16; + public static final int PAPERDOLL_TOTALSLOTS = 17; + + // Speed percentage mods + public static final double MAX_ARMOR_WEIGHT = 12000; + + private final Item[] _paperdoll; + private final List _paperdollListeners; + + // protected to be accessed from child classes only + protected int _totalWeight; + + // used to quickly check for using of items of special type + private int _wearedMask; + + // Recorder of alterations in inventory + private static class ChangeRecorder implements PaperdollListener + { + private final Inventory _inventory; + private final Collection _changed = new ArrayDeque<>(1); + + /** + * Constructor of the ChangeRecorder + * @param inventory + */ + ChangeRecorder(Inventory inventory) + { + _inventory = inventory; + _inventory.addPaperdollListener(this); + } + + /** + * Add alteration in inventory when item equipped + * @param slot + * @param item + * @param inventory + */ + @Override + public void notifyEquiped(int slot, Item item, Inventory inventory) + { + _changed.add(item); + } + + /** + * Add alteration in inventory when item unequipped + * @param slot + * @param item + * @param inventory + */ + @Override + public void notifyUnequiped(int slot, Item item, Inventory inventory) + { + _changed.add(item); + } + + /** + * Returns alterations in inventory + * @return Collection : Collection of altered items + */ + public Collection getChangedItems() + { + return _changed; + } + } + + private static class BowCrossRodListener implements PaperdollListener + { + private static BowCrossRodListener instance = new BowCrossRodListener(); + + public static BowCrossRodListener getInstance() + { + return instance; + } + + @Override + public void notifyUnequiped(int slot, Item item, Inventory inventory) + { + if (slot != PAPERDOLL_RHAND) + { + return; + } + + if (item.getItemType() == WeaponType.BOW) + { + final Item arrow = inventory.getPaperdollItem(PAPERDOLL_LHAND); + if (arrow != null) + { + inventory.setPaperdollItem(PAPERDOLL_LHAND, null); + } + } + else if (item.getItemType() == WeaponType.FISHINGROD) + { + final Item lure = inventory.getPaperdollItem(PAPERDOLL_LHAND); + if (lure != null) + { + inventory.setPaperdollItem(PAPERDOLL_LHAND, null); + } + } + } + + @Override + public void notifyEquiped(int slot, Item item, Inventory inventory) + { + if (slot != PAPERDOLL_RHAND) + { + return; + } + + if (item.getItemType() == WeaponType.BOW) + { + final Item arrow = inventory.findArrowForBow(item.getTemplate()); + if (arrow != null) + { + inventory.setPaperdollItem(PAPERDOLL_LHAND, arrow); + } + } + } + } + + private static class StatsListener implements PaperdollListener + { + private static StatsListener instance = new StatsListener(); + + public static StatsListener getInstance() + { + return instance; + } + + @Override + public void notifyUnequiped(int slot, Item item, Inventory inventory) + { + inventory.getOwner().removeStatsOwner(item); + } + + @Override + public void notifyEquiped(int slot, Item item, Inventory inventory) + { + inventory.getOwner().addStatFuncs(item.getStatFuncs(inventory.getOwner())); + } + } + + private static class ItemSkillsListener implements PaperdollListener + { + private static ItemSkillsListener instance = new ItemSkillsListener(); + + public static ItemSkillsListener getInstance() + { + return instance; + } + + @Override + public void notifyUnequiped(int slot, Item item, Inventory inventory) + { + if (!inventory.getOwner().isPlayer()) + { + return; + } + + final Player player = inventory.getOwner().asPlayer(); + Skill enchant4Skill; + Skill itemSkill; + final ItemTemplate it = item.getTemplate(); + boolean update = false; + boolean updateTimeStamp = false; + + // Remove augmentation bonuses on unequip. + if (item.isAugmented()) + { + item.getAugmentation().removeBonus(player); + } + + // Remove skills bestowed from +4 armor. + if (item.getEnchantLevel() >= 4) + { + enchant4Skill = it.getEnchant4Skill(); + if (enchant4Skill != null) + { + player.removeSkill(enchant4Skill, false, enchant4Skill.isPassive()); + update = true; + } + } + + item.clearEnchantStats(); + + final SkillHolder[] skills = it.getSkills(); + if (skills != null) + { + for (SkillHolder skillInfo : skills) + { + if (skillInfo == null) + { + continue; + } + + itemSkill = skillInfo.getSkill(); + if (itemSkill != null) + { + player.removeSkill(itemSkill, false, itemSkill.isPassive()); + update = true; + } + else + { + LOGGER.warning("Inventory.ItemSkillsListener.Weapon: Incorrect skill: " + skillInfo + "."); + } + } + } + + if (item.isArmor()) + { + for (Item itm : inventory.getItems()) + { + if (!itm.isEquipped() || (itm.getTemplate().getSkills() == null) || itm.equals(item)) + { + continue; + } + + for (SkillHolder sk : itm.getTemplate().getSkills()) + { + if (player.getSkillLevel(sk.getSkillId()) != 0) + { + continue; + } + + itemSkill = sk.getSkill(); + if (itemSkill != null) + { + player.addSkill(itemSkill, false); + if (itemSkill.isActive()) + { + if (!player.hasSkillReuse(itemSkill.getReuseHashCode())) + { + final int equipDelay = item.getEquipReuseDelay(); + if (equipDelay > 0) + { + player.addTimeStamp(itemSkill, equipDelay); + player.disableSkill(itemSkill, equipDelay); + } + } + + updateTimeStamp = true; + } + + update = true; + } + } + } + } + + // Apply skill, if weapon have "skills on unequip". + final Skill unequipSkill = it.getUnequipSkill(); + if (unequipSkill != null) + { + unequipSkill.activateSkill(player, Collections.singletonList(player)); + } + + if (update) + { + player.sendSkillList(); + + if (updateTimeStamp) + { + player.sendPacket(new SkillCoolTime(player)); + } + } + } + + @Override + public void notifyEquiped(int slot, Item item, Inventory inventory) + { + if (!inventory.getOwner().isPlayer()) + { + return; + } + + final Player player = inventory.getOwner().asPlayer(); + Skill enchant4Skill; + Skill itemSkill; + final ItemTemplate it = item.getTemplate(); + boolean update = false; + boolean updateTimeStamp = false; + + // Apply augmentation bonuses on equip. + if (item.isAugmented()) + { + item.getAugmentation().applyBonus(player); + } + + // Add skills bestowed from +4 armor. + if (item.getEnchantLevel() >= 4) + { + enchant4Skill = it.getEnchant4Skill(); + if (enchant4Skill != null) + { + player.addSkill(enchant4Skill, false); + update = true; + } + } + + final SkillHolder[] skills = it.getSkills(); + if (skills != null) + { + for (SkillHolder skillInfo : skills) + { + if (skillInfo == null) + { + continue; + } + + itemSkill = skillInfo.getSkill(); + if (itemSkill != null) + { + itemSkill.setReferenceItemId(item.getId()); + player.addSkill(itemSkill, false); + if (itemSkill.isActive()) + { + if (!player.hasSkillReuse(itemSkill.getReuseHashCode())) + { + final int equipDelay = item.getEquipReuseDelay(); + if (equipDelay > 0) + { + player.addTimeStamp(itemSkill, equipDelay); + player.disableSkill(itemSkill, equipDelay); + } + } + + updateTimeStamp = true; + } + + update = true; + } + else + { + LOGGER.warning("Inventory.ItemSkillsListener.Weapon: Incorrect skill: " + skillInfo + "."); + } + } + } + + if (update) + { + player.sendSkillList(); + + if (updateTimeStamp) + { + player.sendPacket(new SkillCoolTime(player)); + } + } + } + } + + private static class ArmorSetListener implements PaperdollListener + { + private static ArmorSetListener instance = new ArmorSetListener(); + + public static ArmorSetListener getInstance() + { + return instance; + } + + @Override + public void notifyEquiped(int slot, Item item, Inventory inventory) + { + if (!inventory.getOwner().isPlayer()) + { + return; + } + + final Player player = inventory.getOwner().asPlayer(); + + // Checks if player is wearing a chest item. + final Item chestItem = inventory.getPaperdollItem(PAPERDOLL_CHEST); + if (chestItem == null) + { + return; + } + + // Checks for armor set for the equipped chest. + if (!ArmorSetData.getInstance().isArmorSet(chestItem.getId())) + { + return; + } + + final ArmorSet armorSet = ArmorSetData.getInstance().getSet(chestItem.getId()); + boolean update = false; + boolean updateTimeStamp = false; + + // Checks if equipped item is part of set. + if (armorSet.containItem(slot, item.getId())) + { + if (armorSet.containAll(player)) + { + Skill itemSkill; + final List skills = armorSet.getSkills(); + if (skills != null) + { + for (SkillHolder holder : skills) + { + itemSkill = holder.getSkill(); + if (itemSkill != null) + { + player.addSkill(itemSkill, false); + if (itemSkill.isActive()) + { + if (!player.hasSkillReuse(itemSkill.getReuseHashCode())) + { + final int equipDelay = item.getEquipReuseDelay(); + if (equipDelay > 0) + { + player.addTimeStamp(itemSkill, equipDelay); + player.disableSkill(itemSkill, equipDelay); + } + } + + updateTimeStamp = true; + } + + update = true; + } + else + { + LOGGER.warning("Inventory.ArmorSetListener: Incorrect skill: " + holder + "."); + } + } + } + + if (armorSet.containShield(player)) // has shield from set + { + for (SkillHolder holder : armorSet.getShieldSkillId()) + { + if (holder.getSkill() != null) + { + player.addSkill(holder.getSkill(), false); + update = true; + } + else + { + LOGGER.warning("Inventory.ArmorSetListener: Incorrect skill: " + holder + "."); + } + } + } + + if (armorSet.isEnchanted6(player)) // has all parts of set enchanted to 6 or more + { + for (SkillHolder holder : armorSet.getEnchant6skillId()) + { + if (holder.getSkill() != null) + { + player.addSkill(holder.getSkill(), false); + update = true; + } + else + { + LOGGER.warning("Inventory.ArmorSetListener: Incorrect skill: " + holder + "."); + } + } + } + } + } + else if (armorSet.containShield(item.getId())) + { + for (SkillHolder holder : armorSet.getShieldSkillId()) + { + if (holder.getSkill() != null) + { + player.addSkill(holder.getSkill(), false); + update = true; + } + else + { + LOGGER.warning("Inventory.ArmorSetListener: Incorrect skill: " + holder + "."); + } + } + } + + if (update) + { + player.sendSkillList(); + + if (updateTimeStamp) + { + player.sendPacket(new SkillCoolTime(player)); + } + } + } + + @Override + public void notifyUnequiped(int slot, Item item, Inventory inventory) + { + if (!inventory.getOwner().isPlayer()) + { + return; + } + + final Player player = inventory.getOwner().asPlayer(); + boolean remove = false; + Skill itemSkill; + List skills = null; + List shieldSkill = null; // shield skill + List skillId6 = null; // enchant +6 skill + if (slot == PAPERDOLL_CHEST) + { + if (!ArmorSetData.getInstance().isArmorSet(item.getId())) + { + return; + } + + final ArmorSet armorSet = ArmorSetData.getInstance().getSet(item.getId()); + remove = true; + skills = armorSet.getSkills(); + shieldSkill = armorSet.getShieldSkillId(); + skillId6 = armorSet.getEnchant6skillId(); + } + else + { + final Item chestItem = inventory.getPaperdollItem(PAPERDOLL_CHEST); + if (chestItem == null) + { + return; + } + + final ArmorSet armorSet = ArmorSetData.getInstance().getSet(chestItem.getId()); + if (armorSet == null) + { + return; + } + + if (armorSet.containItem(slot, item.getId())) // removed part of set + { + remove = true; + skills = armorSet.getSkills(); + shieldSkill = armorSet.getShieldSkillId(); + skillId6 = armorSet.getEnchant6skillId(); + } + else if (armorSet.containShield(item.getId())) // removed shield + { + remove = true; + shieldSkill = armorSet.getShieldSkillId(); + } + } + + if (remove) + { + if (skills != null) + { + for (SkillHolder holder : skills) + { + itemSkill = holder.getSkill(); + if (itemSkill != null) + { + player.removeSkill(itemSkill, false, itemSkill.isPassive()); + } + else + { + LOGGER.warning("Inventory.ArmorSetListener: Incorrect skill: " + holder + "."); + } + } + } + + if (shieldSkill != null) + { + for (SkillHolder holder : shieldSkill) + { + itemSkill = holder.getSkill(); + if (itemSkill != null) + { + player.removeSkill(itemSkill, false, itemSkill.isPassive()); + } + else + { + LOGGER.warning("Inventory.ArmorSetListener: Incorrect skill: " + holder + "."); + } + } + } + + if (skillId6 != null) + { + for (SkillHolder holder : skillId6) + { + itemSkill = holder.getSkill(); + if (itemSkill != null) + { + player.removeSkill(itemSkill, false, itemSkill.isPassive()); + } + else + { + LOGGER.warning("Inventory.ArmorSetListener: Incorrect skill: " + holder + "."); + } + } + } + + player.checkItemRestriction(); + player.sendSkillList(); + } + } + } + + /** + * Constructor of the inventory + */ + protected Inventory() + { + _paperdoll = new Item[PAPERDOLL_TOTALSLOTS]; + _paperdollListeners = new ArrayList<>(); + if (this instanceof PlayerInventory) + { + addPaperdollListener(ArmorSetListener.getInstance()); + addPaperdollListener(BowCrossRodListener.getInstance()); + addPaperdollListener(ItemSkillsListener.getInstance()); + } + + // common + addPaperdollListener(StatsListener.getInstance()); + } + + protected abstract ItemLocation getEquipLocation(); + + /** + * Returns the instance of new ChangeRecorder + * @return ChangeRecorder + */ + private ChangeRecorder newRecorder() + { + return new ChangeRecorder(this); + } + + /** + * Drop item from inventory and updates database + * @param process : ItemProcessType identifier of process triggering this action + * @param item : Item to be dropped + * @param actor : Player Player requesting the item drop + * @param reference : Object Object referencing current action like NPC selling item or previous item in transformation + * @return Item corresponding to the destroyed item or the updated item in inventory + */ + public Item dropItem(ItemProcessType process, Item item, Player actor, Object reference) + { + if (item == null) + { + return null; + } + + synchronized (item) + { + if (!_items.contains(item)) + { + return null; + } + + removeItem(item); + item.setOwnerId(process, 0, actor, reference); + item.setItemLocation(ItemLocation.VOID); + item.setLastChange(Item.REMOVED); + + item.updateDatabase(); + refreshWeight(); + } + + return item; + } + + /** + * Drop item from inventory by using its objectID and updates database + * @param process : ItemProcessType identifier of process triggering this action + * @param objectId : int Item Instance identifier of the item to be dropped + * @param count : int Quantity of items to be dropped + * @param actor : Player Player requesting the item drop + * @param reference : Object Object referencing current action like NPC selling item or previous item in transformation + * @return Item corresponding to the destroyed item or the updated item in inventory + */ + public Item dropItem(ItemProcessType process, int objectId, int count, Player actor, Object reference) + { + Item item = getItemByObjectId(objectId); + if (item == null) + { + return null; + } + + synchronized (item) + { + if (!_items.contains(item)) + { + return null; + } + + // Adjust item quantity and create new instance to drop. + // Directly drop entire item. + if (item.getCount() > count) + { + item.changeCount(process, -count, actor, reference); + item.setLastChange(Item.MODIFIED); + item.updateDatabase(); + + final Item newItem = ItemManager.createItem(process, item.getId(), count, actor, reference); + newItem.updateDatabase(); + refreshWeight(); + return newItem; + } + } + + return dropItem(process, item, actor, reference); + } + + /** + * Adds item to inventory for further adjustments and Equip it if necessary (itemlocation defined) + * @param item : Item to be added from inventory + */ + @Override + protected void addItem(Item item) + { + super.addItem(item); + if (item.isEquipped()) + { + equipItem(item); + } + + if (item.getTemplate().getSkills() != null) + { + final Player owner = getOwner().asPlayer(); + if (owner != null) + { + owner.sendSkillList(); // Refresh client skill window. + } + } + } + + /** + * Removes item from inventory for further adjustments. + * @param item : Item to be removed from inventory + */ + @Override + protected boolean removeItem(Item item) + { + // Unequip item if equiped. + for (int i = 0; i < _paperdoll.length; i++) + { + if (_paperdoll[i] == item) + { + unEquipItemInSlot(i); + } + } + + if (item.getTemplate().getSkills() != null) + { + final Player owner = getOwner().asPlayer(); + if (owner != null) + { + owner.sendSkillList(); // Refresh client skill window. + } + } + + return super.removeItem(item); + } + + /** + * @param slot the slot. + * @return the item in the paperdoll slot + */ + public Item getPaperdollItem(int slot) + { + return _paperdoll[slot]; + } + + /** + * @param slot the slot. + * @return {@code true} if specified paperdoll slot is empty, {@code false} otherwise + */ + public boolean isPaperdollSlotEmpty(int slot) + { + return _paperdoll[slot] == null; + } + + public static int getPaperdollIndex(BodyPart bodyPart) + { + if (bodyPart == null) + { + return -1; + } + + return bodyPart.getPaperdollSlot(); + } + + /** + * Gets the paperdoll item in the specified body part slot. + * @param bodyPart the body part + * @return the paperdoll item, or {@code null} if none is equipped + */ + public Item getPaperdollItemByBodyPart(BodyPart bodyPart) + { + final int index = getPaperdollIndex(bodyPart); + if (index == -1) + { + return null; + } + + return _paperdoll[index]; + } + + /** + * Returns the ID of the item in the paperdoll slot + * @param slot : int designating the slot + * @return int designating the ID of the item + */ + public int getPaperdollItemId(int slot) + { + final Item item = _paperdoll[slot]; + if (item != null) + { + if (TransmogConfig.ENABLE_TRANSMOG) + { + final int transmogId = item.getTransmogId(); + if (transmogId > 0) + { + return transmogId; + } + } + + return item.getId(); + } + + return 0; + } + + /** + * Returns the first paperdoll item with the specific id + * @param itemId the item id + * @return Item + */ + public Item getPaperdollItemByItemId(int itemId) + { + for (int i = 0; i < _paperdoll.length; i++) + { + final Item item = _paperdoll[i]; + if ((item != null) && (item.getId() == itemId)) + { + return item; + } + } + + return null; + } + + /** + * Returns the ID of the item in the paperdoll slot + * @param slot : int designating the slot + * @return int designating the ID of the item + */ + public int getPaperdollItemDisplayId(int slot) + { + final Item item = _paperdoll[slot]; + if (item != null) + { + if (TransmogConfig.ENABLE_TRANSMOG) + { + final int transmogId = item.getTransmogId(); + if (transmogId > 0) + { + return transmogId; + } + } + + return item.getDisplayId(); + } + + return 0; + } + + public int getPaperdollAugmentationId(int slot) + { + final Item item = _paperdoll[slot]; + return ((item != null) && (item.getAugmentation() != null)) ? item.getAugmentation().getAugmentationId() : 0; + } + + /** + * Returns the objectID associated to the item in the paperdoll slot + * @param slot : int pointing out the slot + * @return int designating the objectID + */ + public int getPaperdollObjectId(int slot) + { + final Item item = _paperdoll[slot]; + return (item != null) ? item.getObjectId() : 0; + } + + /** + * Adds new inventory's paperdoll listener. + * @param listener the new listener + */ + public synchronized void addPaperdollListener(PaperdollListener listener) + { + if (!_paperdollListeners.contains(listener)) + { + _paperdollListeners.add(listener); + } + } + + /** + * Removes a paperdoll listener. + * @param listener the listener to be deleted + */ + public synchronized void removePaperdollListener(PaperdollListener listener) + { + _paperdollListeners.remove(listener); + } + + /** + * Equips an item in the given slot of the paperdoll.
        + * Remark : The item must be in the inventory already. + * @param slot : int pointing out the slot of the paperdoll + * @param item : Item pointing out the item to add in slot + * @return Item designating the item placed in the slot before + */ + public synchronized Item setPaperdollItem(int slot, Item item) + { + final Item old = _paperdoll[slot]; + if (old != item) + { + if (old != null) + { + _paperdoll[slot] = null; + + // Put old item from paperdoll slot to base location. + old.setItemLocation(getBaseLocation()); + old.setLastChange(Item.MODIFIED); + + // Get the mask for paperdoll. + int mask = 0; + for (int i = 0; i < PAPERDOLL_TOTALSLOTS; i++) + { + final Item pi = _paperdoll[i]; + if (pi != null) + { + mask |= pi.getTemplate().getItemMask(); + } + } + + _wearedMask = mask; + + // Notify all paperdoll listener in order to unequip old item in slot. + for (PaperdollListener listener : _paperdollListeners) + { + if (listener == null) + { + continue; + } + + listener.notifyUnequiped(slot, old, this); + } + + old.updateDatabase(); + } + + // Add new item in slot of paperdoll. + if (item != null) + { + _paperdoll[slot] = item; + item.setItemLocation(getEquipLocation(), slot); + item.setLastChange(Item.MODIFIED); + _wearedMask |= item.getTemplate().getItemMask(); + for (PaperdollListener listener : _paperdollListeners) + { + if (listener == null) + { + continue; + } + + listener.notifyEquiped(slot, item, this); + } + + item.updateDatabase(); + } + } + + // Notify to scripts + if (old != null) + { + final Creature owner = getOwner(); + if ((owner != null) && owner.isPlayer() && EventDispatcher.getInstance().hasListener(EventType.ON_PLAYER_ITEM_UNEQUIP, old.getTemplate())) + { + EventDispatcher.getInstance().notifyEventAsync(new OnPlayerItemUnequip(owner.asPlayer(), old), old.getTemplate()); + } + } + + return old; + } + + /** + * @return the mask of wore item + */ + public int getWearedMask() + { + return _wearedMask; + } + + /** + * Unequips item in body slot and returns alterations.
        + * If you do not need return value use {@link Inventory#unEquipItemInBodySlot(BodyPart)} instead + * @param bodyPart : the body part enum + * @return List : List of changes + */ + public Collection unEquipItemInBodySlotAndRecord(BodyPart bodyPart) + { + final ChangeRecorder recorder = newRecorder(); + try + { + unEquipItemInBodySlot(bodyPart); + } + finally + { + removePaperdollListener(recorder); + } + + return recorder.getChangedItems(); + } + + /** + * Sets item in slot of the paperdoll to null value + * @param pdollSlot : int designating the slot + * @return Item designating the item in slot before change + */ + public Item unEquipItemInSlot(int pdollSlot) + { + return setPaperdollItem(pdollSlot, null); + } + + /** + * Unequips item in slot and returns alterations
        + * If you do not need return value use {@link Inventory#unEquipItemInSlot(int)} instead + * @param slot : int designating the slot + * @return Collection : Collection of items altered + */ + public Collection unEquipItemInSlotAndRecord(int slot) + { + final ChangeRecorder recorder = newRecorder(); + try + { + unEquipItemInSlot(slot); + if (getOwner().isPlayer()) + { + getOwner().asPlayer().refreshExpertisePenalty(); + } + } + finally + { + removePaperdollListener(recorder); + } + + return recorder.getChangedItems(); + } + + /** + * Unequips item in body slot + * @param bodyPart the body part enum + * @return {@link Item} designating the item placed in the slot + */ + public Item unEquipItemInBodySlot(BodyPart bodyPart) + { + // Special handling for HAIRALL. + if (bodyPart == BodyPart.HAIRALL) + { + setPaperdollItem(PAPERDOLL_HAIR, null); + } + + final int pdollSlot = BodyPart.getPaperdollIndex(bodyPart); + if (pdollSlot >= 0) + { + final Item old = setPaperdollItem(pdollSlot, null); + if ((old != null) && getOwner().isPlayer()) + { + getOwner().asPlayer().refreshExpertisePenalty(); + } + + return old; + } + + return null; + } + + /** + * Equips item and returns list of alterations
        + * If you don't need return value use {@link Inventory#equipItem(Item)} instead + * @param item : Item corresponding to the item + * @return Collection : Collection of alterations + */ + public Collection equipItemAndRecord(Item item) + { + final ChangeRecorder recorder = newRecorder(); + try + { + equipItem(item); + } + finally + { + removePaperdollListener(recorder); + } + + return recorder.getChangedItems(); + } + + /** + * Equips item in slot of paperdoll. + * @param item : Item designating the item and slot used. + */ + public void equipItem(Item item) + { + final BodyPart bodyPart = item.getTemplate().getBodyPart(); + + if (getOwner().isPlayer()) + { + final Player player = getOwner().asPlayer(); + if (player.isInStoreMode()) + { + return; + } + + if (!player.isGM() && !player.isHero() && item.isHeroItem()) + { + return; + } + + if (player.isWearingFormalWear()) + { + // Only chest target can pass this. + switch (bodyPart) + { + case BodyPart.LR_HAND: + case BodyPart.L_HAND: + case BodyPart.R_HAND: + { + // Unequip formal wear when equipping weapon/shield. + unEquipItemInBodySlotAndRecord(BodyPart.ALLDRESS); + break; + } + case BodyPart.LEGS: + case BodyPart.FEET: + case BodyPart.GLOVES: + case BodyPart.HEAD: + { + return; + } + } + } + } + + // Handle special cases. + switch (bodyPart) + { + case LR_HAND: + { + setPaperdollItem(PAPERDOLL_LHAND, null); + setPaperdollItem(PAPERDOLL_RHAND, item); + break; + } + case L_HAND: + { + final Item rh = getPaperdollItem(PAPERDOLL_RHAND); + if ((rh != null) && (rh.getTemplate().getBodyPart() == BodyPart.LR_HAND) && !(((rh.getItemType() == WeaponType.BOW) && (item.getItemType() == EtcItemType.ARROW)) || ((rh.getItemType() == WeaponType.FISHINGROD) && (item.getItemType() == EtcItemType.LURE)))) + { + setPaperdollItem(PAPERDOLL_RHAND, null); + } + + setPaperdollItem(PAPERDOLL_LHAND, item); + break; + } + case R_HAND: + { + // Don't care about arrows, listener will unequip them (hopefully). + setPaperdollItem(PAPERDOLL_RHAND, item); + break; + } + case L_EAR: + case R_EAR: + case LR_EAR: + { + if (_paperdoll[PAPERDOLL_LEAR] == null) + { + setPaperdollItem(PAPERDOLL_LEAR, item); + } + else if (_paperdoll[PAPERDOLL_REAR] == null) + { + setPaperdollItem(PAPERDOLL_REAR, item); + } + else + { + setPaperdollItem(PAPERDOLL_LEAR, item); + } + break; + } + case L_FINGER: + case R_FINGER: + case LR_FINGER: + { + if (_paperdoll[PAPERDOLL_LFINGER] == null) + { + setPaperdollItem(PAPERDOLL_LFINGER, item); + } + else if (_paperdoll[PAPERDOLL_RFINGER] == null) + { + setPaperdollItem(PAPERDOLL_RFINGER, item); + } + else + { + setPaperdollItem(PAPERDOLL_LFINGER, item); + } + break; + } + case NECK: + { + setPaperdollItem(PAPERDOLL_NECK, item); + break; + } + case FULL_ARMOR: + { + setPaperdollItem(PAPERDOLL_LEGS, null); + setPaperdollItem(PAPERDOLL_CHEST, item); + break; + } + case CHEST: + { + setPaperdollItem(PAPERDOLL_CHEST, item); + break; + } + case LEGS: + { + final Item chest = getPaperdollItem(PAPERDOLL_CHEST); + if ((chest != null) && (chest.getTemplate().getBodyPart() == BodyPart.FULL_ARMOR)) + { + setPaperdollItem(PAPERDOLL_CHEST, null); + } + + setPaperdollItem(PAPERDOLL_LEGS, item); + break; + } + case FEET: + { + setPaperdollItem(PAPERDOLL_FEET, item); + break; + } + case GLOVES: + { + setPaperdollItem(PAPERDOLL_GLOVES, item); + break; + } + case HEAD: + { + setPaperdollItem(PAPERDOLL_HEAD, item); + break; + } + case HAIR: + { + final Item hair = getPaperdollItem(PAPERDOLL_HAIR); + if ((hair != null) && (hair.getTemplate().getBodyPart() == BodyPart.HAIRALL)) + { + setPaperdollItem(PAPERDOLL_HAIR2, null); + } + else + { + setPaperdollItem(PAPERDOLL_HAIR, null); + } + + setPaperdollItem(PAPERDOLL_HAIR, item); + break; + } + case HAIR2: + { + final Item hair2 = getPaperdollItem(PAPERDOLL_HAIR); + if ((hair2 != null) && (hair2.getTemplate().getBodyPart() == BodyPart.HAIRALL)) + { + setPaperdollItem(PAPERDOLL_HAIR, null); + } + else + { + setPaperdollItem(PAPERDOLL_HAIR2, null); + } + + setPaperdollItem(PAPERDOLL_HAIR2, item); + break; + } + case HAIRALL: + { + setPaperdollItem(PAPERDOLL_HAIR2, null); + setPaperdollItem(PAPERDOLL_HAIR, item); + break; + } + case UNDERWEAR: + { + setPaperdollItem(PAPERDOLL_UNDER, item); + break; + } + case BACK: + { + setPaperdollItem(PAPERDOLL_CLOAK, item); + break; + } + case ALLDRESS: + { + setPaperdollItem(PAPERDOLL_LEGS, null); + setPaperdollItem(PAPERDOLL_LHAND, null); + setPaperdollItem(PAPERDOLL_RHAND, null); + setPaperdollItem(PAPERDOLL_HEAD, null); + setPaperdollItem(PAPERDOLL_FEET, null); + setPaperdollItem(PAPERDOLL_GLOVES, null); + setPaperdollItem(PAPERDOLL_CHEST, item); + break; + } + default: + { + LOGGER.warning("Unknown body slot " + bodyPart + " for Item ID: " + item.getId()); + break; + } + } + } + + /** + * Refresh the weight of equipment loaded + */ + @Override + protected void refreshWeight() + { + long weight = 0; + for (Item item : _items) + { + if ((item != null) && (item.getTemplate() != null)) + { + weight += item.getTemplate().getWeight() * item.getCount(); + } + } + + _totalWeight = (int) Math.min(weight, Integer.MAX_VALUE); + } + + /** + * @return the totalWeight. + */ + public int getTotalWeight() + { + return _totalWeight; + } + + /** + * Return the Item of the arrows needed for this bow. + * @param bow : Item designating the bow + * @return Item pointing out arrows for bow + */ + public Item findArrowForBow(ItemTemplate bow) + { + if (bow == null) + { + return null; + } + + Item arrow = null; + for (Item item : _items) + { + if (item.isEtcItem() && (item.getTemplate().getCrystalTypePlus() == bow.getCrystalTypePlus()) && (item.getEtcItem().getItemType() == EtcItemType.ARROW)) + { + arrow = item; + break; + } + } + + // Get the Item corresponding to the item identifier and return it. + return arrow; + } + + /** + * Get back items in inventory from database + */ + @Override + public void restore() + { + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement statement = con.prepareStatement("SELECT object_id, item_id, count, enchant_level, loc, loc_data, custom_type1, custom_type2, mana_left, time FROM items WHERE owner_id=? AND (loc=? OR loc=?) ORDER BY loc_data")) + { + statement.setInt(1, getOwnerId()); + statement.setString(2, getBaseLocation().name()); + statement.setString(3, getEquipLocation().name()); + try (ResultSet inv = statement.executeQuery()) + { + Item item; + while (inv.next()) + { + item = Item.restoreFromDb(getOwnerId(), inv); + if (item == null) + { + continue; + } + + if (getOwner().isPlayer()) + { + final Player player = getOwner().asPlayer(); + if (!player.isGM() && !player.isHero() && item.isHeroItem()) + { + item.setItemLocation(ItemLocation.INVENTORY); + } + } + + World.addObject(item); + + // If stackable item is found in inventory just add to current quantity. + if (item.isStackable() && (getItemByItemId(item.getId()) != null)) + { + addItem(ItemProcessType.RESTORE, item, getOwner().asPlayer(), null); + } + else + { + addItem(item); + } + } + } + + refreshWeight(); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Could not restore inventory: " + e.getMessage(), e); + } + } + + public boolean canEquipCloak() + { + return getOwner().asPlayer().getStat().canEquipCloak(); + } + + /** + * Re-notify to paperdoll listeners every equipped item.
        + * Only used by player ClassId set methods. + */ + public void reloadEquippedItems() + { + int slot; + for (Item item : _paperdoll) + { + if (item == null) + { + continue; + } + + slot = item.getLocationSlot(); + for (PaperdollListener listener : _paperdollListeners) + { + if (listener == null) + { + continue; + } + + listener.notifyUnequiped(slot, item, this); + listener.notifyEquiped(slot, item, this); + } + } + } + + /** + * Gets the items in paperdoll slots filtered by filter. + * @param filters multiple filters + * @return the filtered items in inventory + */ + @SafeVarargs + public final Collection getPaperdollItems(Predicate... filters) + { + Predicate filter = Objects::nonNull; + for (Predicate additionalFilter : filters) + { + filter = filter.and(additionalFilter); + } + + final Collection items = new ArrayDeque<>(); + for (Item item : _paperdoll) + { + if (filter.test(item)) + { + items.add(item); + } + } + + return items; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/itemcontainer/ItemContainer.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/itemcontainer/ItemContainer.java new file mode 100644 index 0000000..cf6d50c --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/itemcontainer/ItemContainer.java @@ -0,0 +1,665 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.itemcontainer; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.util.ArrayDeque; +import java.util.Collection; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.commons.database.DatabaseFactory; +import org.l2jmobius.gameserver.config.GeneralConfig; +import org.l2jmobius.gameserver.data.xml.ItemData; +import org.l2jmobius.gameserver.entity.World; +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.item.ItemTemplate; +import org.l2jmobius.gameserver.entity.item.enums.ItemLocation; +import org.l2jmobius.gameserver.entity.item.enums.ItemProcessType; +import org.l2jmobius.gameserver.entity.item.instance.Item; +import org.l2jmobius.gameserver.managers.ItemManager; +import org.l2jmobius.gameserver.mechanics.variables.ItemVariables; +import org.l2jmobius.gameserver.network.serverpackets.InventoryUpdate; + +/** + * @author Advi + */ +public abstract class ItemContainer +{ + protected static final Logger LOGGER = Logger.getLogger(ItemContainer.class.getName()); + + protected final Set _items = ConcurrentHashMap.newKeySet(1); + + protected ItemContainer() + { + } + + protected abstract Creature getOwner(); + + protected abstract ItemLocation getBaseLocation(); + + public String getName() + { + return "ItemContainer"; + } + + /** + * @return int the owner object Id + */ + public int getOwnerId() + { + return getOwner() == null ? 0 : getOwner().getObjectId(); + } + + /** + * @return the quantity of items in the inventory + */ + public int getSize() + { + return _items.size(); + } + + /** + * Gets the items in inventory. + * @return the items in inventory. + */ + public Collection getItems() + { + return _items; + } + + /** + * @param itemId the item Id + * @return the item from inventory by itemId + */ + public Item getItemByItemId(int itemId) + { + for (Item item : _items) + { + if (item.getId() == itemId) + { + return item; + } + } + + return null; + } + + /** + * @return true if player got item for self resurrection + */ + public boolean haveItemForSelfResurrection() + { + for (Item item : _items) + { + if ((item != null) && item.getTemplate().isAllowSelfResurrection()) + { + return true; + } + } + + return false; + } + + /** + * @param itemId the item Id + * @return the items list from inventory by using its itemId + */ + public Collection getAllItemsByItemId(int itemId) + { + final Collection result = new ArrayDeque<>(); + for (Item item : _items) + { + if (itemId == item.getId()) + { + result.add(item); + } + } + + return result; + } + + /** + * @param itemId the item Id + * @param itemToIgnore used during the loop, to avoid returning the same item + * @return the item from inventory by itemId + */ + public Item getItemByItemId(int itemId, Item itemToIgnore) + { + for (Item item : _items) + { + if ((item.getId() == itemId) && !item.equals(itemToIgnore)) + { + return item; + } + } + + return null; + } + + /** + * @param objectId the item object Id + * @return item from inventory by objectId + */ + public Item getItemByObjectId(int objectId) + { + for (Item item : _items) + { + if (objectId == item.getObjectId()) + { + return item; + } + } + + return null; + } + + /** + * Gets the inventory item count by item Id and enchant level including equipped items. + * @param itemId the item Id + * @param enchantLevel the item enchant level, use -1 to match any enchant level + * @return the inventory item count + */ + public int getInventoryItemCount(int itemId, int enchantLevel) + { + return getInventoryItemCount(itemId, enchantLevel, true); + } + + /** + * Gets the inventory item count by item Id and enchant level, may include equipped items. + * @param itemId the item Id + * @param enchantLevel the item enchant level, use -1 to match any enchant level + * @param includeEquipped if {@code true} includes equipped items in the result + * @return the inventory item count + */ + public int getInventoryItemCount(int itemId, int enchantLevel, boolean includeEquipped) + { + int count = 0; + for (Item item : _items) + { + if ((item.getId() == itemId) && ((item.getEnchantLevel() == enchantLevel) || (enchantLevel < 0)) && (includeEquipped || !item.isEquipped())) + { + if (item.isStackable()) + { + return item.getCount(); + } + + count++; + } + } + + return count; + } + + /** + * Adds item to inventory + * @param process : ItemProcessType identifier of process triggering this action + * @param item : Item to be added + * @param actor : Player Player requesting the item add + * @param reference : Object Object referencing current action like NPC selling item or previous item in transformation + * @return Item corresponding to the new item or the updated item in inventory + */ + public Item addItem(ItemProcessType process, Item item, Player actor, Object reference) + { + Item newItem = item; + final Item olditem = getItemByItemId(newItem.getId()); + + // If stackable item is found in inventory just add to current quantity. + if ((olditem != null) && olditem.isStackable()) + { + final int count = newItem.getCount(); + olditem.changeCount(process, count, actor, reference); + olditem.setLastChange(Item.MODIFIED); + + // And destroys the item + ItemManager.destroyItem(process, newItem, actor, reference); + newItem.updateDatabase(); + newItem = olditem; + } + else // If item hasn't be found in inventory, create new one. + { + newItem.setOwnerId(process, getOwnerId(), actor, reference); + newItem.setItemLocation(getBaseLocation()); + newItem.setLastChange((Item.ADDED)); + + // Add item in inventory. + addItem(newItem); + } + + refreshWeight(); + return newItem; + } + + /** + * Adds item to inventory + * @param process : ItemProcessType identifier of process triggering this action + * @param itemId : int Item Identifier of the item to be added + * @param count : int Quantity of items to be added + * @param actor : Player Player requesting the item add + * @param reference : Object Object referencing current action like NPC selling item or previous item in transformation + * @return Item corresponding to the new item or the updated item in inventory + */ + public Item addItem(ItemProcessType process, int itemId, int count, Player actor, Object reference) + { + Item item = getItemByItemId(itemId); + + // If stackable item is found in inventory just add to current quantity. + if ((item != null) && item.isStackable()) + { + item.changeCount(process, count, actor, reference); + item.setLastChange(Item.MODIFIED); + } + else // If item hasn't be found in inventory, create new one. + { + final InventoryUpdate iu = new InventoryUpdate(); + for (int i = 0; i < count; i++) + { + final ItemTemplate template = ItemData.getInstance().getTemplate(itemId); + if (template == null) + { + LOGGER.warning("Invalid ItemId (" + itemId + ") requested by " + (actor != null ? actor : process)); + return null; + } + + item = ItemManager.createItem(process, itemId, template.isStackable() ? count : 1, actor, reference); + item.setOwnerId(getOwnerId()); + item.setItemLocation(getBaseLocation()); + item.setLastChange(Item.ADDED); + item.setEnchantLevel(template.getDefaultEnchantLevel()); + + // Add item in inventory. + addItem(item); + + // Add additional items to InventoryUpdate. + if ((count > 1) && (i < (count - 1))) + { + iu.addNewItem(item); + } + + // If stackable, end loop as entire count is included in 1 instance of item. + if (template.isStackable() || !GeneralConfig.MULTIPLE_ITEM_DROP) + { + break; + } + } + + // If additional items where created send InventoryUpdate. + if ((count > 1) && (item != null) && !item.isStackable() && (item.getItemLocation() == ItemLocation.INVENTORY)) + { + actor.sendInventoryUpdate(iu); + } + } + + refreshWeight(); + return item; + } + + /** + * Transfers item to another inventory + * @param process ItemProcessType identifier of process triggering this action + * @param objectId Item Identifier of the item to be transfered + * @param countValue Quantity of items to be transfered + * @param target the item container where the item will be moved. + * @param actor Player requesting the item transfer + * @param reference Object Object referencing current action like NPC selling item or previous item in transformation + * @return Item corresponding to the new item or the updated item in inventory + */ + public Item transferItem(ItemProcessType process, int objectId, int countValue, ItemContainer target, Player actor, Object reference) + { + if (target == null) + { + return null; + } + + final Item sourceitem = getItemByObjectId(objectId); + if (sourceitem == null) + { + return null; + } + + Item targetitem = sourceitem.isStackable() ? target.getItemByItemId(sourceitem.getId()) : null; + synchronized (sourceitem) + { + // Check if this item still present in this container. + if (getItemByObjectId(objectId) != sourceitem) + { + return null; + } + + // Check if requested quantity is available. + int count = countValue; + if (count > sourceitem.getCount()) + { + count = sourceitem.getCount(); + } + + // If possible, move entire item object. + if ((sourceitem.getCount() == count) && (targetitem == null) && !sourceitem.isStackable()) + { + removeItem(sourceitem); + target.addItem(process, sourceitem, actor, reference); + targetitem = sourceitem; + } + else + { + if (sourceitem.getCount() > count) // If possible, only update counts. + { + sourceitem.changeCount(process, -count, actor, reference); + } + else // Otherwise destroy old item. + { + removeItem(sourceitem); + ItemManager.destroyItem(process, sourceitem, actor, reference); + } + + if (targetitem != null) // If possible, only update counts. + { + targetitem.changeCount(process, count, actor, reference); + } + else // Otherwise add new item. + { + targetitem = target.addItem(process, sourceitem.getId(), count, actor, reference); + } + } + + // Updates database + sourceitem.updateDatabase(true); + if ((targetitem != sourceitem) && (targetitem != null)) + { + targetitem.updateDatabase(); + } + + if (sourceitem.isAugmented()) + { + sourceitem.getAugmentation().removeBonus(actor); + } + + refreshWeight(); + target.refreshWeight(); + } + + return targetitem; + } + + /** + * Destroy item from inventory and updates database + * @param process : ItemProcessType identifier of process triggering this action + * @param item : Item to be destroyed + * @param actor : Player requesting the item destroy + * @param reference : Object Object referencing current action like NPC selling item or previous item in transformation + * @return Item corresponding to the destroyed item or the updated item in inventory + */ + public Item destroyItem(ItemProcessType process, Item item, Player actor, Object reference) + { + return destroyItem(process, item, item.getCount(), actor, reference); + } + + /** + * Destroy item from inventory and updates database + * @param process : ItemProcessType identifier of process triggering this action + * @param item : Item to be destroyed + * @param count + * @param actor : Player requesting the item destroy + * @param reference : Object Object referencing current action like NPC selling item or previous item in transformation + * @return Item corresponding to the destroyed item or the updated item in inventory + */ + public Item destroyItem(ItemProcessType process, Item item, int count, Player actor, Object reference) + { + synchronized (item) + { + // Adjust item quantity + if (item.getCount() > count) + { + item.changeCount(process, -count, actor, reference); + item.setLastChange(Item.MODIFIED); + refreshWeight(); + } + else + { + if (item.getCount() < count) + { + return null; + } + + final boolean removed = removeItem(item); + if (!removed) + { + return null; + } + + ItemManager.destroyItem(process, item, actor, reference); + item.updateDatabase(); + refreshWeight(); + + item.stopAllTasks(); + } + } + + return item; + } + + /** + * Destroy item from inventory by using its objectID and updates database + * @param process : ItemProcessType identifier of process triggering this action + * @param objectId : int Item Instance identifier of the item to be destroyed + * @param count : int Quantity of items to be destroyed + * @param actor : Player requesting the item destroy + * @param reference : Object Object referencing current action like NPC selling item or previous item in transformation + * @return Item corresponding to the destroyed item or the updated item in inventory + */ + public Item destroyItem(ItemProcessType process, int objectId, int count, Player actor, Object reference) + { + final Item item = getItemByObjectId(objectId); + return item == null ? null : destroyItem(process, item, count, actor, reference); + } + + /** + * Destroy item from inventory by using its itemId and updates database + * @param process : ItemProcessType identifier of process triggering this action + * @param itemId : int Item identifier of the item to be destroyed + * @param count : int Quantity of items to be destroyed + * @param actor : Player requesting the item destroy + * @param reference : Object Object referencing current action like NPC selling item or previous item in transformation + * @return Item corresponding to the destroyed item or the updated item in inventory + */ + public Item destroyItemByItemId(ItemProcessType process, int itemId, int count, Player actor, Object reference) + { + final Item item = getItemByItemId(itemId); + return item == null ? null : destroyItem(process, item, count, actor, reference); + } + + /** + * Destroy all items from inventory and updates database + * @param process : ItemProcessType identifier of process triggering this action + * @param actor : Player requesting the item destroy + * @param reference : Object Object referencing current action like NPC selling item or previous item in transformation + */ + public void destroyAllItems(ItemProcessType process, Player actor, Object reference) + { + for (Item item : _items) + { + destroyItem(process, item, actor, reference); + } + } + + /** + * @return warehouse Adena. + */ + public int getAdena() + { + for (Item item : _items) + { + if (item.getId() == Inventory.ADENA_ID) + { + return item.getCount(); + } + } + + return 0; + } + + /** + * Adds item to inventory for further adjustments. + * @param item : Item to be added from inventory + */ + protected void addItem(Item item) + { + _items.add(item); + } + + /** + * Removes item from inventory for further adjustments. + * @param item : Item to be removed from inventory + * @return + */ + protected boolean removeItem(Item item) + { + return _items.remove(item); + } + + /** + * Refresh the weight of equipment loaded + */ + protected void refreshWeight() + { + } + + /** + * Delete item object from world + */ + public void deleteMe() + { + if ((this instanceof PlayerInventory) || (this instanceof PlayerWarehouse) || (getOwner() != null)) + { + for (Item item : _items) + { + item.updateDatabase(true); + item.stopAllTasks(); + + final ItemVariables vars = item.getScript(ItemVariables.class); + if (vars != null) + { + vars.saveNow(); + } + } + } + + for (Item item : _items) + { + World.removeObject(item); + } + + _items.clear(); + } + + /** + * Update database with items in inventory + */ + public void updateDatabase() + { + if (getOwner() != null) + { + for (Item item : _items) + { + item.updateDatabase(true); + } + } + } + + /** + * Get back items in container from database + */ + public void restore() + { + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement("SELECT object_id, item_id, count, enchant_level, loc, loc_data, custom_type1, custom_type2, mana_left, time FROM items WHERE owner_id=? AND (loc=?)")) + { + ps.setInt(1, getOwnerId()); + ps.setString(2, getBaseLocation().name()); + try (ResultSet rs = ps.executeQuery()) + { + Item item; + while (rs.next()) + { + item = Item.restoreFromDb(getOwnerId(), rs); + if (item == null) + { + continue; + } + + World.addObject(item); + + final Player owner = getOwner() != null ? getOwner().asPlayer() : null; + + // If stackable item is found in inventory just add to current quantity. + if (item.isStackable() && (getItemByItemId(item.getId()) != null)) + { + addItem(ItemProcessType.RESTORE, item, owner, null); + } + else + { + addItem(item); + } + } + } + + refreshWeight(); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Could not restore container:", e); + } + } + + public boolean validateCapacity(long slots) + { + return true; + } + + public boolean validateWeight(long weight) + { + return true; + } + + /** + * If the item is stackable validates 1 slot, if the item isn't stackable validates the item count. + * @param itemId the item Id to verify + * @param count amount of item's weight to validate + * @return {@code true} if the item doesn't exist or it validates its slot count + */ + public boolean validateCapacityByItemId(int itemId, long count) + { + final ItemTemplate template = ItemData.getInstance().getTemplate(itemId); + return (template == null) || (template.isStackable() ? validateCapacity(1) : validateCapacity(count)); + } + + /** + * @param itemId the item Id to verify + * @param count amount of item's weight to validate + * @return {@code true} if the item doesn't exist or it validates its weight + */ + public boolean validateWeightByItemId(int itemId, long count) + { + final ItemTemplate template = ItemData.getInstance().getTemplate(itemId); + return (template == null) || validateWeight(template.getWeight() * count); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/itemcontainer/Mail.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/itemcontainer/Mail.java new file mode 100644 index 0000000..0ea6416 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/itemcontainer/Mail.java @@ -0,0 +1,175 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.itemcontainer; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.util.logging.Level; + +import org.l2jmobius.commons.database.DatabaseFactory; +import org.l2jmobius.gameserver.entity.World; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.item.enums.ItemLocation; +import org.l2jmobius.gameserver.entity.item.enums.ItemProcessType; +import org.l2jmobius.gameserver.entity.item.instance.Item; + +/** + * @author DS + */ +public class Mail extends ItemContainer +{ + private final int _ownerId; + private int _messageId; + + public Mail(int objectId, int messageId) + { + _ownerId = objectId; + _messageId = messageId; + } + + @Override + public String getName() + { + return "Mail"; + } + + @Override + public Player getOwner() + { + return null; + } + + @Override + public ItemLocation getBaseLocation() + { + return ItemLocation.MAIL; + } + + public int getMessageId() + { + return _messageId; + } + + public void setNewMessageId(int messageId) + { + _messageId = messageId; + for (Item item : _items) + { + if (item == null) + { + continue; + } + + item.setItemLocation(getBaseLocation(), messageId); + } + + updateDatabase(); + } + + public void returnToWh(ItemContainer wh) + { + for (Item item : _items) + { + if (item == null) + { + continue; + } + + if (wh != null) + { + transferItem(ItemProcessType.TRANSFER, item.getObjectId(), item.getCount(), wh, null, null); + } + else + { + item.setItemLocation(ItemLocation.WAREHOUSE); + } + } + } + + @Override + protected void addItem(Item item) + { + super.addItem(item); + item.setItemLocation(getBaseLocation(), _messageId); + item.updateDatabase(true); + } + + @Override + public void updateDatabase() + { + _items.forEach(i -> i.updateDatabase(true)); + } + + @Override + public void restore() + { + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement("SELECT object_id, item_id, count, enchant_level, loc, loc_data, custom_type1, custom_type2, mana_left, time FROM items WHERE owner_id=? AND loc=? AND loc_data=?")) + { + ps.setInt(1, _ownerId); + ps.setString(2, getBaseLocation().name()); + ps.setInt(3, _messageId); + try (ResultSet inv = ps.executeQuery()) + { + Item item; + while (inv.next()) + { + item = Item.restoreFromDb(_ownerId, inv); + if (item == null) + { + continue; + } + + World.addObject(item); + + // If stackable item is found just add to current quantity. + if (item.isStackable() && (getItemByItemId(item.getId()) != null)) + { + addItem(ItemProcessType.RESTORE, item, null, null); + } + else + { + addItem(item); + } + } + } + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "could not restore container:", e); + } + } + + @Override + public int getOwnerId() + { + return _ownerId; + } + + @Override + public void deleteMe() + { + _items.forEach(item -> + { + item.updateDatabase(true); + item.stopAllTasks(); + World.removeObject(item); + }); + _items.clear(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/itemcontainer/PetInventory.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/itemcontainer/PetInventory.java new file mode 100644 index 0000000..3b1cdca --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/itemcontainer/PetInventory.java @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.itemcontainer; + +import org.l2jmobius.gameserver.data.xml.ItemData; +import org.l2jmobius.gameserver.entity.actor.instance.Pet; +import org.l2jmobius.gameserver.entity.item.ItemTemplate; +import org.l2jmobius.gameserver.entity.item.enums.ItemLocation; +import org.l2jmobius.gameserver.entity.item.enums.ItemProcessType; +import org.l2jmobius.gameserver.entity.item.instance.Item; + +public class PetInventory extends Inventory +{ + private final Pet _owner; + + public PetInventory(Pet owner) + { + _owner = owner; + } + + @Override + public Pet getOwner() + { + return _owner; + } + + @Override + public int getOwnerId() + { + // gets the Player-owner's ID + int id; + try + { + id = _owner.getOwner().getObjectId(); + } + catch (NullPointerException e) + { + return 0; + } + + return id; + } + + /** + * Refresh the weight of equipment loaded + */ + @Override + protected void refreshWeight() + { + super.refreshWeight(); + _owner.updateAndBroadcastStatus(1); + } + + public boolean validateCapacity(Item item) + { + int slots = 0; + if ((!item.isStackable() || (getItemByItemId(item.getId()) == null)) && !item.getTemplate().hasExImmediateEffect()) + { + slots++; + } + + return validateCapacity(slots); + } + + @Override + public boolean validateCapacity(long slots) + { + return (_items.size() + slots) <= _owner.getInventoryLimit(); + } + + public boolean validateWeight(Item item, long count) + { + int weight = 0; + final ItemTemplate template = ItemData.getInstance().getTemplate(item.getId()); + if (template == null) + { + return false; + } + + weight += count * template.getWeight(); + return validateWeight(weight); + } + + @Override + public boolean validateWeight(long weight) + { + return (_totalWeight + weight) <= _owner.getMaxLoad(); + } + + @Override + protected ItemLocation getBaseLocation() + { + return ItemLocation.PET; + } + + @Override + protected ItemLocation getEquipLocation() + { + return ItemLocation.PET_EQUIP; + } + + @Override + public void restore() + { + super.restore(); + + // check for equipped items from other pets + for (Item item : _items) + { + if (item.isEquipped() && !item.getTemplate().checkCondition(_owner, _owner, false)) + { + unEquipItemInSlot(item.getLocationSlot()); + } + } + } + + public void transferItemsToOwner() + { + for (Item item : _items) + { + getOwner().transferItem(ItemProcessType.TRANSFER, item.getObjectId(), item.getCount(), getOwner().getOwner().getInventory(), getOwner().getOwner(), getOwner()); + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/itemcontainer/PlayerFreight.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/itemcontainer/PlayerFreight.java new file mode 100644 index 0000000..7adad35 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/itemcontainer/PlayerFreight.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.itemcontainer; + +import org.l2jmobius.gameserver.config.PlayerConfig; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.item.enums.ItemLocation; +import org.l2jmobius.gameserver.mechanics.stats.Stat; + +public class PlayerFreight extends ItemContainer +{ + private final Player _owner; + private final int _ownerId; + + public PlayerFreight(int objectId) + { + _owner = null; + _ownerId = objectId; + restore(); + } + + public PlayerFreight(Player owner) + { + _owner = owner; + _ownerId = owner.getObjectId(); + } + + @Override + public int getOwnerId() + { + return _ownerId; + } + + @Override + public Player getOwner() + { + return _owner; + } + + @Override + public ItemLocation getBaseLocation() + { + return ItemLocation.FREIGHT; + } + + @Override + public String getName() + { + return "Freight"; + } + + @Override + public boolean validateCapacity(long slots) + { + return (getSize() + slots) <= (_owner == null ? PlayerConfig.ALT_FREIGHT_SLOTS : PlayerConfig.ALT_FREIGHT_SLOTS + (int) _owner.getStat().calcStat(Stat.FREIGHT_LIM, 0, null, null)); + } + + @Override + public void refreshWeight() + { + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/itemcontainer/PlayerInventory.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/itemcontainer/PlayerInventory.java new file mode 100644 index 0000000..de98955 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/itemcontainer/PlayerInventory.java @@ -0,0 +1,1017 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.itemcontainer; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.util.ArrayDeque; +import java.util.Collection; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.commons.database.DatabaseFactory; +import org.l2jmobius.gameserver.config.PlayerConfig; +import org.l2jmobius.gameserver.config.custom.TransmogConfig; +import org.l2jmobius.gameserver.data.xml.ItemData; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.item.ItemTemplate; +import org.l2jmobius.gameserver.entity.item.enums.ItemLocation; +import org.l2jmobius.gameserver.entity.item.enums.ItemProcessType; +import org.l2jmobius.gameserver.entity.item.instance.Item; +import org.l2jmobius.gameserver.mechanics.events.EventDispatcher; +import org.l2jmobius.gameserver.mechanics.events.EventType; +import org.l2jmobius.gameserver.mechanics.events.holders.actor.player.inventory.OnPlayerItemAdd; +import org.l2jmobius.gameserver.mechanics.events.holders.actor.player.inventory.OnPlayerItemDestroy; +import org.l2jmobius.gameserver.mechanics.events.holders.actor.player.inventory.OnPlayerItemDrop; +import org.l2jmobius.gameserver.mechanics.events.holders.actor.player.inventory.OnPlayerItemTransfer; +import org.l2jmobius.gameserver.mechanics.variables.ItemVariables; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.holders.TradeItem; +import org.l2jmobius.gameserver.network.holders.TradeList; +import org.l2jmobius.gameserver.network.serverpackets.InventoryUpdate; +import org.l2jmobius.gameserver.network.serverpackets.StatusUpdate; +import org.l2jmobius.gameserver.util.ArrayUtil; + +public class PlayerInventory extends Inventory +{ + private static final Logger LOGGER = Logger.getLogger(PlayerInventory.class.getName()); + + private final Player _owner; + private Item _adena; + private Item _ancientAdena; + + private int[] _blockItems = null; + + /** + * Block modes: + *
          + *
        • -1 - no block + *
        • 0 - block items from _invItems, allow usage of other items + *
        • 1 - allow usage of items from _invItems, block other items + *
        + */ + private int _blockMode = -1; + + public PlayerInventory(Player owner) + { + _owner = owner; + } + + @Override + public Player getOwner() + { + return _owner; + } + + @Override + protected ItemLocation getBaseLocation() + { + return ItemLocation.INVENTORY; + } + + @Override + protected ItemLocation getEquipLocation() + { + return ItemLocation.PAPERDOLL; + } + + public Item getAdenaInstance() + { + return _adena; + } + + @Override + public int getAdena() + { + return _adena != null ? _adena.getCount() : 0; + } + + public Item getAncientAdenaInstance() + { + return _ancientAdena; + } + + public int getAncientAdena() + { + return (_ancientAdena != null) ? _ancientAdena.getCount() : 0; + } + + /** + * Retrieves a collection of unique buyable items based on the specified conditions. + * @param allowAdena {@code true} to include Adena in the results, {@code false} to exclude it. + * @param allowAncientAdena {@code true} to include Ancient Adena in the results, {@code false} to exclude it. + * @param onlyAvailable {@code true} to include only items that are currently available to the owner, {@code false} to include all items regardless of availability. + * @param checkSellable {@code true} to check if items can be sold. + * @return a collection of {@link Item} objects that match the specified criteria. + */ + public Collection getUniqueItems(boolean allowAdena, boolean allowAncientAdena, boolean onlyAvailable, boolean checkSellable) + { + final Collection result = new ArrayDeque<>(); + for (Item item : _items) + { + if (!allowAdena && (item.getId() == ADENA_ID)) + { + continue; + } + + if (!allowAncientAdena && (item.getId() == ANCIENT_ADENA_ID)) + { + continue; + } + + boolean isDuplicate = false; + for (Item addedItem : result) + { + if (addedItem.getId() == item.getId()) + { + isDuplicate = true; + break; + } + } + + if (!isDuplicate && (!onlyAvailable || ((!checkSellable || item.isSellable()) && item.isAvailable(_owner, false, false)))) + { + result.add(item); + } + } + + return result; + } + + /** + * Retrieves a collection of unique items based on their enchant level and the specified conditions. + * @param allowAdena {@code true} to include Adena in the results, {@code false} to exclude it. + * @param allowAncientAdena {@code true} to include Ancient Adena in the results, {@code false} to exclude it. + * @param onlyAvailable {@code true} to include only items that are currently available to the owner, {@code false} to include all items regardless of availability. + * @return a collection of {@link Item} objects that match the specified criteria and have unique IDs and enchant levels. + */ + public Collection getUniqueItemsByEnchantLevel(boolean allowAdena, boolean allowAncientAdena, boolean onlyAvailable) + { + final Collection result = new ArrayDeque<>(); + for (Item item : _items) + { + if ((item == null) || (!allowAdena && (item.getId() == ADENA_ID)) || (!allowAncientAdena && (item.getId() == ANCIENT_ADENA_ID))) + { + continue; + } + + boolean isDuplicate = false; + for (Item addedItem : result) + { + if ((addedItem.getId() == item.getId()) && (addedItem.getEnchantLevel() == item.getEnchantLevel())) + { + isDuplicate = true; + break; + } + } + + if (!isDuplicate && (!onlyAvailable || (item.isSellable() && item.isAvailable(_owner, false, false)))) + { + result.add(item); + } + } + + return result; + } + + /** + * Returns the list of all items in inventory that have a given item id. + * @param itemId : ID of item + * @param includeEquipped : include equipped items + * @return Collection : matching items from inventory + */ + public Collection getAllItemsByItemId(int itemId, boolean includeEquipped) + { + final Collection result = new ArrayDeque<>(); + for (Item item : _items) + { + if ((itemId == item.getId()) && (includeEquipped || !item.isEquipped())) + { + result.add(item); + } + } + + return result; + } + + /** + * @param itemId + * @param enchantment + * @return + */ + public Collection getAllItemsByItemId(int itemId, int enchantment) + { + return getAllItemsByItemId(itemId, enchantment, true); + } + + /** + * Returns the list of all items in inventory that have a given item id AND a given enchantment level. + * @param itemId : ID of item + * @param enchantment : enchant level of item + * @param includeEquipped : include equipped items + * @return Collection : matching items from inventory + */ + public Collection getAllItemsByItemId(int itemId, int enchantment, boolean includeEquipped) + { + final Collection result = new ArrayDeque<>(); + for (Item item : _items) + { + if ((itemId == item.getId()) && (item.getEnchantLevel() == enchantment) && (includeEquipped || !item.isEquipped())) + { + result.add(item); + } + } + + return result; + } + + /** + * @param allowAdena + * @param allowNonTradeable + * @param feightable + * @return the list of items in inventory available for transaction + */ + public Collection getAvailableItems(boolean allowAdena, boolean allowNonTradeable, boolean feightable) + { + final Collection result = new ArrayDeque<>(); + for (Item item : _items) + { + if (!item.isAvailable(_owner, allowAdena, allowNonTradeable) || !canManipulateWithItemId(item.getId())) + { + continue; + } + else if (feightable) + { + if ((item.getItemLocation() == ItemLocation.INVENTORY) && item.isFreightable()) + { + result.add(item); + } + continue; + } + + result.add(item); + } + + return result; + } + + /** + * Get all augmented items + * @return + */ + public Collection getAugmentedItems() + { + final Collection result = new ArrayDeque<>(); + for (Item item : _items) + { + if (item.isAugmented()) + { + result.add(item); + } + } + + return result; + } + + /** + * Returns the list of items in inventory available for transaction adjusted by tradeList + * @param tradeList + * @return Item : items in inventory + */ + public Collection getAvailableItems(TradeList tradeList) + { + final Collection result = new ArrayDeque<>(); + for (Item item : _items) + { + if ((item != null) && item.isAvailable(_owner, false, false)) + { + final TradeItem adjItem = tradeList.adjustAvailableItem(item); + if (adjItem != null) + { + result.add(adjItem); + } + } + } + + return result; + } + + /** + * Adjust TradeItem according his status in inventory + * @param item : Item to be adjusted + */ + public void adjustAvailableItem(TradeItem item) + { + boolean notAllEquipped = false; + for (Item adjItem : getAllItemsByItemId(item.getItem().getId())) + { + if (adjItem.isEquipable()) + { + if (!adjItem.isEquipped()) + { + notAllEquipped |= true; + } + } + else + { + notAllEquipped |= true; + break; + } + } + + if (notAllEquipped) + { + final Item adjItem = getItemByItemId(item.getItem().getId()); + item.setObjectId(adjItem.getObjectId()); + item.setEnchant(adjItem.getEnchantLevel()); + + if (adjItem.getCount() < item.getCount()) + { + item.setCount(adjItem.getCount()); + } + + return; + } + + item.setCount(0); + } + + /** + * Adds adena to PcInventory + * @param process : ItemProcessType identifier of process triggering this action + * @param count : int Quantity of adena to be added + * @param actor : Player Player requesting the item add + * @param reference : Object Object referencing current action like NPC selling item or previous item in transformation + */ + public void addAdena(ItemProcessType process, int count, Player actor, Object reference) + { + if (count > 0) + { + addItem(process, ADENA_ID, count, actor, reference); + } + } + + /** + * Removes adena to PcInventory + * @param process : ItemProcessType identifier of process triggering this action + * @param count : int Quantity of adena to be removed + * @param actor : Player Player requesting the item add + * @param reference : Object Object referencing current action like NPC selling item or previous item in transformation + * @return boolean : true if adena was reduced + */ + public boolean reduceAdena(ItemProcessType process, int count, Player actor, Object reference) + { + if (count > 0) + { + return destroyItemByItemId(process, ADENA_ID, count, actor, reference) != null; + } + + return false; + } + + /** + * Adds specified amount of ancient adena to player inventory. + * @param process : ItemProcessType identifier of process triggering this action + * @param count : int Quantity of adena to be added + * @param actor : Player Player requesting the item add + * @param reference : Object Object referencing current action like NPC selling item or previous item in transformation + */ + public void addAncientAdena(ItemProcessType process, int count, Player actor, Object reference) + { + if (count > 0) + { + addItem(process, ANCIENT_ADENA_ID, count, actor, reference); + } + } + + /** + * Removes specified amount of ancient adena from player inventory. + * @param process : ItemProcessType identifier of process triggering this action + * @param count : int Quantity of adena to be removed + * @param actor : Player Player requesting the item add + * @param reference : Object Object referencing current action like NPC selling item or previous item in transformation + * @return boolean : true if adena was reduced + */ + public boolean reduceAncientAdena(ItemProcessType process, int count, Player actor, Object reference) + { + return (count > 0) && (destroyItemByItemId(process, ANCIENT_ADENA_ID, count, actor, reference) != null); + } + + /** + * Adds item in inventory and checks _adena and _ancientAdena + * @param process : ItemProcessType identifier of process triggering this action + * @param item : Item to be added + * @param actor : Player Player requesting the item add + * @param reference : Object Object referencing current action like NPC selling item or previous item in transformation + * @return Item corresponding to the new item or the updated item in inventory + */ + @Override + public Item addItem(ItemProcessType process, Item item, Player actor, Object reference) + { + final Item addedItem = super.addItem(process, item, actor, reference); + if (addedItem != null) + { + if ((addedItem.getId() == ADENA_ID) && !addedItem.equals(_adena)) + { + _adena = addedItem; + } + else if ((addedItem.getId() == ANCIENT_ADENA_ID) && !addedItem.equals(_ancientAdena)) + { + _ancientAdena = addedItem; + } + + // Notify to scripts + if ((actor != null) && EventDispatcher.getInstance().hasListener(EventType.ON_PLAYER_ITEM_ADD, actor)) + { + EventDispatcher.getInstance().notifyEventAsync(new OnPlayerItemAdd(actor, addedItem), actor); + } + } + + return addedItem; + } + + /** + * Adds item in inventory and checks _adena and _ancientAdena + * @param process : ItemProcessType identifier of process triggering this action + * @param itemId : int Item Identifier of the item to be added + * @param count : int Quantity of items to be added + * @param actor : Player Player requesting the item creation + * @param reference : Object Object referencing current action like NPC selling item or previous item in transformation + * @return Item corresponding to the new item or the updated item in inventory + */ + @Override + public Item addItem(ItemProcessType process, int itemId, int count, Player actor, Object reference) + { + final Item item = super.addItem(process, itemId, count, actor, reference); + if (item != null) + { + if ((item.getId() == ADENA_ID) && !item.equals(_adena)) + { + _adena = item; + } + else if ((item.getId() == ANCIENT_ADENA_ID) && !item.equals(_ancientAdena)) + { + _ancientAdena = item; + } + + if (actor != null) + { + // Send inventory update packet. + final InventoryUpdate playerIU = new InventoryUpdate(); + if (item.isStackable() && (item.getCount() > count)) + { + playerIU.addModifiedItem(item); + } + else + { + playerIU.addNewItem(item); + } + + actor.sendInventoryUpdate(playerIU); + + // Update current load as well. + final StatusUpdate su = new StatusUpdate(actor); + su.addAttribute(StatusUpdate.CUR_LOAD, actor.getCurrentLoad()); + actor.sendPacket(su); + + // Notify to scripts + if (EventDispatcher.getInstance().hasListener(EventType.ON_PLAYER_ITEM_ADD, actor)) + { + EventDispatcher.getInstance().notifyEventAsync(new OnPlayerItemAdd(actor, item), actor); + } + } + } + + return item; + } + + /** + * Transfers item to another inventory and checks _adena and _ancientAdena + * @param process ItemProcessType identifier of process triggering this action + * @param objectId Item Identifier of the item to be transfered + * @param count Quantity of items to be transfered + * @param target the item container for the item to be transfered. + * @param actor the player requesting the item transfer + * @param reference : Object Object referencing current action like NPC selling item or previous item in transformation + * @return Item corresponding to the new item or the updated item in inventory + */ + @Override + public Item transferItem(ItemProcessType process, int objectId, int count, ItemContainer target, Player actor, Object reference) + { + final Item item = super.transferItem(process, objectId, count, target, actor, reference); + + if ((_adena != null) && ((_adena.getCount() <= 0) || (_adena.getOwnerId() != getOwnerId()))) + { + _adena = null; + } + + if ((_ancientAdena != null) && ((_ancientAdena.getCount() <= 0) || (_ancientAdena.getOwnerId() != getOwnerId()))) + { + _ancientAdena = null; + } + + // Notify to scripts + if (EventDispatcher.getInstance().hasListener(EventType.ON_PLAYER_ITEM_TRANSFER, item.getTemplate())) + { + EventDispatcher.getInstance().notifyEventAsync(new OnPlayerItemTransfer(actor, item, target), item.getTemplate()); + } + + return item; + } + + /** + * Destroy item from inventory and checks _adena and _ancientAdena + * @param process : ItemProcessType identifier of process triggering this action + * @param item : Item to be destroyed + * @param actor : Player requesting the item destroy + * @param reference : Object Object referencing current action like NPC selling item or previous item in transformation + * @return Item corresponding to the destroyed item or the updated item in inventory + */ + @Override + public Item destroyItem(ItemProcessType process, Item item, Player actor, Object reference) + { + return destroyItem(process, item, item.getCount(), actor, reference); + } + + /** + * Destroy item from inventory and checks _adena and _ancientAdena + * @param process : ItemProcessType identifier of process triggering this action + * @param item : Item to be destroyed + * @param actor : Player requesting the item destroy + * @param reference : Object Object referencing current action like NPC selling item or previous item in transformation + * @return Item corresponding to the destroyed item or the updated item in inventory + */ + @Override + public Item destroyItem(ItemProcessType process, Item item, int count, Player actor, Object reference) + { + final Item destroyedItem = super.destroyItem(process, item, count, actor, reference); + + if ((_adena != null) && (_adena.getCount() <= 0)) + { + _adena = null; + } + + if ((_ancientAdena != null) && (_ancientAdena.getCount() <= 0)) + { + _ancientAdena = null; + } + + // Notify to scripts + if ((destroyedItem != null) && EventDispatcher.getInstance().hasListener(EventType.ON_PLAYER_ITEM_DESTROY, destroyedItem.getTemplate())) + { + EventDispatcher.getInstance().notifyEventAsync(new OnPlayerItemDestroy(actor, destroyedItem), destroyedItem.getTemplate()); + } + + return destroyedItem; + } + + /** + * Destroys item from inventory and checks _adena and _ancientAdena + * @param process : ItemProcessType identifier of process triggering this action + * @param objectId : int Item Instance identifier of the item to be destroyed + * @param count : int Quantity of items to be destroyed + * @param actor : Player requesting the item destroy + * @param reference : Object Object referencing current action like NPC selling item or previous item in transformation + * @return Item corresponding to the destroyed item or the updated item in inventory + */ + @Override + public Item destroyItem(ItemProcessType process, int objectId, int count, Player actor, Object reference) + { + final Item item = getItemByObjectId(objectId); + return item == null ? null : destroyItem(process, item, count, actor, reference); + } + + /** + * Destroy item from inventory by using its itemId and checks _adena and _ancientAdena + * @param process : ItemProcessType identifier of process triggering this action + * @param itemId : int Item identifier of the item to be destroyed + * @param count : int Quantity of items to be destroyed + * @param actor : Player requesting the item destroy + * @param reference : Object Object referencing current action like NPC selling item or previous item in transformation + * @return Item corresponding to the destroyed item or the updated item in inventory + */ + @Override + public Item destroyItemByItemId(ItemProcessType process, int itemId, int count, Player actor, Object reference) + { + // Attempt to find non equipped items. + Item destroyItem = null; + final Collection items = getAllItemsByItemId(itemId); + for (Item item : items) + { + destroyItem = item; + if (!destroyItem.isEquipped()) + { + break; + } + } + + // No item found. + if (destroyItem == null) + { + return null; + } + + // Support destroying multiple non stackable items. + if (!destroyItem.isStackable() && (count > 1)) + { + if (getInventoryItemCount(itemId, -1, false) >= count) + { + final InventoryUpdate iu = new InventoryUpdate(); + long destroyed = 0; + for (Item item : items) + { + if (!item.isEquipped() && (destroyItem(process, item, 1, actor, reference) != null)) + { + iu.addRemovedItem(item); + if (++destroyed == count) + { + _owner.sendInventoryUpdate(iu); + refreshWeight(); + return item; + } + } + } + } + else + { + return null; + } + } + + // Single item or stackable. + return destroyItem(process, destroyItem, count, actor, reference); + } + + /** + * Drop item from inventory and checks _adena and _ancientAdena + * @param process : ItemProcessType identifier of process triggering this action + * @param item : Item to be dropped + * @param actor : Player Player requesting the item drop + * @param reference : Object Object referencing current action like NPC selling item or previous item in transformation + * @return Item corresponding to the destroyed item or the updated item in inventory + */ + @Override + public Item dropItem(ItemProcessType process, Item item, Player actor, Object reference) + { + final Item droppedItem = super.dropItem(process, item, actor, reference); + + if ((_adena != null) && ((_adena.getCount() <= 0) || (_adena.getOwnerId() != getOwnerId()))) + { + _adena = null; + } + + if ((_ancientAdena != null) && ((_ancientAdena.getCount() <= 0) || (_ancientAdena.getOwnerId() != getOwnerId()))) + { + _ancientAdena = null; + } + + // Notify to scripts + if ((droppedItem != null) && EventDispatcher.getInstance().hasListener(EventType.ON_PLAYER_ITEM_DROP, droppedItem.getTemplate())) + { + EventDispatcher.getInstance().notifyEventAsync(new OnPlayerItemDrop(actor, droppedItem, droppedItem.getLocation()), droppedItem.getTemplate()); + } + + return droppedItem; + } + + /** + * Drop item from inventory by using its objectID and checks _adena and _ancientAdena + * @param process : ItemProcessType identifier of process triggering this action + * @param objectId : int Item Instance identifier of the item to be dropped + * @param count : int Quantity of items to be dropped + * @param actor : Player Player requesting the item drop + * @param reference : Object Object referencing current action like NPC selling item or previous item in transformation + * @return Item corresponding to the destroyed item or the updated item in inventory + */ + @Override + public Item dropItem(ItemProcessType process, int objectId, int count, Player actor, Object reference) + { + final Item item = super.dropItem(process, objectId, count, actor, reference); + + if ((_adena != null) && ((_adena.getCount() <= 0) || (_adena.getOwnerId() != getOwnerId()))) + { + _adena = null; + } + + if ((_ancientAdena != null) && ((_ancientAdena.getCount() <= 0) || (_ancientAdena.getOwnerId() != getOwnerId()))) + { + _ancientAdena = null; + } + + // Notify to scripts + if ((item != null) && EventDispatcher.getInstance().hasListener(EventType.ON_PLAYER_ITEM_DROP, item.getTemplate())) + { + EventDispatcher.getInstance().notifyEventAsync(new OnPlayerItemDrop(actor, item, item.getLocation()), item.getTemplate()); + } + + return item; + } + + /** + * Adds item to inventory for further adjustments. + * @param item : Item to be added from inventory + */ + @Override + protected void addItem(Item item) + { + super.addItem(item); + } + + /** + * Overloaded, when removes item from inventory, remove also owner shortcuts. + * @param item : Item to be removed from inventory + */ + @Override + protected boolean removeItem(Item item) + { + // Removes any reference to the item from Shortcut bar. + _owner.removeItemFromShortcut(item.getObjectId()); + + // Removes active Enchant Scroll. + if (item.getObjectId() == _owner.getActiveEnchantItemId()) + { + _owner.setActiveEnchantItemId(Player.ID_NONE); + } + + if (item.getId() == ADENA_ID) + { + _adena = null; + } + else if (item.getId() == ANCIENT_ADENA_ID) + { + _ancientAdena = null; + } + + return super.removeItem(item); + } + + /** + * @return the quantity of items in the inventory + */ + public int getNonQuestSize() + { + return _items.size(); + } + + /** + * Refresh the weight of equipment loaded + */ + @Override + public void refreshWeight() + { + super.refreshWeight(); + _owner.refreshOverloaded(); + } + + /** + * Get back items in inventory from database + */ + @Override + public void restore() + { + super.restore(); + _adena = getItemByItemId(ADENA_ID); + _ancientAdena = getItemByItemId(ANCIENT_ADENA_ID); + } + + public static int[][] restoreVisibleInventory(int objectId) + { + final int[][] paperdoll = new int[31][3]; + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement("SELECT object_id,item_id,loc_data,enchant_level FROM items WHERE owner_id=? AND loc='PAPERDOLL'")) + { + ps.setInt(1, objectId); + try (ResultSet invdata = ps.executeQuery()) + { + while (invdata.next()) + { + final int slot = invdata.getInt("loc_data"); + paperdoll[slot][0] = invdata.getInt("object_id"); + if (TransmogConfig.ENABLE_TRANSMOG) + { + final ItemVariables vars = new ItemVariables(paperdoll[slot][0]); + paperdoll[slot][1] = vars.getInt(ItemVariables.TRANSMOG_ID, invdata.getInt("item_id")); + } + else + { + final int itemId = invdata.getInt("item_id"); + final ItemTemplate template = ItemData.getInstance().getTemplate(itemId); + paperdoll[slot][1] = template == null ? itemId : template.getDisplayId(); + } + + paperdoll[slot][2] = invdata.getInt("enchant_level"); + } + } + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Could not restore inventory: " + e.getMessage(), e); + } + + return paperdoll; + } + + /** + * @param itemList the items that needs to be validated. + * @param sendMessage if {@code true} will send a message of inventory full. + * @param sendSkillMessage if {@code true} will send a message of skill not available. + * @return {@code true} if the inventory isn't full after taking new items and items weight add to current load doesn't exceed max weight load. + */ + public boolean checkInventorySlotsAndWeight(List itemList, boolean sendMessage, boolean sendSkillMessage) + { + int lootWeight = 0; + int requiredSlots = 0; + if (itemList != null) + { + for (ItemTemplate item : itemList) + { + // If the item is not stackable or is stackable and not present in inventory, will need a slot. + if (!item.isStackable() || (getInventoryItemCount(item.getId(), -1) <= 0)) + { + requiredSlots++; + } + + lootWeight += item.getWeight(); + } + } + + final boolean inventoryStatusOK = validateCapacity(requiredSlots) && validateWeight(lootWeight); + if (!inventoryStatusOK && sendMessage) + { + _owner.sendPacket(SystemMessageId.YOUR_INVENTORY_IS_FULL); + if (sendSkillMessage) + { + _owner.sendPacket(SystemMessageId.WEIGHT_AND_VOLUME_LIMIT_HAS_BEEN_EXCEEDED_THAT_SKILL_IS_CURRENTLY_UNAVAILABLE); + } + } + + return inventoryStatusOK; + } + + /** + * If the item is not stackable or is stackable and not present in inventory, will need a slot. + * @param item the item to validate. + * @return {@code true} if there is enough room to add the item inventory. + */ + public boolean validateCapacity(Item item) + { + int slots = 0; + if (!item.isStackable() || ((getInventoryItemCount(item.getId(), -1) <= 0) && !item.getTemplate().hasExImmediateEffect())) + { + slots++; + } + + return validateCapacity(slots, item.isQuestItem()); + } + + /** + * If the item is not stackable or is stackable and not present in inventory, will need a slot. + * @param itemId the item Id for the item to validate. + * @return {@code true} if there is enough room to add the item inventory. + */ + public boolean validateCapacityByItemId(int itemId) + { + int slots = 0; + final Item invItem = getItemByItemId(itemId); + if ((invItem == null) || !invItem.isStackable()) + { + slots++; + } + + return validateCapacity(slots, ItemData.getInstance().getTemplate(itemId).isQuestItem()); + } + + @Override + public boolean validateCapacity(long slots) + { + return validateCapacity(slots, false); + } + + public boolean validateCapacity(long slots, boolean questItem) + { + return ((slots == 0) && !PlayerConfig.AUTO_LOOT_SLOT_LIMIT) || ((getNonQuestSize() + slots) <= _owner.getInventoryLimit()); + } + + @Override + public boolean validateWeight(long weight) + { + // Disable weight check for GMs. + if (_owner.isGM() && _owner.getDietMode() && _owner.getAccessLevel().allowTransaction()) + { + return true; + } + + return ((_totalWeight + weight) <= _owner.getMaxLoad()); + } + + /** + * Set inventory block for specified IDs
        + * array reference is used for {@link PlayerInventory#_blockItems} + * @param items array of Ids to block/allow + * @param mode blocking mode {@link PlayerInventory#_blockMode} + */ + public void setInventoryBlock(int[] items, int mode) + { + _blockMode = mode; + _blockItems = items; + + _owner.sendItemList(false); + } + + /** + * Unblock blocked itemIds + */ + public void unblock() + { + _blockMode = -1; + _blockItems = null; + + _owner.sendItemList(false); + } + + /** + * Check if player inventory is in block mode. + * @return true if some itemIds blocked + */ + public boolean hasInventoryBlock() + { + return (_blockMode > -1) && (_blockItems != null) && (_blockItems.length > 0); + } + + /** + * Block all player items + */ + public void blockAllItems() + { + // temp fix, some id must be sended + setInventoryBlock(new int[] + { + (ItemData.getInstance().getArraySize() + 2) + }, 1); + } + + /** + * Return block mode + * @return int {@link PlayerInventory#_blockMode} + */ + public int getBlockMode() + { + return _blockMode; + } + + /** + * Return int[] array with blocked item ids + * @return int[] array + */ + public int[] getBlockItems() + { + return _blockItems; + } + + /** + * Check if player can use item by itemid + * @param itemId int + * @return true if can use + */ + public boolean canManipulateWithItemId(int itemId) + { + return ((_blockMode != 0) || !ArrayUtil.contains(_blockItems, itemId)) && ((_blockMode != 1) || ArrayUtil.contains(_blockItems, itemId)); + } + + @Override + public String toString() + { + return getClass().getSimpleName() + "[" + _owner + "]"; + } + + /** + * Apply skills of inventory items + */ + public void applyItemSkills() + { + for (Item item : _items) + { + item.giveSkillsToOwner(); + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/itemcontainer/PlayerRefund.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/itemcontainer/PlayerRefund.java new file mode 100644 index 0000000..92adef2 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/itemcontainer/PlayerRefund.java @@ -0,0 +1,110 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.itemcontainer; + +import java.util.logging.Level; + +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.item.enums.ItemLocation; +import org.l2jmobius.gameserver.entity.item.enums.ItemProcessType; +import org.l2jmobius.gameserver.entity.item.instance.Item; +import org.l2jmobius.gameserver.managers.ItemManager; + +/** + * @author DS + */ +public class PlayerRefund extends ItemContainer +{ + private final Player _owner; + + public PlayerRefund(Player owner) + { + _owner = owner; + } + + @Override + public String getName() + { + return "Refund"; + } + + @Override + public Player getOwner() + { + return _owner; + } + + @Override + public ItemLocation getBaseLocation() + { + return ItemLocation.REFUND; + } + + @Override + protected void addItem(Item item) + { + super.addItem(item); + try + { + if (getSize() > 12) + { + final Item removedItem = _items.stream().findFirst().get(); + if (_items.remove(removedItem)) + { + ItemManager.destroyItem(ItemProcessType.REFUND, removedItem, getOwner(), null); + removedItem.updateDatabase(true); + } + } + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "addItem()", e); + } + } + + @Override + public void refreshWeight() + { + } + + @Override + public void deleteMe() + { + try + { + for (Item item : _items) + { + if (item != null) + { + ItemManager.destroyItem(ItemProcessType.REFUND, item, getOwner(), null); + item.updateDatabase(true); + } + } + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "deleteMe()", e); + } + + _items.clear(); + } + + @Override + public void restore() + { + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/itemcontainer/PlayerWarehouse.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/itemcontainer/PlayerWarehouse.java new file mode 100644 index 0000000..97a8795 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/itemcontainer/PlayerWarehouse.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.itemcontainer; + +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.item.enums.ItemLocation; + +public class PlayerWarehouse extends Warehouse +{ + // private static final Logger LOGGER = Logger.getLogger(PcWarehouse.class.getName()); + + private final Player _owner; + + public PlayerWarehouse(Player owner) + { + _owner = owner; + } + + @Override + public String getName() + { + return "Warehouse"; + } + + @Override + public Player getOwner() + { + return _owner; + } + + @Override + public ItemLocation getBaseLocation() + { + return ItemLocation.WAREHOUSE; + } + + @Override + public boolean validateCapacity(long slots) + { + return (_items.size() + slots) <= _owner.getWareHouseLimit(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/itemcontainer/Warehouse.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/itemcontainer/Warehouse.java new file mode 100644 index 0000000..7a2467a --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/itemcontainer/Warehouse.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.itemcontainer; + +/** + * @version $Revision: 1.3.2.1.2.12 $ $Date: 2005/04/06 16:13:42 $ + */ +public abstract class Warehouse extends ItemContainer +{ +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/residences/AbstractResidence.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/residences/AbstractResidence.java new file mode 100644 index 0000000..ffa3844 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/residences/AbstractResidence.java @@ -0,0 +1,122 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.residences; + +import java.util.ArrayList; +import java.util.List; + +import org.l2jmobius.gameserver.data.xml.SkillTreeData; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.zone.type.ResidenceZone; +import org.l2jmobius.gameserver.mechanics.events.ListenersContainer; +import org.l2jmobius.gameserver.mechanics.skill.holders.SkillHolder; +import org.l2jmobius.gameserver.mechanics.skill.holders.SkillLearn; + +/** + * @author xban1x + */ +public abstract class AbstractResidence extends ListenersContainer +{ + private final int _residenceId; + private String _name; + + private ResidenceZone _zone = null; + private final List _residentialSkills = new ArrayList<>(); + + public AbstractResidence(int residenceId) + { + _residenceId = residenceId; + initResidentialSkills(); + } + + protected abstract void load(); + + protected abstract void initResidenceZone(); + + protected void initResidentialSkills() + { + for (SkillLearn s : SkillTreeData.getInstance().getAvailableResidentialSkills(getResidenceId())) + { + _residentialSkills.add(new SkillHolder(s.getSkillId(), s.getSkillLevel())); + } + } + + public int getResidenceId() + { + return _residenceId; + } + + public String getName() + { + return _name; + } + + // TODO: Remove it later when both castles and forts are loaded from same table. + public void setName(String name) + { + _name = name; + } + + public ResidenceZone getResidenceZone() + { + return _zone; + } + + protected void setResidenceZone(ResidenceZone zone) + { + _zone = zone; + } + + public List getResidentialSkills() + { + return _residentialSkills; + } + + public void giveResidentialSkills(Player player) + { + if (!_residentialSkills.isEmpty()) + { + for (SkillHolder sh : _residentialSkills) + { + player.addSkill(sh.getSkill(), false); + } + } + } + + public void removeResidentialSkills(Player player) + { + if (!_residentialSkills.isEmpty()) + { + for (SkillHolder sh : _residentialSkills) + { + player.removeSkill(sh.getSkill(), false); + } + } + } + + @Override + public boolean equals(Object obj) + { + return (obj instanceof AbstractResidence) && (((AbstractResidence) obj).getResidenceId() == getResidenceId()); + } + + @Override + public String toString() + { + return _name + "(" + _residenceId + ")"; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/residences/AuctionItemType.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/residences/AuctionItemType.java new file mode 100644 index 0000000..badc573 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/residences/AuctionItemType.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.residences; + +public enum AuctionItemType +{ + ClanHall +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/residences/AuctionableHall.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/residences/AuctionableHall.java new file mode 100644 index 0000000..e1735bb --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/residences/AuctionableHall.java @@ -0,0 +1,236 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.residences; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.commons.database.DatabaseFactory; +import org.l2jmobius.commons.threads.ThreadPool; +import org.l2jmobius.gameserver.data.sql.ClanHallTable; +import org.l2jmobius.gameserver.data.sql.ClanTable; +import org.l2jmobius.gameserver.entity.clan.Clan; +import org.l2jmobius.gameserver.entity.item.enums.ItemProcessType; +import org.l2jmobius.gameserver.entity.itemcontainer.Inventory; +import org.l2jmobius.gameserver.managers.ClanHallAuctionManager; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; +import org.l2jmobius.gameserver.util.StatSet; + +public class AuctionableHall extends ClanHall +{ + private static final int CH_RATE = 604800000; + + protected long _paidUntil; + private final int _grade; + protected boolean _paid; + final int _lease; + + public AuctionableHall(StatSet set) + { + super(set); + _paidUntil = set.getLong("paidUntil"); + _grade = set.getInt("grade"); + _paid = set.getBoolean("paid"); + _lease = set.getInt("lease"); + if (getOwnerId() != 0) + { + _isFree = false; + initialyzeTask(false); + loadFunctions(); + } + } + + /** + * @return if clanHall is paid or not + */ + public boolean getPaid() + { + return _paid; + } + + /** Return lease */ + @Override + public int getLease() + { + return _lease; + } + + /** Return PaidUntil */ + @Override + public long getPaidUntil() + { + return _paidUntil; + } + + /** Return Grade */ + @Override + public int getGrade() + { + return _grade; + } + + @Override + public void free() + { + super.free(); + _paidUntil = 0; + _paid = false; + } + + @Override + public void setOwner(Clan clan) + { + super.setOwner(clan); + _paidUntil = System.currentTimeMillis(); + initialyzeTask(true); + } + + /** + * Initialize Fee Task + * @param forced + */ + private void initialyzeTask(boolean forced) + { + final long currentTime = System.currentTimeMillis(); + if (_paidUntil > currentTime) + { + ThreadPool.schedule(new FeeTask(), _paidUntil - currentTime); + } + else if (!_paid && !forced) + { + if ((System.currentTimeMillis() + (3600000 * 24)) <= (_paidUntil + CH_RATE)) + { + ThreadPool.schedule(new FeeTask(), System.currentTimeMillis() + (3600000 * 24)); + } + else + { + ThreadPool.schedule(new FeeTask(), (_paidUntil + CH_RATE) - System.currentTimeMillis()); + } + } + else + { + ThreadPool.schedule(new FeeTask(), 0); + } + } + + /** Fee Task */ + protected class FeeTask implements Runnable + { + private final Logger LOGGER = Logger.getLogger(FeeTask.class.getName()); + + @Override + public void run() + { + try + { + if (isFree()) + { + return; + } + + final long _time = System.currentTimeMillis(); + if (_paidUntil > _time) + { + ThreadPool.schedule(new FeeTask(), _paidUntil - _time); + return; + } + + final Clan clan = ClanTable.getInstance().getClan(getOwnerId()); + if (ClanTable.getInstance().getClan(getOwnerId()).getWarehouse().getAdena() >= getLease()) + { + if (_paidUntil != 0) + { + while (_paidUntil <= _time) + { + _paidUntil += CH_RATE; + } + } + else + { + _paidUntil = _time + CH_RATE; + } + + ClanTable.getInstance().getClan(getOwnerId()).getWarehouse().destroyItemByItemId(ItemProcessType.FEE, Inventory.ADENA_ID, getLease(), null, null); + ThreadPool.schedule(new FeeTask(), _paidUntil - _time); + _paid = true; + updateDb(); + } + else + { + _paid = false; + if (_time > (_paidUntil + CH_RATE)) + { + if (ClanHallTable.getInstance().loaded()) + { + ClanHallAuctionManager.getInstance().initNPC(getId()); + ClanHallTable.getInstance().setFree(getId()); + clan.broadcastToOnlineMembers(new SystemMessage(SystemMessageId.THE_CLAN_HALL_FEE_IS_ONE_WEEK_OVERDUE_THEREFORE_THE_CLAN_HALL_OWNERSHIP_HAS_BEEN_REVOKED)); + } + else + { + ThreadPool.schedule(new FeeTask(), 3000); + } + } + else + { + updateDb(); + final SystemMessage sm = new SystemMessage(SystemMessageId.PAYMENT_FOR_YOUR_CLAN_HALL_HAS_NOT_BEEN_MADE_PLEASE_MAKE_PAYMENT_TO_YOUR_CLAN_WAREHOUSE_BY_S1_TOMORROW); + sm.addInt(_lease); + clan.broadcastToOnlineMembers(sm); + if ((_time + (3600000 * 24)) <= (_paidUntil + CH_RATE)) + { + ThreadPool.schedule(new FeeTask(), _time + (3600000 * 24)); + } + else + { + ThreadPool.schedule(new FeeTask(), (_paidUntil + CH_RATE) - _time); + } + } + } + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "", e); + } + } + } + + @Override + public void updateDb() + { + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement("UPDATE clanhall SET ownerId=?, paidUntil=?, paid=? WHERE id=?")) + { + ps.setInt(1, getOwnerId()); + ps.setLong(2, _paidUntil); + ps.setInt(3, _paid ? 1 : 0); + ps.setInt(4, getId()); + ps.execute(); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Exception: updateOwnerInDB(Pledge clan): " + e.getMessage(), e); + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/residences/ClanHall.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/residences/ClanHall.java new file mode 100644 index 0000000..f7668d7 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/residences/ClanHall.java @@ -0,0 +1,551 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.residences; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.commons.database.DatabaseFactory; +import org.l2jmobius.commons.threads.ThreadPool; +import org.l2jmobius.gameserver.data.sql.ClanTable; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.instance.Door; +import org.l2jmobius.gameserver.entity.clan.Clan; +import org.l2jmobius.gameserver.entity.item.enums.ItemProcessType; +import org.l2jmobius.gameserver.entity.itemcontainer.Inventory; +import org.l2jmobius.gameserver.entity.zone.type.ClanHallZone; +import org.l2jmobius.gameserver.network.serverpackets.PledgeShowInfoUpdate; +import org.l2jmobius.gameserver.util.StatSet; + +public abstract class ClanHall +{ + protected static final Logger LOGGER = Logger.getLogger(ClanHall.class.getName()); + + final int _clanHallId; + private List _doors; + private final String _name; + private int _ownerId; + private final String _desc; + private final String _location; + private ClanHallZone _zone; + protected boolean _isFree = true; + private final Map _functions; + + /** Clan Hall Functions */ + public static final int FUNC_TELEPORT = 1; + public static final int FUNC_ITEM_CREATE = 2; + public static final int FUNC_RESTORE_HP = 3; + public static final int FUNC_RESTORE_MP = 4; + public static final int FUNC_RESTORE_EXP = 5; + public static final int FUNC_SUPPORT = 6; + public static final int FUNC_DECO_FRONTPLATEFORM = 7; // Only Auctionable Halls + public static final int FUNC_DECO_CURTAINS = 8; // Only Auctionable Halls + + public class ClanHallFunction + { + final int _type; + private int _level; + protected int _fee; + protected int _tempFee; + final long _rate; + long _endDate; + protected boolean _inDebt; + public boolean _cwh; // first activating clanhall function is payed from player inventory, any others from clan warehouse + + public ClanHallFunction(int type, int lvl, int lease, int tempLease, long rate, long time, boolean cwh) + { + _type = type; + _level = lvl; + _fee = lease; + _tempFee = tempLease; + _rate = rate; + _endDate = time; + initializeTask(cwh); + } + + public int getType() + { + return _type; + } + + public int getLevel() + { + return _level; + } + + public int getLease() + { + return _fee; + } + + public long getRate() + { + return _rate; + } + + public long getEndTime() + { + return _endDate; + } + + public void setLevel(int level) + { + _level = level; + } + + public void setLease(int lease) + { + _fee = lease; + } + + public void setEndTime(long time) + { + _endDate = time; + } + + private void initializeTask(boolean cwh) + { + if (_isFree) + { + return; + } + + final long currentTime = System.currentTimeMillis(); + if (_endDate > currentTime) + { + ThreadPool.schedule(new FunctionTask(cwh), _endDate - currentTime); + } + else + { + ThreadPool.schedule(new FunctionTask(cwh), 0); + } + } + + private class FunctionTask implements Runnable + { + public FunctionTask(boolean cwh) + { + _cwh = cwh; + } + + @Override + public void run() + { + try + { + if (_isFree) + { + return; + } + + if ((ClanTable.getInstance().getClan(getOwnerId()).getWarehouse().getAdena() >= _fee) || !_cwh) + { + final int fee = _endDate == -1 ? _tempFee : _fee; + setEndTime(System.currentTimeMillis() + _rate); + dbSave(); + if (_cwh) + { + ClanTable.getInstance().getClan(getOwnerId()).getWarehouse().destroyItemByItemId(ItemProcessType.FEE, Inventory.ADENA_ID, fee, null, null); + } + + ThreadPool.schedule(new FunctionTask(true), _rate); + } + else + { + removeFunction(_type); + } + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "", e); + } + } + } + + public void dbSave() + { + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement("REPLACE INTO clanhall_functions (hall_id, type, lvl, lease, rate, endTime) VALUES (?,?,?,?,?,?)")) + { + ps.setInt(1, _clanHallId); + ps.setInt(2, _type); + ps.setInt(3, _level); + ps.setInt(4, _fee); + ps.setLong(5, _rate); + ps.setLong(6, _endDate); + ps.execute(); + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "Exception: ClanHall.updateFunctions(int type, int lvl, int lease, long rate, long time, boolean addNew): " + e.getMessage(), e); + } + } + } + + public ClanHall(StatSet set) + { + _clanHallId = set.getInt("id"); + _name = set.getString("name"); + _ownerId = set.getInt("ownerId"); + _desc = set.getString("desc"); + _location = set.getString("location"); + _functions = new ConcurrentHashMap<>(); + if (_ownerId <= 0) + { + return; + } + + final Clan clan = ClanTable.getInstance().getClan(_ownerId); + if (clan != null) + { + clan.setHideoutId(_clanHallId); + } + else + { + free(); + } + } + + /** + * @return Id Of Clan hall + */ + public int getId() + { + return _clanHallId; + } + + /** + * @return the Clan Hall name. + */ + public String getName() + { + return _name; + } + + /** + * @return OwnerId + */ + public int getOwnerId() + { + return _ownerId; + } + + /** + * @return Desc + */ + public String getDesc() + { + return _desc; + } + + /** + * @return Location + */ + public String getLocation() + { + return _location; + } + + /** + * @return all Door + */ + public List getDoors() + { + if (_doors == null) + { + _doors = new ArrayList<>(); + } + + return _doors; + } + + /** + * @param doorId + * @return Door + */ + public Door getDoor(int doorId) + { + if (doorId <= 0) + { + return null; + } + + for (Door door : getDoors()) + { + if (door.getId() == doorId) + { + return door; + } + } + + return null; + } + + /** + * @param type + * @return function with id + */ + public ClanHallFunction getFunction(int type) + { + return _functions.get(type); + } + + /** + * Sets this clan halls zone + * @param zone + */ + public void setZone(ClanHallZone zone) + { + _zone = zone; + } + + /** + * @param x + * @param y + * @param z + * @return true if object is inside the zone + */ + public boolean checkIfInZone(int x, int y, int z) + { + return _zone.isInsideZone(x, y, z); + } + + /** + * @return the zone of this clan hall + */ + public ClanHallZone getZone() + { + return _zone; + } + + /** Free this clan hall */ + public void free() + { + _ownerId = 0; + _isFree = true; + for (Integer fc : _functions.keySet()) + { + removeFunction(fc); + } + + _functions.clear(); + updateDb(); + } + + /** + * Set owner if clan hall is free + * @param clan + */ + public void setOwner(Clan clan) + { + // Verify that this ClanHall is Free and Clan isn't null. + if ((_ownerId > 0) || (clan == null)) + { + return; + } + + _ownerId = clan.getId(); + _isFree = false; + clan.setHideoutId(_clanHallId); + + // Announce to Online member new ClanHall. + clan.broadcastToOnlineMembers(new PledgeShowInfoUpdate(clan)); + updateDb(); + } + + /** + * Open or Close Door + * @param player + * @param doorId + * @param open + */ + public void openCloseDoor(Player player, int doorId, boolean open) + { + if ((player != null) && (player.getClanId() == _ownerId)) + { + openCloseDoor(doorId, open); + } + } + + public void openCloseDoor(int doorId, boolean open) + { + openCloseDoor(getDoor(doorId), open); + } + + public void openCloseDoor(Door door, boolean open) + { + if (door != null) + { + if (open) + { + door.openMe(); + } + else + { + door.closeMe(); + } + } + } + + public void openCloseDoors(Player player, boolean open) + { + if ((player != null) && (player.getClanId() == _ownerId)) + { + openCloseDoors(open); + } + } + + public void openCloseDoors(boolean open) + { + for (Door door : getDoors()) + { + if (door != null) + { + if (open) + { + door.openMe(); + } + else + { + door.closeMe(); + } + } + } + } + + /** Banish Foreigner */ + public void banishForeigners() + { + if (_zone != null) + { + _zone.banishForeigners(_ownerId); + } + else + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Zone is null for clan hall: " + _clanHallId + " " + _name); + } + } + + /** Load All Functions */ + protected void loadFunctions() + { + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement("SELECT * FROM clanhall_functions WHERE hall_id = ?")) + { + ps.setInt(1, _clanHallId); + try (ResultSet rs = ps.executeQuery()) + { + while (rs.next()) + { + _functions.put(rs.getInt("type"), new ClanHallFunction(rs.getInt("type"), rs.getInt("lvl"), rs.getInt("lease"), 0, rs.getLong("rate"), rs.getLong("endTime"), true)); + } + } + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "Exception: ClanHall.loadFunctions(): " + e.getMessage(), e); + } + } + + /** + * Remove function In List and in DB + * @param functionType + */ + public void removeFunction(int functionType) + { + _functions.remove(functionType); + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement("DELETE FROM clanhall_functions WHERE hall_id=? AND type=?")) + { + ps.setInt(1, _clanHallId); + ps.setInt(2, functionType); + ps.execute(); + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "Exception: ClanHall.removeFunctions(int functionType): " + e.getMessage(), e); + } + } + + public boolean updateFunctions(Player player, int type, int level, int lease, long rate, boolean addNew) + { + if ((player == null) || ((lease > 0) && !player.destroyItemByItemId(null, Inventory.ADENA_ID, lease, null, true))) + { + return false; + } + + if (addNew) + { + _functions.put(type, new ClanHallFunction(type, level, lease, 0, rate, 0, false)); + } + else if ((level == 0) && (lease == 0)) + { + removeFunction(type); + } + else if ((lease - _functions.get(type).getLease()) > 0) + { + _functions.remove(type); + _functions.put(type, new ClanHallFunction(type, level, lease, 0, rate, -1, false)); + } + else + { + _functions.get(type).setLease(lease); + _functions.get(type).setLevel(level); + _functions.get(type).dbSave(); + } + + return true; + } + + public int getGrade() + { + return 0; + } + + public long getPaidUntil() + { + return 0; + } + + public int getLease() + { + return 0; + } + + public boolean isSiegableHall() + { + return false; + } + + public boolean isFree() + { + return _isFree; + } + + public abstract void updateDb(); +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/residences/ClanHallAuction.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/residences/ClanHallAuction.java new file mode 100644 index 0000000..473d626 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/residences/ClanHallAuction.java @@ -0,0 +1,632 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.residences; + +import static org.l2jmobius.gameserver.entity.itemcontainer.Inventory.ADENA_ID; +import static org.l2jmobius.gameserver.entity.itemcontainer.Inventory.MAX_ADENA; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.util.Calendar; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.commons.database.DatabaseFactory; +import org.l2jmobius.commons.threads.ThreadPool; +import org.l2jmobius.gameserver.data.sql.ClanHallTable; +import org.l2jmobius.gameserver.data.sql.ClanTable; +import org.l2jmobius.gameserver.entity.World; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.clan.Clan; +import org.l2jmobius.gameserver.entity.item.enums.ItemProcessType; +import org.l2jmobius.gameserver.entity.itemcontainer.ItemContainer; +import org.l2jmobius.gameserver.managers.ClanHallAuctionManager; +import org.l2jmobius.gameserver.managers.IdManager; +import org.l2jmobius.gameserver.network.SystemMessageId; + +public class ClanHallAuction +{ + protected static final Logger LOGGER = Logger.getLogger(ClanHallAuction.class.getName()); + private int _id = 0; + private long _endDate; + private int _highestBidderId = 0; + private String _highestBidderName = ""; + private int _highestBidderMaxBid = 0; + private int _itemId = 0; + private String _itemName = ""; + private int _itemObjectId = 0; + private final int _itemQuantity = 0; + private String _itemType = ""; + private int _sellerId = 0; + private String _sellerClanName = ""; + private String _sellerName = ""; + private int _currentBid = 0; + private int _startingBid = 0; + + private final Map _bidders = new ConcurrentHashMap<>(); + + private static final String[] ItemTypeName = + { + "ClanHall" + }; + + public static class Bidder + { + private final String _name; // TODO replace with objId + private final String _clanName; + private int _bid; + private final Calendar _timeBid; + + public Bidder(String name, String clanName, int bid, long timeBid) + { + _name = name; + _clanName = clanName; + _bid = bid; + _timeBid = Calendar.getInstance(); + _timeBid.setTimeInMillis(timeBid); + } + + public String getName() + { + return _name; + } + + public String getClanName() + { + return _clanName; + } + + public int getBid() + { + return _bid; + } + + public Calendar getTimeBid() + { + return _timeBid; + } + + public void setTimeBid(long timeBid) + { + _timeBid.setTimeInMillis(timeBid); + } + + public void setBid(int bid) + { + _bid = bid; + } + } + + /** Task Sheduler for endAuction */ + public class AutoEndTask implements Runnable + { + @Override + public void run() + { + try + { + endAuction(); + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "", e); + } + } + } + + /** + * Constructor + * @param auctionId + */ + public ClanHallAuction(int auctionId) + { + _id = auctionId; + load(); + startAutoTask(); + } + + public ClanHallAuction(int itemId, Clan clan, long delay, int bid, String name) + { + _id = itemId; + _endDate = System.currentTimeMillis() + delay; + _itemId = itemId; + _itemName = name; + _itemType = "ClanHall"; + _sellerId = clan.getLeaderId(); + _sellerName = clan.getLeaderName(); + _sellerClanName = clan.getName(); + _startingBid = bid; + } + + /** Load auctions */ + private void load() + { + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement("Select * from auction where id = ?")) + { + ps.setInt(1, _id); + try (ResultSet rs = ps.executeQuery()) + { + while (rs.next()) + { + _currentBid = rs.getInt("currentBid"); + _endDate = rs.getLong("endDate"); + _itemId = rs.getInt("itemId"); + _itemName = rs.getString("itemName"); + _itemObjectId = rs.getInt("itemObjectId"); + _itemType = rs.getString("itemType"); + _sellerId = rs.getInt("sellerId"); + _sellerClanName = rs.getString("sellerClanName"); + _sellerName = rs.getString("sellerName"); + _startingBid = rs.getInt("startingBid"); + } + } + + loadBid(); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Exception: Auction.load(): " + e.getMessage(), e); + } + } + + /** Load bidders **/ + private void loadBid() + { + _highestBidderId = 0; + _highestBidderName = ""; + _highestBidderMaxBid = 0; + + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement("SELECT bidderId, bidderName, maxBid, clan_name, time_bid FROM auction_bid WHERE auctionId = ? ORDER BY maxBid DESC")) + { + ps.setInt(1, _id); + try (ResultSet rs = ps.executeQuery()) + { + while (rs.next()) + { + if (rs.isFirst()) + { + _highestBidderId = rs.getInt("bidderId"); + _highestBidderName = rs.getString("bidderName"); + _highestBidderMaxBid = rs.getInt("maxBid"); + } + + _bidders.put(rs.getInt("bidderId"), new Bidder(rs.getString("bidderName"), rs.getString("clan_name"), rs.getInt("maxBid"), rs.getLong("time_bid"))); + } + } + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Exception: Auction.loadBid(): " + e.getMessage(), e); + } + } + + /** Task Manage */ + private void startAutoTask() + { + final long currentTime = System.currentTimeMillis(); + long taskDelay = 0; + if (_endDate <= currentTime) + { + _endDate = currentTime + (7 * 24 * 3600000); + saveAuctionDate(); + } + else + { + taskDelay = _endDate - currentTime; + } + + ThreadPool.schedule(new AutoEndTask(), taskDelay); + } + + public static String getItemTypeName(AuctionItemType value) + { + return ItemTypeName[value.ordinal()]; + } + + /** Save Auction Data End */ + private void saveAuctionDate() + { + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement("Update auction set endDate = ? where id = ?")) + { + ps.setLong(1, _endDate); + ps.setInt(2, _id); + ps.execute(); + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "Exception: saveAuctionDate(): " + e.getMessage(), e); + } + } + + /** + * Set a bid + * @param bidder + * @param bid + */ + public synchronized void setBid(Player bidder, int bid) + { + final int requiredAdena = _highestBidderName.equals(bidder.getClan().getLeaderName()) ? bid - _highestBidderMaxBid : bid; + if ((((_highestBidderId > 0) && (bid > _highestBidderMaxBid)) || ((_highestBidderId == 0) && (bid >= _startingBid))) && takeItem(bidder, requiredAdena)) + { + updateInDB(bidder, bid); + bidder.getClan().setAuctionBiddedAt(_id, true); + return; + } + + if ((bid < _startingBid) || (bid <= _highestBidderMaxBid)) + { + bidder.sendPacket(SystemMessageId.YOUR_BID_PRICE_MUST_BE_HIGHER_THAN_THE_MINIMUM_PRICE_THAT_CAN_BE_BID); + } + } + + /** + * Returns the item to the clan warehouse. + * @param clanName the clan name + * @param quantity the Adena value + * @param penalty if {@code true} fees are applied + */ + private void returnItem(String clanName, int quantity, boolean penalty) + { + int amount = quantity; + if (penalty) + { + amount *= 0.9; // take 10% tax fee if needed + } + + final Clan clan = ClanTable.getInstance().getClanByName(clanName); + if (clan == null) + { + LOGGER.warning("Clan " + clanName + " doesn't exist!"); + return; + } + + final ItemContainer cwh = clan.getWarehouse(); + if (cwh == null) + { + LOGGER.warning("There has been a problem with " + clanName + "'s clan warehouse!"); + return; + } + + // avoid overflow on return + final int limit = MAX_ADENA - cwh.getAdena(); + amount = Math.min(amount, limit); + cwh.addItem(ItemProcessType.REFUND, ADENA_ID, amount, null, null); + } + + /** + * Take Item in WHC + * @param bidder + * @param quantity + * @return + */ + private boolean takeItem(Player bidder, int quantity) + { + if ((bidder.getClan() != null) && (bidder.getClan().getWarehouse().getAdena() >= quantity)) + { + bidder.getClan().getWarehouse().destroyItemByItemId(ItemProcessType.BUY, ADENA_ID, quantity, bidder, bidder); + return true; + } + + bidder.sendPacket(SystemMessageId.THERE_IS_NOT_ENOUGH_ADENA_IN_THE_CLAN_HALL_WAREHOUSE); + return false; + } + + /** + * Update auction in DB + * @param bidder + * @param bid + */ + private void updateInDB(Player bidder, int bid) + { + try (Connection con = DatabaseFactory.getConnection()) + { + if (_bidders.get(bidder.getClanId()) != null) + { + try (PreparedStatement ps = con.prepareStatement("UPDATE auction_bid SET bidderId=?, bidderName=?, maxBid=?, time_bid=? WHERE auctionId=? AND bidderId=?")) + { + ps.setInt(1, bidder.getClanId()); + ps.setString(2, bidder.getClan().getLeaderName()); + ps.setInt(3, bid); + ps.setLong(4, System.currentTimeMillis()); + ps.setInt(5, _id); + ps.setInt(6, bidder.getClanId()); + ps.execute(); + } + } + else + { + try (PreparedStatement ps = con.prepareStatement("INSERT INTO auction_bid (id, auctionId, bidderId, bidderName, maxBid, clan_name, time_bid) VALUES (?, ?, ?, ?, ?, ?, ?)")) + { + ps.setInt(1, IdManager.getInstance().getNextId()); + ps.setInt(2, _id); + ps.setInt(3, bidder.getClanId()); + ps.setString(4, bidder.getName()); + ps.setInt(5, bid); + ps.setString(6, bidder.getClan().getName()); + ps.setLong(7, System.currentTimeMillis()); + ps.execute(); + } + + if (World.getPlayer(_highestBidderName) != null) + { + World.getPlayer(_highestBidderName).sendMessage("You have been out bidded"); + } + } + + final long currentTime = System.currentTimeMillis(); + _highestBidderId = bidder.getClanId(); + _highestBidderMaxBid = bid; + _highestBidderName = bidder.getClan().getLeaderName(); + if (_bidders.get(_highestBidderId) == null) + { + _bidders.put(_highestBidderId, new Bidder(_highestBidderName, bidder.getClan().getName(), bid, currentTime)); + } + else + { + _bidders.get(_highestBidderId).setBid(bid); + _bidders.get(_highestBidderId).setTimeBid(currentTime); + } + + bidder.sendPacket(SystemMessageId.YOU_HAVE_BID_IN_A_CLAN_HALL_AUCTION); + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "Exception: Auction.updateInDB(Player bidder, int bid): " + e.getMessage(), e); + } + } + + /** Remove bids */ + private void removeBids() + { + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement("DELETE FROM auction_bid WHERE auctionId=?")) + { + ps.setInt(1, _id); + ps.execute(); + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "Exception: Auction.deleteFromDB(): " + e.getMessage(), e); + } + + for (Bidder b : _bidders.values()) + { + if (ClanTable.getInstance().getClanByName(b.getClanName()).getHideoutId() == 0) + { + returnItem(b.getClanName(), b.getBid(), true); // 10 % tax + } + else if (World.getPlayer(b.getName()) != null) + { + World.getPlayer(b.getName()).sendMessage("Congratulation you have won ClanHall!"); + } + + ClanTable.getInstance().getClanByName(b.getClanName()).setAuctionBiddedAt(0, true); + } + + _bidders.clear(); + } + + /** Remove auctions */ + public void deleteAuctionFromDB() + { + ClanHallAuctionManager.getInstance().getAuctions().remove(this); + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement("DELETE FROM auction WHERE itemId=?")) + { + ps.setInt(1, _itemId); + ps.execute(); + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "Exception: Auction.deleteFromDB(): " + e.getMessage(), e); + } + } + + /** End of auction */ + public void endAuction() + { + if (ClanHallTable.getInstance().loaded()) + { + if ((_highestBidderId == 0) && (_sellerId == 0)) + { + startAutoTask(); + return; + } + + if ((_highestBidderId == 0) && (_sellerId > 0)) + { + /** + * If seller haven't sell ClanHall, auction removed, THIS MUST BE CONFIRMED + */ + final int aucId = ClanHallAuctionManager.getInstance().getAuctionIndex(_id); + ClanHallAuctionManager.getInstance().getAuctions().remove(aucId); + return; + } + + if (_sellerId > 0) + { + returnItem(_sellerClanName, _highestBidderMaxBid, true); + returnItem(_sellerClanName, ClanHallTable.getInstance().getAuctionableHallById(_itemId).getLease(), false); + } + + deleteAuctionFromDB(); + final Clan clan = ClanTable.getInstance().getClanByName(_bidders.get(_highestBidderId).getClanName()); + _bidders.remove(_highestBidderId); + clan.setAuctionBiddedAt(0, true); + removeBids(); + ClanHallTable.getInstance().setOwner(_itemId, clan); + } + else + { + /** Task waiting ClanHallManager is loaded every 3s */ + ThreadPool.schedule(new AutoEndTask(), 3000); + } + } + + /** + * Cancel bid + * @param bidder + */ + public synchronized void cancelBid(int bidder) + { + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement("DELETE FROM auction_bid WHERE auctionId=? AND bidderId=?")) + { + ps.setInt(1, _id); + ps.setInt(2, bidder); + ps.execute(); + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "Exception: Auction.cancelBid(String bidder): " + e.getMessage(), e); + } + + returnItem(_bidders.get(bidder).getClanName(), _bidders.get(bidder).getBid(), true); + ClanTable.getInstance().getClanByName(_bidders.get(bidder).getClanName()).setAuctionBiddedAt(0, true); + _bidders.clear(); + loadBid(); + } + + /** Cancel auction */ + public void cancelAuction() + { + deleteAuctionFromDB(); + removeBids(); + } + + /** Confirm an auction */ + public void confirmAuction() + { + ClanHallAuctionManager.getInstance().getAuctions().add(this); + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement("INSERT INTO auction (id, sellerId, sellerName, sellerClanName, itemType, itemId, itemObjectId, itemName, itemQuantity, startingBid, currentBid, endDate) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)")) + { + ps.setInt(1, _id); + ps.setInt(2, _sellerId); + ps.setString(3, _sellerName); + ps.setString(4, _sellerClanName); + ps.setString(5, _itemType); + ps.setInt(6, _itemId); + ps.setInt(7, _itemObjectId); + ps.setString(8, _itemName); + ps.setInt(9, _itemQuantity); + ps.setInt(10, _startingBid); + ps.setInt(11, _currentBid); + ps.setLong(12, _endDate); + ps.execute(); + ps.close(); + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "Exception: Auction.load(): " + e.getMessage(), e); + } + } + + /** + * Get var auction + * @return + */ + public int getId() + { + return _id; + } + + public long getCurrentBid() + { + return _currentBid; + } + + public long getEndDate() + { + return _endDate; + } + + public int getHighestBidderId() + { + return _highestBidderId; + } + + public String getHighestBidderName() + { + return _highestBidderName; + } + + public long getHighestBidderMaxBid() + { + return _highestBidderMaxBid; + } + + public int getItemId() + { + return _itemId; + } + + public String getItemName() + { + return _itemName; + } + + public int getItemObjectId() + { + return _itemObjectId; + } + + public long getItemQuantity() + { + return _itemQuantity; + } + + public String getItemType() + { + return _itemType; + } + + public int getSellerId() + { + return _sellerId; + } + + public String getSellerName() + { + return _sellerName; + } + + public String getSellerClanName() + { + return _sellerClanName; + } + + public long getStartingBid() + { + return _startingBid; + } + + public Map getBidders() + { + return _bidders; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/spawns/AutoSpawnHandler.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/spawns/AutoSpawnHandler.java new file mode 100644 index 0000000..c86c0fb --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/spawns/AutoSpawnHandler.java @@ -0,0 +1,714 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.spawns; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.commons.database.DatabaseFactory; +import org.l2jmobius.commons.threads.ThreadPool; +import org.l2jmobius.commons.util.Rnd; +import org.l2jmobius.gameserver.data.SpawnTable; +import org.l2jmobius.gameserver.data.xml.MapRegionData; +import org.l2jmobius.gameserver.entity.Location; +import org.l2jmobius.gameserver.entity.World; +import org.l2jmobius.gameserver.entity.actor.Npc; +import org.l2jmobius.gameserver.managers.IdManager; + +/** + * Auto Spawn handler.
        + * Allows spawning of a NPC object based on a timer.
        + * (From the official idea used for the Merchant and Blacksmith of Mammon)
        + * General Usage: - Call registerSpawn() with the parameters listed below.
        + * int npcId int[][] spawnPoints or specify NULL to add points later.
        + * int initialDelay (If < 0 = default value) int respawnDelay (If < 0 = default value)
        + * int despawnDelay (If < 0 = default value or if = 0, function disabled)
        + * spawnPoints is a standard two-dimensional int array containing X,Y and Z coordinates.
        + * The default respawn/despawn delays are currently every hour (as for Mammon on official servers).
        + * The resulting AutoSpawnInstance object represents the newly added spawn index.
        + * The internal methods of this object can be used to adjust random spawning, for instance a call to setRandomSpawn(1, true); would set the spawn at index 1 to be randomly rather than sequentially-based.
        + * Also they can be used to specify the number of NPC instances to spawn using setSpawnCount(), and broadcast a message to all users using setBroadcast().
        + * Random Spawning = OFF by default Broadcasting = OFF by default + * @author Tempy + */ +public class AutoSpawnHandler +{ + protected static final Logger LOGGER = Logger.getLogger(AutoSpawnHandler.class.getName()); + + private static final int DEFAULT_INITIAL_SPAWN = 30000; // 30 seconds after registration + private static final int DEFAULT_RESPAWN = 3600000; // 1 hour in millisecs + private static final int DEFAULT_DESPAWN = 3600000; // 1 hour in millisecs + + protected Map _registeredSpawns = new ConcurrentHashMap<>(); + protected Map> _runningSpawns = new ConcurrentHashMap<>(); + + protected boolean _activeState = true; + + protected AutoSpawnHandler() + { + restoreSpawnData(); + } + + public int size() + { + return _registeredSpawns.size(); + } + + public void reload() + { + // unload + unload(); + + // create clean list + _registeredSpawns.clear(); + _runningSpawns.clear(); + + // load + restoreSpawnData(); + } + + public void unload() + { + // stop all timers + for (ScheduledFuture sf : _runningSpawns.values()) + { + if (sf != null) + { + sf.cancel(true); + } + } + + // unregister all registered spawns + for (AutoSpawnInstance asi : _registeredSpawns.values()) + { + if (asi != null) + { + removeSpawn(asi); + } + } + } + + private void restoreSpawnData() + { + try (Connection con = DatabaseFactory.getConnection(); + Statement s = con.createStatement(); + ResultSet rs = s.executeQuery("SELECT * FROM random_spawn ORDER BY groupId ASC"); + PreparedStatement ps = con.prepareStatement("SELECT * FROM random_spawn_loc WHERE groupId=?")) + { + // Restore spawn group data, then the location data. + while (rs.next()) + { + // Register random spawn group, set various options on the created spawn instance. + final AutoSpawnInstance spawnInst = registerSpawn(rs.getInt("npcId"), rs.getInt("initialDelay"), rs.getInt("respawnDelay"), rs.getInt("despawnDelay")); + spawnInst.setSpawnCount(rs.getInt("count")); + spawnInst.setBroadcast(Boolean.parseBoolean(rs.getString("broadcastSpawn"))); + spawnInst.setRandomSpawn(Boolean.parseBoolean(rs.getString("randomSpawn"))); + + // Restore the spawn locations for this spawn group/instance. + ps.setInt(1, rs.getInt("groupId")); + try (ResultSet rs2 = ps.executeQuery()) + { + ps.clearParameters(); + while (rs2.next()) + { + // Add each location to the spawn group/instance. + spawnInst.addSpawnLocation(rs2.getInt("x"), rs2.getInt("y"), rs2.getInt("z"), rs2.getInt("heading")); + } + } + } + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "AutoSpawnHandler: Could not restore spawn data: " + e.getMessage(), e); + } + } + + /** + * Registers a spawn with the given parameters with the spawner, and marks it as active.
        + * Returns a AutoSpawnInstance containing info about the spawn. + * @param npcId + * @param spawnPoints + * @param initialDelay (If < 0 = default value) + * @param respawnDelay (If < 0 = default value) + * @param despawnDelay (If < 0 = default value or if = 0, function disabled) + * @return AutoSpawnInstance spawnInst + */ + public AutoSpawnInstance registerSpawn(int npcId, int[][] spawnPoints, int initialDelay, int respawnDelay, int despawnDelay) + { + final AutoSpawnInstance newSpawn = new AutoSpawnInstance(npcId, initialDelay < 0 ? DEFAULT_INITIAL_SPAWN : initialDelay, respawnDelay < 0 ? DEFAULT_RESPAWN : respawnDelay, despawnDelay < 0 ? DEFAULT_DESPAWN : despawnDelay); + if (spawnPoints != null) + { + for (int[] spawnPoint : spawnPoints) + { + newSpawn.addSpawnLocation(spawnPoint); + } + } + + final int newId = IdManager.getInstance().getNextId(); + newSpawn._objectId = newId; + _registeredSpawns.put(newId, newSpawn); + + setSpawnActive(newSpawn, true); + return newSpawn; + } + + /** + * Registers a spawn with the given parameters with the spawner, and marks it as active.
        + * Returns a AutoSpawnInstance containing info about the spawn.
        + * Warning: Spawn locations must be specified separately using addSpawnLocation(). + * @param npcId + * @param initialDelay (If < 0 = default value) + * @param respawnDelay (If < 0 = default value) + * @param despawnDelay (If < 0 = default value or if = 0, function disabled) + * @return AutoSpawnInstance spawnInst + */ + public AutoSpawnInstance registerSpawn(int npcId, int initialDelay, int respawnDelay, int despawnDelay) + { + return registerSpawn(npcId, null, initialDelay, respawnDelay, despawnDelay); + } + + /** + * Remove a registered spawn from the list, specified by the given spawn instance. + * @param spawnInst + * @return boolean removedSuccessfully + */ + public boolean removeSpawn(AutoSpawnInstance spawnInst) + { + if (!isSpawnRegistered(spawnInst)) + { + return false; + } + + try + { + // Try to remove from the list of registered spawns if it exists. + _registeredSpawns.remove(spawnInst.getId()); + + // Cancel the currently associated running scheduled task. + final ScheduledFuture task = _runningSpawns.remove(spawnInst._objectId); + if (task != null) + { + task.cancel(false); + } + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "AutoSpawnHandler: Could not auto spawn for NPC ID " + spawnInst._npcId + " (Object ID = " + spawnInst._objectId + "): " + e.getMessage(), e); + return false; + } + + return true; + } + + /** + * Remove a registered spawn from the list, specified by the given spawn object ID. + * @param objectId + */ + public void removeSpawn(int objectId) + { + removeSpawn(_registeredSpawns.get(objectId)); + } + + /** + * Sets the active state of the specified spawn. + * @param spawnInst + * @param isActive + */ + public void setSpawnActive(AutoSpawnInstance spawnInst, boolean isActive) + { + if (spawnInst == null) + { + return; + } + + final int objectId = spawnInst._objectId; + if (!isSpawnRegistered(objectId)) + { + return; + } + + ScheduledFuture spawnTask = null; + if (isActive) + { + final AutoSpawner rs = new AutoSpawner(objectId); + spawnTask = spawnInst._desDelay > 0 ? ThreadPool.scheduleAtFixedRate(rs, spawnInst._initDelay, spawnInst._resDelay) : ThreadPool.schedule(rs, spawnInst._initDelay); + _runningSpawns.put(objectId, spawnTask); + } + else + { + final AutoDespawner rd = new AutoDespawner(objectId); + spawnTask = _runningSpawns.remove(objectId); + if (spawnTask != null) + { + spawnTask.cancel(false); + } + + ThreadPool.schedule(rd, 0); + } + + spawnInst.setSpawnActive(isActive); + } + + /** + * Sets the active state of all auto spawn instances to that specified, and cancels the scheduled spawn task if necessary. + * @param isActive + */ + public void setAllActive(boolean isActive) + { + if (_activeState == isActive) + { + return; + } + + for (AutoSpawnInstance spawnInst : _registeredSpawns.values()) + { + setSpawnActive(spawnInst, isActive); + } + + _activeState = isActive; + } + + /** + * Returns the number of milliseconds until the next occurrence of the given spawn. + * @param spawnInst + * @return + */ + public long getTimeToNextSpawn(AutoSpawnInstance spawnInst) + { + final int objectId = spawnInst.getObjectId(); + return !isSpawnRegistered(objectId) ? -1 : _runningSpawns.containsKey(objectId) ? _runningSpawns.get(objectId).getDelay(TimeUnit.MILLISECONDS) : 0; + } + + /** + * Attempts to return the AutoSpawnInstance associated with the given NPC or Object ID type.
        + * Note: If isObjectId == false, returns first instance for the specified NPC ID. + * @param id + * @param isObjectId + * @return AutoSpawnInstance spawnInst + */ + public AutoSpawnInstance getAutoSpawnInstance(int id, boolean isObjectId) + { + if (isObjectId) + { + if (isSpawnRegistered(id)) + { + return _registeredSpawns.get(id); + } + } + else + { + for (AutoSpawnInstance spawnInst : _registeredSpawns.values()) + { + if (spawnInst.getId() == id) + { + return spawnInst; + } + } + } + + return null; + } + + public List getAutoSpawnInstances(int npcId) + { + final List result = new ArrayList<>(); + for (AutoSpawnInstance spawnInst : _registeredSpawns.values()) + { + if (spawnInst.getId() == npcId) + { + result.add(spawnInst); + } + } + + return result; + } + + /** + * Tests if the specified object ID is assigned to an auto spawn. + * @param objectId + * @return isAssigned + */ + public boolean isSpawnRegistered(int objectId) + { + return _registeredSpawns.containsKey(objectId); + } + + /** + * Tests if the specified spawn instance is assigned to an auto spawn. + * @param spawnInst + * @return boolean isAssigned + */ + public boolean isSpawnRegistered(AutoSpawnInstance spawnInst) + { + return _registeredSpawns.containsValue(spawnInst); + } + + /** + * AutoSpawner class
        + * This handles the main spawn task for an auto spawn instance, and initializes a despawner if required. + */ + private class AutoSpawner implements Runnable + { + private final int _objectId; + + protected AutoSpawner(int objectId) + { + _objectId = objectId; + } + + @Override + public void run() + { + try + { + // Retrieve the required spawn instance for this spawn task. + final AutoSpawnInstance spawnInst = _registeredSpawns.get(_objectId); + + // If the spawn is not scheduled to be active, cancel the spawn task. + if (!spawnInst.isSpawnActive()) + { + return; + } + + final List locationList = spawnInst.getLocationList(); + + // If there are no set co-ordinates, cancel the spawn task. + if (locationList.isEmpty()) + { + LOGGER.info("AutoSpawnHandler: No location co-ords specified for spawn instance (Object ID = " + _objectId + ")."); + return; + } + + final int locationCount = locationList.size(); + int locationIndex = Rnd.get(locationCount); + + // If random spawning is disabled, the spawn at the next set of co-ordinates after the last. + // If the index is greater than the number of possible spawns, reset the counter to zero. + if (!spawnInst.isRandomSpawn()) + { + locationIndex = spawnInst._lastLocIndex + 1; + if (locationIndex == locationCount) + { + locationIndex = 0; + } + + spawnInst._lastLocIndex = locationIndex; + } + + // Set the X, Y and Z co-ordinates, where this spawn will take place. + final int x = locationList.get(locationIndex).getX(); + final int y = locationList.get(locationIndex).getY(); + final int z = locationList.get(locationIndex).getZ(); + final int heading = locationList.get(locationIndex).getHeading(); + final Spawn newSpawn = new Spawn(spawnInst.getId()); + newSpawn.setXYZ(x, y, z); + if (heading != -1) + { + newSpawn.setHeading(heading); + } + + newSpawn.setAmount(spawnInst.getSpawnCount()); + if (spawnInst._desDelay == 0) + { + newSpawn.setRespawnDelay(spawnInst._resDelay); + } + + // Add the new spawn information to the spawn table, but do not store it. + SpawnTable.getInstance().addSpawn(newSpawn); + Npc npcInst = null; + if (spawnInst._spawnCount == 1) + { + npcInst = newSpawn.doSpawn(); + npcInst.setXYZ(npcInst.getX(), npcInst.getY(), npcInst.getZ()); + spawnInst.addNpc(npcInst); + } + else + { + for (int i = 0; i < spawnInst._spawnCount; i++) + { + npcInst = newSpawn.doSpawn(); + + // To prevent spawning of more than one NPC in the exact same spot, move it slightly by a small random offset. + npcInst.setXYZ(npcInst.getX() + Rnd.get(50), npcInst.getY() + Rnd.get(50), npcInst.getZ()); + + // Add the NPC instance to the list of managed instances. + spawnInst.addNpc(npcInst); + } + } + + newSpawn.stopRespawn(); + + if (npcInst != null) + { + if (spawnInst.isRandomSpawn()) + { + while ((World.getFirstVisibleObjectInRange(npcInst, Npc.class, 5) != null)) + { + // LOGGER.log(Level.INFO, "AutoSpawnHandler: Random spawn location " + npcInst.getLocation() + " for " + npcInst + " is occupied. Teleporting..."); + npcInst.teleToLocation(locationList.get(Rnd.get(locationList.size()))); + } + } + + if (spawnInst.isBroadcasting()) + { + World.broadcastToAllOnlinePlayers("The " + npcInst.getName() + " has spawned near " + MapRegionData.getInstance().getClosestTownName(npcInst) + "!"); + } + } + + // If there is no despawn time, do not create a despawn task. + if (spawnInst.getDespawnDelay() > 0) + { + ThreadPool.schedule(new AutoDespawner(_objectId), spawnInst.getDespawnDelay() - 1000); + } + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "AutoSpawnHandler: An error occurred while initializing spawn instance (Object ID = " + _objectId + "): " + e.getMessage(), e); + } + } + } + + /** + * AutoDespawner Class
        + * Simply used as a secondary class for despawning an auto spawn instance. + */ + private class AutoDespawner implements Runnable + { + private final int _objectId; + + protected AutoDespawner(int objectId) + { + _objectId = objectId; + } + + @Override + public void run() + { + try + { + final AutoSpawnInstance spawnInst = _registeredSpawns.get(_objectId); + if (spawnInst == null) + { + LOGGER.info("AutoSpawnHandler: No spawn registered for object ID = " + _objectId + "."); + return; + } + + for (Npc npcInst : spawnInst.getNpcList()) + { + if (npcInst == null) + { + continue; + } + + npcInst.deleteMe(); + SpawnTable.getInstance().removeSpawn(npcInst.getSpawn()); + spawnInst.removeNpc(npcInst); + } + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "AutoSpawnHandler: An error occurred while despawning spawn (Object ID = " + _objectId + "): " + e.getMessage(), e); + } + } + } + + /** + * AutoSpawnInstance Class
        + * Stores information about a registered auto spawn. + */ + public static class AutoSpawnInstance + { + protected int _objectId; + + protected int _spawnIndex; + + protected int _npcId; + + protected int _initDelay; + + protected int _resDelay; + + protected int _desDelay; + + protected int _spawnCount = 1; + + protected int _lastLocIndex = -1; + + private final Queue _npcList = new ConcurrentLinkedQueue<>(); + + private final List _locList = new CopyOnWriteArrayList<>(); + + private boolean _spawnActive; + + private boolean _randomSpawn = false; + + private boolean _broadcastAnnouncement = false; + + protected AutoSpawnInstance(int npcId, int initDelay, int respawnDelay, int despawnDelay) + { + _npcId = npcId; + _initDelay = initDelay; + _resDelay = respawnDelay; + _desDelay = despawnDelay; + } + + protected void setSpawnActive(boolean activeValue) + { + _spawnActive = activeValue; + } + + protected boolean addNpc(Npc npcInst) + { + return _npcList.add(npcInst); + } + + protected boolean removeNpc(Npc npcInst) + { + return _npcList.remove(npcInst); + } + + public int getObjectId() + { + return _objectId; + } + + public int getInitialDelay() + { + return _initDelay; + } + + public int getRespawnDelay() + { + return _resDelay; + } + + public int getDespawnDelay() + { + return _desDelay; + } + + /** + * Gets the NPC ID. + * @return the NPC ID + */ + public int getId() + { + return _npcId; + } + + public int getSpawnCount() + { + return _spawnCount; + } + + public List getLocationList() + { + return _locList; + } + + public Queue getNpcList() + { + return _npcList; + } + + public List getSpawns() + { + final List npcSpawns = new ArrayList<>(); + for (Npc npcInst : _npcList) + { + npcSpawns.add(npcInst.getSpawn()); + } + + return npcSpawns; + } + + public void setSpawnCount(int spawnCount) + { + _spawnCount = spawnCount; + } + + public void setRandomSpawn(boolean randValue) + { + _randomSpawn = randValue; + } + + public void setBroadcast(boolean broadcastValue) + { + _broadcastAnnouncement = broadcastValue; + } + + public boolean isSpawnActive() + { + return _spawnActive; + } + + public boolean isRandomSpawn() + { + return _randomSpawn; + } + + public boolean isBroadcasting() + { + return _broadcastAnnouncement; + } + + public boolean addSpawnLocation(int x, int y, int z, int heading) + { + return _locList.add(new Location(x, y, z, heading)); + } + + public boolean addSpawnLocation(int[] spawnLoc) + { + return (spawnLoc.length == 3) && addSpawnLocation(spawnLoc[0], spawnLoc[1], spawnLoc[2], -1); + } + + public Location removeSpawnLocation(int locIndex) + { + try + { + return _locList.remove(locIndex); + } + catch (IndexOutOfBoundsException e) + { + return null; + } + } + } + + public static AutoSpawnHandler getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final AutoSpawnHandler INSTANCE = new AutoSpawnHandler(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/spawns/Spawn.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/spawns/Spawn.java new file mode 100644 index 0000000..5c3c721 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/spawns/Spawn.java @@ -0,0 +1,690 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.spawns; + +import java.lang.reflect.Constructor; +import java.util.Deque; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedDeque; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.commons.util.Rnd; +import org.l2jmobius.gameserver.config.custom.ChampionMonstersConfig; +import org.l2jmobius.gameserver.config.custom.RandomSpawnsConfig; +import org.l2jmobius.gameserver.data.xml.NpcData; +import org.l2jmobius.gameserver.data.xml.NpcPersonalAIData; +import org.l2jmobius.gameserver.entity.Location; +import org.l2jmobius.gameserver.entity.actor.Npc; +import org.l2jmobius.gameserver.entity.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.entity.zone.type.NpcSpawnTerritory; +import org.l2jmobius.gameserver.entity.zone.type.WaterZone; +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.managers.WalkingManager; +import org.l2jmobius.gameserver.managers.ZoneManager; +import org.l2jmobius.gameserver.mechanics.sevensigns.SevenSignsFestival; +import org.l2jmobius.gameserver.taskmanagers.RespawnTaskManager; +import org.l2jmobius.gameserver.util.LocationUtil; + +/** + * This class manages the spawn and respawn of a group of Npcs that are in the same are and have the same type.
        + * Concept:
        + * Npc can be spawned either in a random position into a location area (if Lox=0 and Locy=0), either at an exact position.
        + * The heading of the Npc can be a random heading if not defined (value= -1) or an exact heading (ex : merchant...). + * @author Nightmare, Mobius + */ +public class Spawn extends Location +{ + protected static final Logger LOGGER = Logger.getLogger(Spawn.class.getName()); + + /** String identifier of this spawn */ + private String _name; + /** The link on the NpcTemplate object containing generic and static properties of this spawn (ex : RewardExp, RewardSP, AggroRange...) */ + private NpcTemplate _template; + /** The maximum number of Npc that can manage this Spawn */ + private int _maximumCount; + /** The current number of Npc managed by this Spawn */ + private int _currentCount; + /** The current number of SpawnTask in progress or stand by of this Spawn */ + public int _scheduledCount; + /** The identifier of the location area where Npc can be spawned */ + private int _locationId; + /** Link to NPC spawn territory */ + private NpcSpawnTerritory _spawnTerritory = null; + /** Minimum respawn delay */ + private int _respawnMinDelay; + /** Maximum respawn delay */ + private int _respawnMaxDelay; + /** Maximum distance monsters can be pulled away from spawn. */ + private int _chaseRange = 0; + /** The generic constructor of Npc managed by this Spawn */ + private Constructor _constructor; + /** If True an Npc is respawned each time that another is killed */ + private boolean _doRespawn = true; + private static Set _spawnListeners = ConcurrentHashMap.newKeySet(1); + private final Deque _spawnedNpcs = new ConcurrentLinkedDeque<>(); + private boolean _randomWalk = false; // Is random walk + private int _spawnTemplateId = 0; + private Location _spawnLocation = null; + /** Spawn group this spawn is a slot of, null for regular spawns */ + private SpawnGroup _group = null; + + /** + * Constructor of Spawn.
        + *
        + * Concept:
        + *
        + * Each Spawn owns generic and static properties (ex : RewardExp, RewardSP, AggroRange...).
        + * All of those properties are stored in a different NpcTemplate for each type of Spawn. Each template is loaded once in the server cache memory (reduce memory use).
        + * When a new instance of Spawn is created, server just create a link between the instance and the template.
        + * This link is stored in _template Each Npc is linked to a Spawn that manages its spawn and respawn (delay, location...).
        + * This link is stored in _spawn of the Npc.
        + *
        + * Actions:
        + *
          + *
        • Set the _template of the Spawn
        • + *
        • Calculate the implementationName used to generate the generic constructor of Npc managed by this Spawn
        • + *
        • Create the generic constructor of Npc managed by this Spawn
        • + *
        + * @param template The NpcTemplate to link to this Spawn + * @throws ClassNotFoundException + * @throws NoSuchMethodException + * @throws ClassCastException when template type is not subclass of Npc + */ + public Spawn(NpcTemplate template) throws ClassNotFoundException, NoSuchMethodException, ClassCastException + { + super(0, 0, -10000); + + // Set the _template of the Spawn. + _template = template; + + if (_template == null) + { + return; + } + + _constructor = Class.forName("org.l2jmobius.gameserver.entity.actor.instance." + _template.getType()).asSubclass(Npc.class).getConstructor(NpcTemplate.class); + } + + /** + * Creates a spawn. + * @param npcId the NPC ID + * @throws ClassCastException + * @throws NoSuchMethodException + * @throws ClassNotFoundException + */ + public Spawn(int npcId) throws ClassNotFoundException, NoSuchMethodException, ClassCastException + { + this(NpcData.getInstance().getTemplate(npcId)); + } + + /** + * @return the maximum number of Npc that this Spawn can manage. + */ + public int getAmount() + { + return _maximumCount; + } + + /** + * @return the String Identifier of this spawn. + */ + public String getName() + { + return _name; + } + + /** + * Set the String Identifier of this spawn. + * @param name + */ + public void setName(String name) + { + _name = name; + } + + /** + * @return the Identifier of the location area where Npc can be spawned. + */ + public int getLocationId() + { + return _locationId; + } + + /** + * Gets the NPC ID. + * @return the NPC ID + */ + public int getId() + { + return _template.getId(); + } + + /** + * @return min respawn delay. + */ + public int getRespawnMinDelay() + { + return _respawnMinDelay; + } + + /** + * @return max respawn delay. + */ + public int getRespawnMaxDelay() + { + return _respawnMaxDelay; + } + + /** + * Set the maximum number of Npc that this Spawn can manage. + * @param amount + */ + public void setAmount(int amount) + { + _maximumCount = amount; + } + + /** + * Set the Identifier of the location area where Npc can be spawned. + * @param id + */ + public void setLocationId(int id) + { + _locationId = id; + } + + /** + * Set Minimum Respawn Delay. + * @param date + */ + public void setRespawnMinDelay(int date) + { + _respawnMinDelay = date; + } + + /** + * Set Maximum Respawn Delay. + * @param date + */ + public void setRespawnMaxDelay(int date) + { + _respawnMaxDelay = date; + } + + /** + * Decrease the current number of Npc of this Spawn and if necessary create a SpawnTask to launch after the respawn Delay. Actions: + *
      • Decrease the current number of Npc of this Spawn
      • + *
      • Check if respawn is possible to prevent multiple respawning caused by lag
      • + *
      • Update the current number of SpawnTask in progress or stand by of this Spawn
      • + *
      • Create a new SpawnTask to launch after the respawn Delay
      • Caution: A respawn is possible ONLY if _doRespawn=True and _scheduledCount + _currentCount < _maximumCount + * @param oldNpc + */ + public void decreaseCount(Npc oldNpc) + { + // sanity check + if (_currentCount <= 0) + { + return; + } + + // Decrease the current number of Npc of this Spawn. + _currentCount--; + + // Remove this NPC from list of spawned. + _spawnedNpcs.remove(oldNpc); + + // Check if respawn is possible to prevent multiple respawning caused by lag. + if (_doRespawn && ((_scheduledCount + _currentCount) < _maximumCount)) + { + // Update the current number of SpawnTask in progress or stand by of this Spawn. + _scheduledCount++; + + // Schedule the next respawn. + RespawnTaskManager.getInstance().add(oldNpc, System.currentTimeMillis() + (hasRespawnRandom() ? Rnd.get(_respawnMinDelay, _respawnMaxDelay) : _respawnMinDelay)); + } + + // Notify the spawn group, it rolls the replacement itself. + if (_group != null) + { + _group.onUnitDeath(this); + } + } + + /** + * @return the spawn group this spawn is a slot of, {@code null} for regular spawns + */ + public SpawnGroup getGroup() + { + return _group; + } + + /** + * Sets the spawn group this spawn is a slot of. + * @param group the owning spawn group + */ + public void setGroup(SpawnGroup group) + { + _group = group; + } + + /** + * Create the initial spawning and set _doRespawn to False, if respawn time set to 0, or set it to True otherwise. + * @return The number of Npc that were spawned + */ + public int init() + { + while (_currentCount < _maximumCount) + { + final int previousCount = _currentCount; + if ((doSpawn() == null) && (_currentCount == previousCount)) + { + // Respawn is disabled or the NPC could not be created, looping again would never end. + break; + } + } + + _doRespawn = _respawnMinDelay > 0; + + return _currentCount; + } + + /** + * @return true if respawn enabled + */ + public boolean isRespawnEnabled() + { + return _doRespawn; + } + + /** + * Set _doRespawn to False to stop respawn in this Spawn. + */ + public void stopRespawn() + { + _doRespawn = false; + } + + /** + * Set _doRespawn to True to start or restart respawn in this Spawn. + */ + public void startRespawn() + { + _doRespawn = true; + } + + public Npc doSpawn() + { + return _doRespawn ? doSpawn(false) : null; + } + + /** + * Create the Npc, add it to the world and lauch its OnSpawn action.
        + *
        + * Concept:
        + *
        + * Npc can be spawned either in a random position into a location area (if Lox=0 and Locy=0), either at an exact position.
        + * The heading of the Npc can be a random heading if not defined (value= -1) or an exact heading (ex : merchant...).
        + *
        + * Actions for an random spawn into location area: (if Locx=0 and Locy=0) + *
          + *
        • Get Npc Init parameters and its generate an Identifier
        • + *
        • Call the constructor of the Npc
        • + *
        • Calculate the random position in the location area (if Locx=0 and Locy=0) or get its exact position from the Spawn
        • + *
        • Set the position of the Npc
        • + *
        • Set the HP and MP of the Npc to the max
        • + *
        • Set the heading of the Npc (random heading if not defined : value=-1)
        • + *
        • Link the Npc to this Spawn
        • + *
        • Init other values of the Npc (ex : from its CreatureTemplate for INT, STR, DEX...) and add it in the world
        • + *
        • Launch the action OnSpawn fo the Npc
        • + *
        • Increase the current number of Npcs managed by this Spawn
        • + *
        + * @param isSummonSpawn + * @return + */ + public Npc doSpawn(boolean isSummonSpawn) + { + try + { + // Check if the spawn is not a Pet, Decoy or Trap spawn. + if (_template.isType("Pet") || _template.isType("Decoy") || _template.isType("Trap")) + { + _currentCount++; + return null; + } + + // Call the constructor of the Npc. + final Npc npc = _constructor.newInstance(_template); + npc.setInstanceId(getInstanceId()); // Must be done before object is spawned into visible world. + if (isSummonSpawn) + { + npc.setShowSummonAnimation(isSummonSpawn); + } + + return initializeNpc(npc); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "NPC " + _template.getId() + " class not found", e); + } + + return null; + } + + /** + * @param npc + * @return + */ + private Npc initializeNpc(Npc npc) + { + // Make it alive + npc.setDead(false); + + // Reset decay info + npc.setDecayed(false); + + int newlocx = 0; + int newlocy = 0; + int newlocz = -10000; + + // If Locx and Locy are not defined, the Npc must be spawned in an area defined by location or spawn territory. + if (_spawnTerritory != null) + { + final Location loc = _spawnTerritory.getRandomPoint(); + newlocx = loc.getX(); + newlocy = loc.getY(); + newlocz = loc.getZ(); + setLocation(loc); + setHeading(-1); + } + else if ((getX() == 0) && (getY() == 0)) + { + LOGGER.warning("NPC " + npc + " doesn't have spawn location!"); + return null; + } + else + { + // The Npc is spawned at the exact position (Lox, Locy, Locz). + newlocx = _spawnLocation != null ? _spawnLocation.getX() : getX(); + newlocy = _spawnLocation != null ? _spawnLocation.getY() : getY(); + newlocz = _spawnLocation != null ? _spawnLocation.getZ() : getZ(); + + if (_spawnLocation == null) + { + _spawnLocation = new Location(newlocx, newlocy, newlocz); + } + } + + // Check if npc is in water. + final WaterZone water = ZoneManager.getInstance().getZone(newlocx, newlocy, newlocz, WaterZone.class); + + // If random spawn system is enabled. + if (RandomSpawnsConfig.ENABLE_RANDOM_MONSTER_SPAWNS && (getHeading() != -1) && npc.isMonster() && !npc.isQuestMonster() && !WalkingManager.getInstance().isTargeted(npc) && (getInstanceId() == 0) && !getTemplate().isUndying() && !npc.isRaid() && !npc.isRaidMinion() && !npc.isFlying() && (water == null) && !RandomSpawnsConfig.MOBS_LIST_NOT_RANDOM.contains(npc.getId())) + { + final int randX = newlocx + Rnd.get(RandomSpawnsConfig.MOB_MIN_SPAWN_RANGE, RandomSpawnsConfig.MOB_MAX_SPAWN_RANGE); + final int randY = newlocy + Rnd.get(RandomSpawnsConfig.MOB_MIN_SPAWN_RANGE, RandomSpawnsConfig.MOB_MAX_SPAWN_RANGE); + if (GeoEngine.getInstance().canMoveToTarget(newlocx, newlocy, newlocz, randX, randY, newlocz, getInstanceId()) // + && GeoEngine.getInstance().canSeeTarget(newlocx, newlocy, newlocz, randX, randY, newlocz, getInstanceId())) + { + newlocx = randX; + newlocy = randY; + setXYZ(newlocx, newlocy, newlocz); + setHeading(-1); + } + } + + // Correct Z of monsters. + if (npc.isMonster() && !npc.isFlying() && (water == null)) + { + // Do not correct Z distances greater than 300. + final int geoZ = GeoEngine.getInstance().getHeight(newlocx, newlocy, newlocz); + if (LocationUtil.calculateDistance(newlocx, newlocy, newlocz, newlocx, newlocy, geoZ, true, true) < 300) + { + newlocz = geoZ; + } + + // Prevent new z exceeding spawn territory high z. + if (_spawnTerritory != null) + { + final int highZ = _spawnTerritory.getHighZ(); + if (highZ < newlocz) + { + newlocz = highZ; + } + } + } + + npc.stopAllEffects(); + + // Set the HP and MP of the Npc to the max. + npc.setCurrentHpMp(npc.getMaxHp(), npc.getMaxMp()); + + // Clear script variables + if (npc.hasVariables()) + { + npc.getVariables().getSet().clear(); + } + + // Set is not random walk default value. + npc.setRandomWalking(_randomWalk); + + // Set the heading of the Npc (random heading if not defined). + if (getHeading() == -1) + { + npc.setHeading(Rnd.get(61794)); + } + else + { + npc.setHeading(getHeading()); + } + + // Reset summoner + npc.setSummoner(null); + + // Reset summoned list + npc.resetSummonedNpcs(); + + // Link the Npc to this Spawn. + npc.setSpawn(this); + + // Spawn NPC + npc.spawnMe(newlocx, newlocy, newlocz); + + // Make sure info is broadcasted in instances. + if (npc.isInInstance()) + { + npc.broadcastInfo(); + } + + notifyNpcSpawned(npc); + + // Check for overriden by spawnlist AIData. + if (_name != null) + { + NpcPersonalAIData.getInstance().initializeNpcParameters(npc, this, _name); + } + + _spawnedNpcs.add(npc); + + // Increase the current number of Npcs managed by this Spawn. + _currentCount++; + + // Minions + if (npc.isMonster() && NpcData.getMasterMonsterIDs().contains(npc.getId())) + { + npc.asMonster().getMinionList().spawnMinions(npc.getTemplate().getParameters().getMinionList("Privates")); + } + + if (npc.isAttackable()) + { + npc.asAttackable().setChampion(false); + } + + // Set champion on next spawn. + if (ChampionMonstersConfig.CHAMPION_ENABLE && npc.isMonster() && !npc.isQuestMonster() && !_template.isUndying() && !npc.isRaid() && !npc.isRaidMinion() && (ChampionMonstersConfig.CHAMPION_FREQUENCY > 0) && (npc.getLevel() >= ChampionMonstersConfig.CHAMP_MIN_LEVEL) && (npc.getLevel() <= ChampionMonstersConfig.CHAMP_MAX_LEVEL) && (ChampionMonstersConfig.CHAMPION_ENABLE_IN_INSTANCES || (getInstanceId() == 0)) && (Rnd.get(100) < ChampionMonstersConfig.CHAMPION_FREQUENCY)) + { + npc.asAttackable().setChampion(true); + } + + return npc; + } + + public static void addSpawnListener(SevenSignsFestival listener) + { + _spawnListeners.add(listener); + } + + public static void removeSpawnListener(SevenSignsFestival listener) + { + _spawnListeners.remove(listener); + } + + public static void notifyNpcSpawned(Npc npc) + { + for (SevenSignsFestival listener : _spawnListeners) + { + listener.npcSpawned(npc); + } + } + + /** + * Set bounds for random calculation and delay for respawn + * @param delay delay in seconds + * @param randomInterval random interval in seconds + */ + public void setRespawnDelay(int delay, int randomInterval) + { + if (delay != 0) + { + if (delay < 0) + { + LOGGER.warning("respawn delay is negative for spawn:" + this); + } + + final int minDelay = delay - randomInterval; + final int maxDelay = delay + randomInterval; + + _respawnMinDelay = Math.max(1, minDelay) * 1000; + _respawnMaxDelay = Math.max(1, maxDelay) * 1000; + } + else + { + _respawnMinDelay = 0; + _respawnMaxDelay = 0; + } + } + + public void setRespawnDelay(int delay) + { + setRespawnDelay(delay, 0); + } + + public int getRespawnDelay() + { + return (_respawnMinDelay + _respawnMaxDelay) / 2; + } + + public boolean hasRespawnRandom() + { + return _respawnMinDelay != _respawnMaxDelay; + } + + public void setChaseRange(int chaseRange) + { + _chaseRange = chaseRange; + } + + public int getChaseRange() + { + return _chaseRange; + } + + public void setSpawnTerritory(NpcSpawnTerritory territory) + { + _spawnTerritory = territory; + } + + public NpcSpawnTerritory getSpawnTerritory() + { + return _spawnTerritory; + } + + public boolean isTerritoryBased() + { + return (_spawnTerritory != null) && (getX() == 0) && (getY() == 0); + } + + public Npc getLastSpawn() + { + return _spawnedNpcs.peekLast(); + } + + public Deque getSpawnedNpcs() + { + return _spawnedNpcs; + } + + public void respawnNpc(Npc oldNpc) + { + if (_doRespawn) + { + // oldNpc.refreshId(); + initializeNpc(oldNpc); + } + } + + public NpcTemplate getTemplate() + { + return _template; + } + + public boolean getRandomWalking() + { + return _randomWalk; + } + + public void setRandomWalking(boolean value) + { + _randomWalk = value; + } + + public void setSpawnTemplateId(int npcSpawnTemplateId) + { + _spawnTemplateId = npcSpawnTemplateId; + } + + public int getNpcSpawnTemplateId() + { + return _spawnTemplateId; + } + + public Location getSpawnLocation() + { + return _spawnLocation; + } + + @Override + public String toString() + { + return "Spawn ID: " + _template.getId() + " X: " + getX() + " Y: " + getY() + " Z: " + getZ() + " Heading: " + getHeading(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/spawns/SpawnGroup.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/spawns/SpawnGroup.java new file mode 100644 index 0000000..8822dd6 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/spawns/SpawnGroup.java @@ -0,0 +1,282 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.spawns; + +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Logger; + +import org.l2jmobius.commons.util.Rnd; +import org.l2jmobius.gameserver.taskmanagers.SpawnGroupTaskManager; + +/** + * A group of NPC spawn entries sharing an entry selection policy, the runtime equivalent of a retail npcmaker.
        + * Reservations are made under the group monitor and the actual spawning happens outside of it. + * @author Altur + */ +public class SpawnGroup +{ + private static final Logger LOGGER = Logger.getLogger(SpawnGroup.class.getName()); + + private final String _name; + private final SpawnSelection _selection; + private final int _maximumNpc; + private final List _entries = new ArrayList<>(); + private int _aliveOrReserved = 0; + private boolean _active = true; + + public SpawnGroup(String name, SpawnSelection selection, int maximumNpc) + { + _name = name; + _selection = selection; + _maximumNpc = maximumNpc; + } + + public String getName() + { + return _name; + } + + public SpawnSelection getSelection() + { + return _selection; + } + + public int getMaximumNpc() + { + return _maximumNpc; + } + + public List getEntries() + { + return _entries; + } + + public void addEntry(SpawnGroupEntry entry) + { + _entries.add(entry); + } + + public synchronized int getAliveOrReserved() + { + return _aliveOrReserved; + } + + /** + * Reserves the given amount of units on an entry, honoring the entry quota and the group cap.
        + * The group cap is deliberately not checked for RANDOM selection, retail random_spawn never reads it. + * @param entry the entry to reserve on + * @param count the amount of units to reserve + * @return {@code true} when the reservation was made, {@code false} when quota or cap would be exceeded + */ + public synchronized boolean tryReserve(SpawnGroupEntry entry, int count) + { + if ((_selection != SpawnSelection.RANDOM) && (_maximumNpc > 0) && ((_aliveOrReserved + count) > _maximumNpc)) + { + return false; + } + + if ((entry.getAliveOrReserved() + count) > entry.getTotal()) + { + return false; + } + + entry.setAliveOrReserved(entry.getAliveOrReserved() + count); + _aliveOrReserved += count; + return true; + } + + /** + * Releases previously reserved units, used when the actual spawn could not be created. + * @param entry the entry to release from + * @param count the amount of units to release + */ + public synchronized void release(SpawnGroupEntry entry, int count) + { + entry.setAliveOrReserved(entry.getAliveOrReserved() - count); + _aliveOrReserved -= count; + } + + /** + * Performs the initial spawn of the group according to its selection policy. + * @return the number of NPC units spawned + */ + public int initialSpawn() + { + if (_entries.isEmpty()) + { + LOGGER.warning(getClass().getSimpleName() + ": " + _name + " has no valid entries to spawn."); + return 0; + } + + int spawned = 0; + switch (_selection) + { + case ALL: + { + for (SpawnGroupEntry entry : _entries) + { + if (tryReserve(entry, entry.getTotal())) + { + spawned += spawnUnits(entry, entry.getTotal()); + } + } + break; + } + case RANDOM: + { + // A single roll over the entry index, the whole quota of the rolled entry spawns. Nothing is filled up and the cap is not read. + final SpawnGroupEntry entry = _entries.get(Rnd.get(_entries.size())); + if (tryReserve(entry, entry.getTotal())) + { + spawned += spawnUnits(entry, entry.getTotal()); + } + break; + } + case RANDOM_FILL: + { + // The cap is the roll counter. Rolls use replacement and no retry, a roll into a full entry is simply lost. + for (int i = 0; i < _maximumNpc; i++) + { + final SpawnGroupEntry entry = _entries.get(Rnd.get(_entries.size())); + if (tryReserve(entry, 1)) + { + spawned += spawnUnits(entry, 1); + } + } + break; + } + } + + return spawned; + } + + /** + * Handles the death of a unit of this group.
        + * A new entry is rolled ignoring which one died; when the rolled entry is at its quota nothing is replaced this cycle. + * @param spawn the spawn slot the dead unit belonged to + */ + public void onUnitDeath(Spawn spawn) + { + if (_selection == SpawnSelection.ALL) + { + // ALL slots respawn themselves with their fixed species, the unit stays reserved. + return; + } + + final SpawnGroupEntry deadEntry = getEntryBySlot(spawn); + if (deadEntry == null) + { + return; + } + + synchronized (this) + { + deadEntry.setAliveOrReserved(deadEntry.getAliveOrReserved() - 1); + _aliveOrReserved--; + } + + if (!_active) + { + // Deactivated groups keep their census truthful but stop replacing dead units. + return; + } + + final SpawnGroupEntry rolled = _entries.get(Rnd.get(_entries.size())); + if (tryReserve(rolled, 1)) + { + // Reserve now, materialize later. The delay comes from the rolled entry, not from the dead one. + final int respawnDelay = rolled.getRespawnDelay(); + final int respawnRandom = rolled.getRespawnRandom(); + final int minDelay = Math.max(1, respawnDelay - respawnRandom) * 1000; + final int maxDelay = Math.max(1, respawnDelay + respawnRandom) * 1000; + SpawnGroupTaskManager.getInstance().add(this, rolled, System.currentTimeMillis() + (respawnRandom > 0 ? Rnd.get(minDelay, maxDelay) : minDelay)); + } + } + + public boolean isActive() + { + return _active; + } + + /** + * Enables or disables the replacement of dead units, used by the unspawn admin commands. + * @param active {@code false} to stop replacing dead units + */ + public void setActive(boolean active) + { + _active = active; + } + + /** + * @param spawn the spawn slot to look up + * @return the entry owning the given slot, or {@code null} when it belongs to another group + */ + public SpawnGroupEntry getEntryBySlot(Spawn spawn) + { + for (SpawnGroupEntry entry : _entries) + { + for (Spawn slot : entry.getSlots()) + { + // Identity, not Location equality: slot coordinates mutate and can coincide. + if (slot == spawn) + { + return entry; + } + } + } + + return null; + } + + /** + * Materializes the given amount of already reserved units of an entry, rolling a territory slot per unit. + * @param entry the entry to spawn from + * @param count the amount of units to spawn + * @return the number of NPC units spawned + */ + public int spawnUnits(SpawnGroupEntry entry, int count) + { + if (!_active) + { + // A pending replacement dequeued while the group was being stopped must not materialize. + release(entry, count); + return 0; + } + + int spawned = 0; + final List slots = entry.getSlots(); + for (int i = 0; i < count; i++) + { + final Spawn slot = slots.get(Rnd.get(slots.size())); + if (slot.doSpawn(false) == null) + { + release(entry, 1); + } + else + { + spawned++; + } + } + + return spawned; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/spawns/SpawnGroupEntry.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/spawns/SpawnGroupEntry.java new file mode 100644 index 0000000..318c594 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/spawns/SpawnGroupEntry.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.spawns; + +import java.util.ArrayList; +import java.util.List; + +/** + * A single NPC entry of a spawn group, holding its quota and one spawn slot per territory. + * @author Altur + */ +public class SpawnGroupEntry +{ + private final int _total; + private final int _respawnDelay; + private final int _respawnRandom; + private final List _slots = new ArrayList<>(); + private int _aliveOrReserved = 0; + + public SpawnGroupEntry(int total, int respawnDelay, int respawnRandom) + { + _total = total; + _respawnDelay = respawnDelay; + _respawnRandom = respawnRandom; + } + + public int getTotal() + { + return _total; + } + + public int getRespawnDelay() + { + return _respawnDelay; + } + + public int getRespawnRandom() + { + return _respawnRandom; + } + + public List getSlots() + { + return _slots; + } + + public void addSlot(Spawn spawn) + { + _slots.add(spawn); + } + + public int getAliveOrReserved() + { + return _aliveOrReserved; + } + + public void setAliveOrReserved(int count) + { + _aliveOrReserved = count; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/spawns/SpawnSelection.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/spawns/SpawnSelection.java new file mode 100644 index 0000000..a840e4c --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/spawns/SpawnSelection.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.spawns; + +/** + * Entry selection policy of a spawn group.
        + * ALL spawns every entry, RANDOM rolls a single entry, RANDOM_FILL rolls maximumNpc times with replacement. + * @author Altur + */ +public enum SpawnSelection +{ + ALL, + RANDOM, + RANDOM_FILL +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/teleporter/TeleportHolder.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/teleporter/TeleportHolder.java new file mode 100644 index 0000000..8fa689b --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/teleporter/TeleportHolder.java @@ -0,0 +1,317 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.teleporter; + +import java.util.ArrayList; +import java.util.Calendar; +import java.util.List; +import java.util.logging.Logger; + +import org.l2jmobius.gameserver.config.PlayerConfig; +import org.l2jmobius.gameserver.data.xml.ItemData; +import org.l2jmobius.gameserver.entity.actor.Npc; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.enums.player.TeleportType; +import org.l2jmobius.gameserver.entity.item.ItemTemplate; +import org.l2jmobius.gameserver.entity.item.enums.ItemProcessType; +import org.l2jmobius.gameserver.entity.itemcontainer.Inventory; +import org.l2jmobius.gameserver.managers.CastleManager; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.serverpackets.NpcHtmlMessage; +import org.l2jmobius.gameserver.util.StatSet; + +/** + * Teleport holder + * @author UnAfraid + */ +public class TeleportHolder +{ + private static final Logger LOGGER = Logger.getLogger(TeleportHolder.class.getName()); + + private final String _name; + private final TeleportType _type; + private final List _teleportData = new ArrayList<>(); + + /** + * Constructor + * @param name name of teleport list + * @param type type of teleport list + */ + public TeleportHolder(String name, TeleportType type) + { + _name = name; + _type = type; + } + + /** + * Gets list identification (name). + * @return list name + */ + public String getName() + { + return _name; + } + + /** + * Gets type of teleport list. + * @return type of list + */ + public TeleportType getType() + { + return _type; + } + + /** + * Create new teleport location in this holder. + * @param locData information about teleport location + */ + public void registerLocation(StatSet locData) + { + _teleportData.add(new TeleportLocation(_teleportData.size(), locData)); + } + + /** + * Gets teleport location with specific index. + * @param locationId index of location (begins with {@code 0}) + * @return instance of {@link TeleportLocation} if found otherwise {@code null} + */ + public TeleportLocation getLocation(int locationId) + { + return _teleportData.get(locationId); + } + + /** + * Gets all teleport locations registered in current holder. + * @return collection of {@link TeleportLocation} + */ + public List getLocations() + { + return _teleportData; + } + + /** + * Build HTML message from teleport list and send it to player. + * @param player receiver of HTML message + * @param npc teleporter + */ + public void showTeleportList(Player player, Npc npc) + { + showTeleportList(player, npc, "npc_" + npc.getObjectId() + "_teleport"); + } + + /** + * Build HTML message from teleport list and send it to player. + * @param player receiver of HTML message + * @param npc teleporter + * @param bypass bypass used while building message + */ + public void showTeleportList(Player player, Npc npc, String bypass) + { + if (isNoblesse() && !player.isNoble()) + { + LOGGER.warning(player + " requested noblesse teleport without being noble!"); + return; + } + + // Build html + final StringBuilder sb = new StringBuilder(); + final StringBuilder sbF = new StringBuilder(); + for (TeleportLocation loc : _teleportData) + { + String finalName = loc.getName(); + String confirmDesc = loc.getName(); + if (shouldPayFee(player, loc)) + { + final long fee = calculateFee(player, loc); + if (fee != 0) + { + finalName += " - " + fee + " " + getItemName(loc.getFeeId()); + } + } + + sb.append("" + finalName + ""); + } + sbF.append(sb.toString()); + + // Send html message + final NpcHtmlMessage msg = new NpcHtmlMessage(npc.getObjectId()); + msg.setFile(player, "data/html/teleporter/teleports.htm"); + msg.replace("%locations%", sbF.toString()); + player.sendPacket(msg); + } + + /** + * Teleports player to final location + * @param player player being teleported + * @param npc teleporter + * @param locId destination + */ + public void doTeleport(Player player, Npc npc, int locId) + { + // Check if player is dead before other checks. + if (player.isAlikeDead()) + { + return; + } + + if (isNoblesse() && !player.isNoble()) + { + LOGGER.warning(player + " requested noblesse teleport without being noble!"); + return; + } + + final TeleportLocation loc = getLocation(locId); + if (loc == null) + { + LOGGER.warning(player + " requested unknown teleport location " + locId + " within list " + _name + "!"); + return; + } + + // Check if castle is in siege. + if (!PlayerConfig.TELEPORT_WHILE_SIEGE_IN_PROGRESS) + { + for (int castleId : loc.getCastleId()) + { + if (CastleManager.getInstance().getCastleById(castleId).getSiege().isInProgress()) + { + player.sendPacket(SystemMessageId.YOU_CANNOT_TELEPORT_TO_A_VILLAGE_THAT_IS_IN_A_SIEGE); + return; + } + } + } + + // Validate conditions for NORMAL teleport. + if (isNormalTeleport()) + { + if (!PlayerConfig.TELEPORT_WHILE_SIEGE_IN_PROGRESS && npc.getCastle().getSiege().isInProgress()) + { + final NpcHtmlMessage msg = new NpcHtmlMessage(npc.getObjectId()); + msg.setFile(player, "data/html/teleporter/castleteleporter-busy.htm"); + msg.replace("%npcname%", npc.getName()); + player.sendPacket(msg); + return; + } + else if (!PlayerConfig.ALT_GAME_KARMA_PLAYER_CAN_USE_GK && (player.getKarma() > 0)) + { + player.sendMessage("Go away, you're not welcome here."); + return; + } + } + + // Check if player should pay fee. + if (shouldPayFee(player, loc) && !player.destroyItemByItemId(ItemProcessType.FEE, loc.getFeeId(), calculateFee(player, loc), npc, true)) + { + if (loc.getFeeId() == Inventory.ADENA_ID) + { + player.sendPacket(SystemMessageId.YOU_DO_NOT_HAVE_ENOUGH_ADENA); + } + else + { + player.sendMessage("You do not have enough " + getItemName(loc.getFeeId()) + "."); + } + return; + } + + player.teleToLocation(loc); + } + + /** + * Check if player have to play fee or not. + * @param player player which request teleport + * @param loc location where player should be teleported + * @return {@code true} when all requirements are met otherwise {@code false} + */ + private boolean shouldPayFee(Player player, TeleportLocation loc) + { + return !isNormalTeleport() || (((player.getLevel() > PlayerConfig.MAX_FREE_TELEPORT_LEVEL) || player.isSubClassActive()) && ((loc.getFeeId() != 0) && (loc.getFeeCount() > 0))); + } + + /** + * Calculate fee amount for requested teleport.
        + * For characters below level 40 teleport service is free.
        + * From 8.00 pm to 00.00 from Monday till Tuesday for all characters there's a 50% discount on teleportation services + * @param player player which request teleport + * @param loc location where player should be teleported + * @return fee amount + */ + private int calculateFee(Player player, TeleportLocation loc) + { + if (isNormalTeleport()) + { + if (!player.isSubClassActive() && (player.getLevel() <= PlayerConfig.MAX_FREE_TELEPORT_LEVEL)) + { + return 0; + } + + final Calendar cal = Calendar.getInstance(); + final int hour = cal.get(Calendar.HOUR_OF_DAY); + final int dayOfWeek = cal.get(Calendar.DAY_OF_WEEK); + if ((hour >= 20) && ((dayOfWeek >= Calendar.MONDAY) && (dayOfWeek <= Calendar.TUESDAY))) + { + return loc.getFeeCount() / 2; + } + } + + return loc.getFeeCount(); + } + + private boolean isNormalTeleport() + { + return _type == TeleportType.NORMAL; + } + + /** + * Check if teleport list is for noblesse or not. + * @return {@code true} if is for noblesse otherwise {@code false} + */ + public boolean isNoblesse() + { + return (_type == TeleportType.NOBLES_ADENA) || (_type == TeleportType.NOBLES_TOKEN); + } + + /** + * Gets name of specified item. + * @param itemId template id of item + * @return item name + */ + private String getItemName(int itemId) + { + final ItemTemplate item = ItemData.getInstance().getTemplate(itemId); + if (item != null) + { + return item.getName(); + } + + switch (itemId) + { + case -100: // PC_CAFE_POINTS + { + return "Player Commendation Points"; + } + case -200: // CLAN_REPUTATION + { + return "Clan Reputation Points"; + } + case -300: // FAME + { + return "Fame"; + } + } + + return "Unknown item: " + itemId; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/teleporter/TeleportLocation.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/teleporter/TeleportLocation.java new file mode 100644 index 0000000..d0c643a --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/teleporter/TeleportLocation.java @@ -0,0 +1,89 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.teleporter; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.l2jmobius.gameserver.entity.Location; +import org.l2jmobius.gameserver.entity.itemcontainer.Inventory; +import org.l2jmobius.gameserver.util.StatSet; + +/** + * @author UnAfraid + */ +public class TeleportLocation extends Location +{ + private final int _id; + private final String _name; + private final int _feeId; + private final int _feeCount; + private final List _castleId; + + public TeleportLocation(int id, StatSet set) + { + super(set); + _id = id; + _name = set.getString("name", null); + _feeId = set.getInt("feeId", Inventory.ADENA_ID); + _feeCount = set.getInt("feeCount", 0); + + final String castleIds = set.getString("castleId", ""); + if (castleIds.isEmpty()) + { + _castleId = Collections.emptyList(); + } + else if (!castleIds.contains(";")) + { + _castleId = Collections.singletonList(Integer.parseInt(castleIds)); + } + else + { + _castleId = new ArrayList<>(); + for (String castleId : castleIds.split(";")) + { + _castleId.add(Integer.parseInt(castleId)); + } + } + } + + public int getId() + { + return _id; + } + + public String getName() + { + return _name; + } + + public int getFeeId() + { + return _feeId; + } + + public int getFeeCount() + { + return _feeCount; + } + + public List getCastleId() + { + return _castleId; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/AbstractZoneSettings.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/AbstractZoneSettings.java new file mode 100644 index 0000000..fc4e174 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/AbstractZoneSettings.java @@ -0,0 +1,29 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.zone; + +/** + * Abstract class for Zone settings. + * @author UnAfraid + */ +public abstract class AbstractZoneSettings +{ + /** + * Clear the Zone settings. + */ + public abstract void clear(); +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/ZoneForm.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/ZoneForm.java new file mode 100644 index 0000000..812c71c --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/ZoneForm.java @@ -0,0 +1,62 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.zone; + +import org.l2jmobius.gameserver.entity.Location; +import org.l2jmobius.gameserver.entity.item.instance.Item; +import org.l2jmobius.gameserver.managers.IdManager; +import org.l2jmobius.gameserver.managers.ZoneManager; + +/** + * Abstract base class for any zone form + * @author durgus + */ +public abstract class ZoneForm +{ + protected static final int STEP = 10; + + public abstract boolean isInsideZone(int x, int y, int z); + + public abstract boolean intersectsRectangle(int x1, int x2, int y1, int y2); + + public abstract double getDistanceToZone(int x, int y); + + public abstract int getLowZ(); // Support for the ability to extract the z coordinates of zones. + + public abstract int getHighZ(); // New fishing patch makes use of that to get the Z for the hook. + + // landing coordinates. + + protected boolean lineSegmentsIntersect(int ax1, int ay1, int ax2, int ay2, int bx1, int by1, int bx2, int by2) + { + return java.awt.geom.Line2D.linesIntersect(ax1, ay1, ax2, ay2, bx1, by1, bx2, by2); + } + + public abstract void visualizeZone(int z); + + protected void dropDebugItem(int itemId, int num, int x, int y, int z) + { + final Item item = new Item(IdManager.getInstance().getNextId(), itemId); + item.setCount(num); + item.spawnMe(x, y, z + 5); + ZoneManager.getInstance().getDebugItems().add(item); + } + + public abstract Location getRandomPoint(); + + public abstract Location getCenterPoint(); +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/ZoneId.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/ZoneId.java new file mode 100644 index 0000000..1dfecaf --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/ZoneId.java @@ -0,0 +1,52 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.zone; + +/** + * Zone Ids. + * @author Zoey76 + */ +public enum ZoneId +{ + PVP, + PEACE, + SIEGE, + MOTHER_TREE, + CLAN_HALL, + NO_LANDING, + WATER, + JAIL, + MONSTER_TRACK, + CASTLE, + SWAMP, + NO_SUMMON_FRIEND, + NO_STORE, + NO_PVP, + TOWN, + SCRIPT, + HQ, + DANGER_AREA, + ALTERED, + NO_BOOKMARK, + NO_ITEM_DROP, + NO_RESTART; + + public static int getZoneCount() + { + return values().length; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/ZoneRegion.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/ZoneRegion.java new file mode 100644 index 0000000..ab55df9 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/ZoneRegion.java @@ -0,0 +1,141 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.zone; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.zone.type.PeaceZone; +import org.l2jmobius.gameserver.mechanics.skill.Skill; + +/** + * @author NosBit + */ +public class ZoneRegion +{ + private final int _regionX; + private final int _regionY; + private final Map _zones = new ConcurrentHashMap<>(); + + public ZoneRegion(int regionX, int regionY) + { + _regionX = regionX; + _regionY = regionY; + } + + public Map getZones() + { + return _zones; + } + + public int getRegionX() + { + return _regionX; + } + + public int getRegionY() + { + return _regionY; + } + + public void revalidateZones(Creature creature) + { + // Do NOT update the world region while the character is still in the process of teleporting. + // Once the teleport is COMPLETED, revalidation occurs safely, at that time. + if (creature.isTeleporting()) + { + return; + } + + for (ZoneType z : _zones.values()) + { + z.revalidateInZone(creature); + } + } + + public void removeFromZones(Creature creature) + { + for (ZoneType z : _zones.values()) + { + z.removeCharacter(creature); + } + } + + public boolean checkEffectRangeInsidePeaceZone(Skill skill, int x, int y, int z) + { + final int range = skill.getEffectRange(); + final int up = y + range; + final int down = y - range; + final int left = x + range; + final int right = x - range; + for (ZoneType e : _zones.values()) + { + if (e instanceof PeaceZone) + { + if (e.isInsideZone(x, up, z)) + { + return false; + } + + if (e.isInsideZone(x, down, z)) + { + return false; + } + + if (e.isInsideZone(left, y, z)) + { + return false; + } + + if (e.isInsideZone(right, y, z)) + { + return false; + } + + if (e.isInsideZone(x, y, z)) + { + return false; + } + } + } + + return true; + } + + public void onDeath(Creature creature) + { + for (ZoneType z : _zones.values()) + { + if (z.isInsideZone(creature)) + { + z.onDieInside(creature); + } + } + } + + public void onRevive(Creature creature) + { + for (ZoneType z : _zones.values()) + { + if (z.isInsideZone(creature)) + { + z.onReviveInside(creature); + } + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/ZoneRespawn.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/ZoneRespawn.java new file mode 100644 index 0000000..ba61002 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/ZoneRespawn.java @@ -0,0 +1,174 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.zone; + +import java.util.ArrayList; +import java.util.List; + +import org.l2jmobius.commons.util.Rnd; +import org.l2jmobius.gameserver.config.PlayerConfig; +import org.l2jmobius.gameserver.entity.Location; + +/** + * Abstract zone with spawn locations + * @author DS, Nyaran + */ +public abstract class ZoneRespawn extends ZoneType +{ + private List _spawnLocs = null; + private List _otherSpawnLocs = null; + private List _chaoticSpawnLocs = null; + private List _banishSpawnLocs = null; + + protected ZoneRespawn(int id) + { + super(id); + } + + public void parseLoc(int x, int y, int z, String type) + { + if ((type == null) || type.isEmpty()) + { + addSpawn(x, y, z); + } + else + { + switch (type) + { + case "other": + { + addOtherSpawn(x, y, z); + break; + } + case "chaotic": + { + addChaoticSpawn(x, y, z); + break; + } + case "banish": + { + addBanishSpawn(x, y, z); + break; + } + default: + { + LOGGER.warning(getClass().getSimpleName() + ": Unknown location type: " + type); + } + } + } + } + + public void addSpawn(int x, int y, int z) + { + if (_spawnLocs == null) + { + _spawnLocs = new ArrayList<>(); + } + + _spawnLocs.add(new Location(x, y, z)); + } + + public void addOtherSpawn(int x, int y, int z) + { + if (_otherSpawnLocs == null) + { + _otherSpawnLocs = new ArrayList<>(); + } + + _otherSpawnLocs.add(new Location(x, y, z)); + } + + public void addChaoticSpawn(int x, int y, int z) + { + if (_chaoticSpawnLocs == null) + { + _chaoticSpawnLocs = new ArrayList<>(); + } + + _chaoticSpawnLocs.add(new Location(x, y, z)); + } + + public void addBanishSpawn(int x, int y, int z) + { + if (_banishSpawnLocs == null) + { + _banishSpawnLocs = new ArrayList<>(); + } + + _banishSpawnLocs.add(new Location(x, y, z)); + } + + public List getSpawns() + { + return _spawnLocs; + } + + public Location getSpawnLoc() + { + if (PlayerConfig.RANDOM_RESPAWN_IN_TOWN_ENABLED) + { + return _spawnLocs.get(Rnd.get(_spawnLocs.size())); + } + + return _spawnLocs.get(0); + } + + public Location getOtherSpawnLoc() + { + if (_otherSpawnLocs != null) + { + if (PlayerConfig.RANDOM_RESPAWN_IN_TOWN_ENABLED) + { + return _otherSpawnLocs.get(Rnd.get(_otherSpawnLocs.size())); + } + + return _otherSpawnLocs.get(0); + } + + return getSpawnLoc(); + } + + public Location getChaoticSpawnLoc() + { + if (_chaoticSpawnLocs != null) + { + if (PlayerConfig.RANDOM_RESPAWN_IN_TOWN_ENABLED) + { + return _chaoticSpawnLocs.get(Rnd.get(_chaoticSpawnLocs.size())); + } + + return _chaoticSpawnLocs.get(0); + } + + return getSpawnLoc(); + } + + public Location getBanishSpawnLoc() + { + if (_banishSpawnLocs != null) + { + if (PlayerConfig.RANDOM_RESPAWN_IN_TOWN_ENABLED) + { + return _banishSpawnLocs.get(Rnd.get(_banishSpawnLocs.size())); + } + + return _banishSpawnLocs.get(0); + } + + return getSpawnLoc(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/ZoneType.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/ZoneType.java new file mode 100644 index 0000000..28b622d --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/ZoneType.java @@ -0,0 +1,590 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.zone; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Logger; + +import org.l2jmobius.gameserver.entity.WorldObject; +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.enums.creature.InstanceType; +import org.l2jmobius.gameserver.entity.actor.enums.player.TeleportWhereType; +import org.l2jmobius.gameserver.entity.instancezone.InstanceWorld; +import org.l2jmobius.gameserver.interfaces.ILocational; +import org.l2jmobius.gameserver.managers.InstanceManager; +import org.l2jmobius.gameserver.mechanics.events.EventDispatcher; +import org.l2jmobius.gameserver.mechanics.events.EventType; +import org.l2jmobius.gameserver.mechanics.events.ListenersContainer; +import org.l2jmobius.gameserver.mechanics.events.holders.actor.creature.OnCreatureZoneEnter; +import org.l2jmobius.gameserver.mechanics.events.holders.actor.creature.OnCreatureZoneExit; +import org.l2jmobius.gameserver.network.serverpackets.ServerPacket; + +/** + * Abstract base class for any zone type handles basic operations. + * @author durgus + */ +public abstract class ZoneType extends ListenersContainer +{ + protected static final Logger LOGGER = Logger.getLogger(ZoneType.class.getName()); + + private final int _id; + protected ZoneForm _zone; + private final Map _characterList = new ConcurrentHashMap<>(); + + /** Parameters to affect specific characters */ + protected boolean _checkAffected = false; + private String _name = null; + private int _minLevel; + private int _maxLevel; + private int[] _race; + private int[] _class; + private char _classType; + private InstanceType _target = InstanceType.Creature; // Default all chars. + private boolean _allowStore; + private boolean _enabled; + private AbstractZoneSettings _settings; + private int _instanceTemplateId = -1; + private int _instanceId = -1; + + protected ZoneType(int id) + { + _id = id; + _minLevel = 0; + _maxLevel = 0xFF; + _classType = 0; + _race = null; + _class = null; + _allowStore = true; + _enabled = true; + } + + /** + * @return Returns the id. + */ + public int getId() + { + return _id; + } + + /** + * Setup new parameters for this zone + * @param name + * @param value + */ + public void setParameter(String name, String value) + { + _checkAffected = true; + + // Zone name + if (name.equals("name")) + { + _name = value; + } + // Minimum level + else if (name.equals("affectedLvlMin")) + { + _minLevel = Integer.parseInt(value); + } + // Maximum level + else if (name.equals("affectedLvlMax")) + { + _maxLevel = Integer.parseInt(value); + } + // Affected Races + else if (name.equals("affectedRace")) + { + // Create a new array holding the affected race. + if (_race == null) + { + _race = new int[1]; + _race[0] = Integer.parseInt(value); + } + else + { + final int[] temp = new int[_race.length + 1]; + int i = 0; + for (; i < _race.length; i++) + { + temp[i] = _race[i]; + } + + temp[i] = Integer.parseInt(value); + _race = temp; + } + } + // Affected classes + else if (name.equals("affectedClassId")) + { + // Create a new array holding the affected classIds. + if (_class == null) + { + _class = new int[1]; + _class[0] = Integer.parseInt(value); + } + else + { + final int[] temp = new int[_class.length + 1]; + int i = 0; + for (; i < _class.length; i++) + { + temp[i] = _class[i]; + } + + temp[i] = Integer.parseInt(value); + _class = temp; + } + } + // Affected class type + else if (name.equals("affectedClassType")) + { + if (value.equals("Fighter")) + { + _classType = 1; + } + else + { + _classType = 2; + } + } + else if (name.equals("targetClass")) + { + _target = Enum.valueOf(InstanceType.class, value); + } + else if (name.equals("allowStore")) + { + _allowStore = Boolean.parseBoolean(value); + } + else if (name.equals("default_enabled")) + { + _enabled = Boolean.parseBoolean(value); + } + else if (name.equals("instanceId")) + { + _instanceTemplateId = Integer.parseInt(value); + } + else + { + LOGGER.info(getClass().getSimpleName() + ": Unknown parameter - " + name + " in zone: " + _id); + } + } + + /** + * @param creature the player to verify. + * @return {@code true} if the given character is affected by this zone, {@code false} otherwise. + */ + protected boolean isAffected(Creature creature) + { + // Check instance + if (_instanceTemplateId > 0) + { + final InstanceWorld world = InstanceManager.getInstance().getWorld(creature); + if ((world != null) && (world.getTemplateId() != _instanceTemplateId)) + { + return false; + } + } + + if (!isEnabled()) + { + return false; + } + + // Check level + if ((creature.getLevel() < _minLevel) || (creature.getLevel() > _maxLevel)) + { + return false; + } + + // Check obj class. + if (!creature.isInstanceTypes(_target)) + { + return false; + } + + if (creature.isPlayer()) + { + // Check class type. + if (_classType != 0) + { + if (creature.asPlayer().isMageClass()) + { + if (_classType == 1) + { + return false; + } + } + else if (_classType == 2) + { + return false; + } + } + + // Check race + if (_race != null) + { + boolean ok = false; + for (int element : _race) + { + if (creature.getRace().ordinal() == element) + { + ok = true; + break; + } + } + + if (!ok) + { + return false; + } + } + + // Check class + if (_class != null) + { + boolean ok = false; + for (int _clas : _class) + { + if (creature.asPlayer().getPlayerClass().getId() == _clas) + { + ok = true; + break; + } + } + + if (!ok) + { + return false; + } + } + } + + return true; + } + + /** + * Set the zone for this ZoneType Instance + * @param zone + */ + public void setZone(ZoneForm zone) + { + if (_zone != null) + { + throw new IllegalStateException("Zone already set"); + } + + _zone = zone; + } + + /** + * Returns this zones zone form. + * @return {@link #_zone} + */ + public ZoneForm getZone() + { + return _zone; + } + + /** + * Set the zone name. + * @param name + */ + public void setName(String name) + { + _name = name; + } + + /** + * Returns zone name + * @return + */ + public String getName() + { + return _name; + } + + /** + * Set the zone instanceId. + * @param instanceId + */ + public void setInstanceId(int instanceId) + { + _instanceId = instanceId; + } + + /** + * Returns zone instanceId + * @return + */ + public int getInstanceId() + { + return _instanceId; + } + + /** + * Set the zone instance Template Id . + * @param instanceTemplateId + */ + public void setInstanceTemplateId(int instanceTemplateId) + { + _instanceTemplateId = instanceTemplateId; + } + + /** + * Returns zone instance TemplateId + * @return + */ + public int getInstanceTemplateId() + { + return _instanceTemplateId; + } + + /** + * Checks if the given coordinates are within zone's plane + * @param x + * @param y + * @return + */ + public boolean isInsideZone(int x, int y) + { + return isInsideZone(x, y, _zone.getHighZ()); + } + + /** + * Checks if the given coordinates are within the zone, ignores instanceId check + * @param loc + * @return + */ + public boolean isInsideZone(ILocational loc) + { + return isInsideZone(loc.getX(), loc.getY(), loc.getZ()); + } + + /** + * Checks if the given coordinates are within the zone, ignores instanceId check + * @param x + * @param y + * @param z + * @return + */ + public boolean isInsideZone(int x, int y, int z) + { + return _zone.isInsideZone(x, y, z); + } + + public double getDistanceToZone(int x, int y) + { + return _zone.getDistanceToZone(x, y); + } + + public double getDistanceToZone(WorldObject object) + { + return _zone.getDistanceToZone(object.getX(), object.getY()); + } + + public void revalidateInZone(Creature creature) + { + // If the object is inside the zone... + if (isInsideZone(creature)) + { + // If the character can't be affected by this zone return. + if (_checkAffected && !isAffected(creature)) + { + return; + } + + if (_characterList.putIfAbsent(creature.getObjectId(), creature) == null) + { + // Notify to scripts. + if (EventDispatcher.getInstance().hasListener(EventType.ON_CREATURE_ZONE_ENTER, this)) + { + EventDispatcher.getInstance().notifyEventAsync(new OnCreatureZoneEnter(creature, this), this); + } + + // Notify Zone implementation. + onEnter(creature); + } + } + else + { + removeCharacter(creature); + } + } + + /** + * Force fully removes a character from the zone Should use during teleport / logoff + * @param creature + */ + public void removeCharacter(Creature creature) + { + // Was the character inside this zone? + if (_characterList.containsKey(creature.getObjectId())) + { + // Notify to scripts. + if (EventDispatcher.getInstance().hasListener(EventType.ON_CREATURE_ZONE_EXIT, this)) + { + EventDispatcher.getInstance().notifyEventAsync(new OnCreatureZoneExit(creature, this), this); + } + + // Unregister player. + _characterList.remove(creature.getObjectId()); + + // Notify Zone implementation. + onExit(creature); + } + } + + /** + * Will scan the zones char list for the character + * @param creature + * @return + */ + public boolean isCharacterInZone(Creature creature) + { + return _characterList.containsKey(creature.getObjectId()); + } + + public AbstractZoneSettings getSettings() + { + return _settings; + } + + public void setSettings(AbstractZoneSettings settings) + { + if (_settings != null) + { + _settings.clear(); + } + + _settings = settings; + } + + protected abstract void onEnter(Creature creature); + + protected abstract void onExit(Creature creature); + + public void onDieInside(Creature creature) + { + } + + public void onReviveInside(Creature creature) + { + } + + public void onPlayerLoginInside(Player player) + { + } + + public void onPlayerLogoutInside(Player player) + { + } + + public Collection getCharactersInside() + { + return _characterList.values(); + } + + public List getPlayersInside() + { + final List players = new ArrayList<>(); + for (Creature ch : _characterList.values()) + { + if ((ch != null) && ch.isPlayer()) + { + players.add(ch.asPlayer()); + } + } + + return players; + } + + /** + * Broadcasts packet to all players inside the zone + * @param packet + */ + public void broadcastPacket(ServerPacket packet) + { + if (_characterList.isEmpty()) + { + return; + } + + for (Creature creature : _characterList.values()) + { + if ((creature != null) && creature.isPlayer()) + { + creature.sendPacket(packet); + } + } + } + + public InstanceType getTargetType() + { + return _target; + } + + public void setTargetType(InstanceType type) + { + _target = type; + _checkAffected = true; + } + + public boolean getAllowStore() + { + return _allowStore; + } + + @Override + public String toString() + { + return getClass().getSimpleName() + "[" + _id + "]"; + } + + public void visualizeZone(int z) + { + _zone.visualizeZone(z); + } + + public void setEnabled(boolean value) + { + _enabled = value; + } + + public boolean isEnabled() + { + return _enabled; + } + + public void oustAllPlayers() + { + for (Creature obj : _characterList.values()) + { + if ((obj != null) && obj.isPlayer()) + { + final Player player = obj.asPlayer(); + if (player.isOnline()) + { + player.teleToLocation(TeleportWhereType.TOWN); + } + } + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/form/ZoneCuboid.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/form/ZoneCuboid.java new file mode 100644 index 0000000..34d5c05 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/form/ZoneCuboid.java @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.zone.form; + +import java.awt.Rectangle; + +import org.l2jmobius.commons.util.Rnd; +import org.l2jmobius.gameserver.entity.Location; +import org.l2jmobius.gameserver.entity.itemcontainer.Inventory; +import org.l2jmobius.gameserver.entity.zone.ZoneForm; +import org.l2jmobius.gameserver.geoengine.GeoEngine; + +/** + * A primitive rectangular zone + * @author durgus, Mobius + */ +public class ZoneCuboid extends ZoneForm +{ + private final int _z1; + private final int _z2; + private final Rectangle _r; + private final Location _centerPoint; + + public ZoneCuboid(int x1, int x2, int y1, int y2, int z1, int z2) + { + final int _x1 = Math.min(x1, x2); + final int _x2 = Math.max(x1, x2); + final int _y1 = Math.min(y1, y2); + final int _y2 = Math.max(y1, y2); + + _r = new Rectangle(_x1, _y1, _x2 - _x1, _y2 - _y1); + + _z1 = Math.min(z1, z2); + _z2 = Math.max(z1, z2); + + _centerPoint = new Location((_x1 + _x2) / 2, (_y1 + _y2) / 2, (_z1 + _z2) / 2); + } + + @Override + public boolean isInsideZone(int x, int y, int z) + { + return _r.contains(x, y) && (z >= _z1) && (z <= _z2); + } + + @Override + public boolean intersectsRectangle(int ax1, int ax2, int ay1, int ay2) + { + return _r.intersects(Math.min(ax1, ax2), Math.min(ay1, ay2), Math.abs(ax2 - ax1), Math.abs(ay2 - ay1)); + } + + @Override + public double getDistanceToZone(int x, int y) + { + final int _x1 = _r.x; + final int _x2 = _r.x + _r.width; + final int _y1 = _r.y; + final int _y2 = _r.y + _r.height; + final double dx1 = _x1 - x; + final double dx2 = _x2 - x; + final double dy1 = _y1 - y; + final double dy2 = _y2 - y; + final double dx1Sq = dx1 * dx1; + final double dx2Sq = dx2 * dx2; + final double dy1Sq = dy1 * dy1; + final double dy2Sq = dy2 * dy2; + double test = dx1Sq + dy2Sq; + double shortestDist = dx1Sq + dy1Sq; + + if (test < shortestDist) + { + shortestDist = test; + } + + test = dx2Sq + dy1Sq; + if (test < shortestDist) + { + shortestDist = test; + } + + test = dx2Sq + dy2Sq; + if (test < shortestDist) + { + shortestDist = test; + } + + return Math.sqrt(shortestDist); + } + + /* + * getLowZ() / getHighZ() - These two functions were added to cope with the demand of the new fishing algorithms, which are now able to correctly place the hook in the water, thanks to getHighZ(). getLowZ() was added, considering potential future modifications. + */ + @Override + public int getLowZ() + { + return _z1; + } + + @Override + public int getHighZ() + { + return _z2; + } + + @Override + public void visualizeZone(int z) + { + final int _x1 = _r.x; + final int _x2 = _r.x + _r.width; + final int _y1 = _r.y; + final int _y2 = _r.y + _r.height; + + // x1->x2 + for (int x = _x1; x < _x2; x += STEP) + { + dropDebugItem(Inventory.ADENA_ID, 1, x, _y1, z); + dropDebugItem(Inventory.ADENA_ID, 1, x, _y2, z); + } + + // y1->y2 + for (int y = _y1; y < _y2; y += STEP) + { + dropDebugItem(Inventory.ADENA_ID, 1, _x1, y, z); + dropDebugItem(Inventory.ADENA_ID, 1, _x2, y, z); + } + } + + @Override + public Location getRandomPoint() + { + final int x = Rnd.get(_r.x, _r.x + _r.width); + final int y = Rnd.get(_r.y, _r.y + _r.height); + + return new Location(x, y, GeoEngine.getInstance().getHeight(x, y, (_z1 + _z2) / 2)); + } + + @Override + public Location getCenterPoint() + { + return _centerPoint; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/form/ZoneCylinder.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/form/ZoneCylinder.java new file mode 100644 index 0000000..4703cab --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/form/ZoneCylinder.java @@ -0,0 +1,189 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.zone.form; + +import org.l2jmobius.commons.util.Rnd; +import org.l2jmobius.gameserver.entity.Location; +import org.l2jmobius.gameserver.entity.itemcontainer.Inventory; +import org.l2jmobius.gameserver.entity.zone.ZoneForm; +import org.l2jmobius.gameserver.geoengine.GeoEngine; + +/** + * A primitive circular zone + * @author durgus, Mobius + */ +public class ZoneCylinder extends ZoneForm +{ + private final int _x; + private final int _y; + private final int _z1; + private final int _z2; + private final int _rad; + private final int _radS; + private final Location _centerPoint; + + public ZoneCylinder(int x, int y, int z1, int z2, int rad) + { + _x = x; + _y = y; + _z1 = z1; + _z2 = z2; + _rad = rad; + _radS = rad * rad; + _centerPoint = new Location(_x, _y, (_z1 + _z2) / 2); + } + + @Override + public boolean isInsideZone(int x, int y, int z) + { + final double dx = _x - x; + final double dy = _y - y; + return (((dx * dx) + (dy * dy)) <= _radS) && (z >= _z1) && (z <= _z2); + } + + @Override + public boolean intersectsRectangle(int ax1, int ax2, int ay1, int ay2) + { + // Circles point inside the rectangle? + if ((_x > ax1) && (_x < ax2) && (_y > ay1) && (_y < ay2)) + { + return true; + } + + // Any point of the rectangle intersecting the Circle? + final double dx1 = ax1 - _x; + final double dy1 = ay1 - _y; + final double dx1Sq = dx1 * dx1; + final double dy1Sq = dy1 * dy1; + if ((dx1Sq + dy1Sq) < _radS) + { + return true; + } + + final double dy2 = ay2 - _y; + final double dy2Sq = dy2 * dy2; + if ((dx1Sq + dy2Sq) < _radS) + { + return true; + } + + final double dx2 = ax2 - _x; + final double dx2Sq = dx2 * dx2; + if ((dx2Sq + dy1Sq) < _radS) + { + return true; + } + + if ((dx2Sq + dy2Sq) < _radS) + { + return true; + } + + // Collision on any side of the rectangle? + if ((_x > ax1) && (_x < ax2)) + { + if (Math.abs(_y - ay2) < _rad) + { + return true; + } + + if (Math.abs(_y - ay1) < _rad) + { + return true; + } + } + + if ((_y > ay1) && (_y < ay2)) + { + if (Math.abs(_x - ax2) < _rad) + { + return true; + } + + if (Math.abs(_x - ax1) < _rad) + { + return true; + } + } + + return false; + } + + @Override + public double getDistanceToZone(int x, int y) + { + final double dx = _x - x; + final double dy = _y - y; + return Math.sqrt((dx * dx) + (dy * dy)) - _rad; + } + + // getLowZ() / getHighZ() - These two functions were added to cope with the demand of the new fishing algorithms, wich are now able to correctly place the hook in the water, thanks to getHighZ(). getLowZ() was added, considering potential future modifications. + @Override + public int getLowZ() + { + return _z1; + } + + @Override + public int getHighZ() + { + return _z2; + } + + @Override + public void visualizeZone(int z) + { + final int count = (int) ((2 * Math.PI * _rad) / STEP); + final double angle = (2 * Math.PI) / count; + for (int i = 0; i < count; i++) + { + dropDebugItem(Inventory.ADENA_ID, 1, _x + (int) (Math.cos(angle * i) * _rad), _y + (int) (Math.sin(angle * i) * _rad), z); + } + } + + @Override + public Location getRandomPoint() + { + int x = 0; + int y = 0; + final int x2 = _x - _rad; + final int y2 = _y - _rad; + final int x3 = _x + _rad; + final int y3 = _y + _rad; + double dx = _x - x; + double dy = _y - y; + while (((dx * dx) + (dy * dy)) > _radS) + { + x = Rnd.get(x2, x3); + y = Rnd.get(y2, y3); + dx = _x - x; + dy = _y - y; + } + + return new Location(x, y, GeoEngine.getInstance().getHeight(x, y, (_z1 + _z2) / 2)); + } + + @Override + public Location getCenterPoint() + { + return _centerPoint; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/form/ZoneNPoly.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/form/ZoneNPoly.java new file mode 100644 index 0000000..25e2942 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/form/ZoneNPoly.java @@ -0,0 +1,191 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.zone.form; + +import java.awt.Polygon; + +import org.l2jmobius.commons.util.Rnd; +import org.l2jmobius.gameserver.entity.Location; +import org.l2jmobius.gameserver.entity.itemcontainer.Inventory; +import org.l2jmobius.gameserver.entity.zone.ZoneForm; +import org.l2jmobius.gameserver.geoengine.GeoEngine; + +/** + * A not so primitive npoly zone + * @author durgus, Mobius + */ +public class ZoneNPoly extends ZoneForm +{ + private final Polygon _p; + private final int _z1; + private final int _z2; + private final Location _centerPoint; + + /** + * @param x + * @param y + * @param z1 + * @param z2 + */ + public ZoneNPoly(int[] x, int[] y, int z1, int z2) + { + _p = new Polygon(x, y, x.length); + + _z1 = Math.min(z1, z2); + _z2 = Math.max(z1, z2); + + double area = 0; + double cx = 0; + double cy = 0; + for (int i = 0; i < _p.npoints; i++) + { + final int nextIndex = (i + 1) % _p.npoints; + final double crossProduct = (_p.xpoints[i] * _p.ypoints[nextIndex]) - (_p.xpoints[nextIndex] * _p.ypoints[i]); + area += crossProduct; + cx += (_p.xpoints[i] + _p.xpoints[nextIndex]) * crossProduct; + cy += (_p.ypoints[i] + _p.ypoints[nextIndex]) * crossProduct; + } + + area /= 2.0; + if (area == 0) + { + cx = 0; + cy = 0; + for (int i = 0; i < _p.npoints; i++) + { + cx += _p.xpoints[i]; + cy += _p.ypoints[i]; + } + + cx /= _p.npoints; + cy /= _p.npoints; + } + else + { + cx /= (6.0 * area); + cy /= (6.0 * area); + } + + _centerPoint = new Location((int) cx, (int) cy, (_z1 + _z2) / 2); + } + + @Override + public boolean isInsideZone(int x, int y, int z) + { + return _p.contains(x, y) && (z >= _z1) && (z <= _z2); + } + + @Override + public boolean intersectsRectangle(int ax1, int ax2, int ay1, int ay2) + { + return _p.intersects(Math.min(ax1, ax2), Math.min(ay1, ay2), Math.abs(ax2 - ax1), Math.abs(ay2 - ay1)); + } + + @Override + public double getDistanceToZone(int x, int y) + { + final int[] xPoints = _p.xpoints; + final int[] yPoints = _p.ypoints; + double test; + double dx = xPoints[0] - x; + double dy = yPoints[0] - y; + double shortestDist = (dx * dx) + (dy * dy); + + for (int i = 1; i < _p.npoints; i++) + { + dx = xPoints[i] - x; + dy = yPoints[i] - y; + test = (dx * dx) + (dy * dy); + if (test < shortestDist) + { + shortestDist = test; + } + } + + return Math.sqrt(shortestDist); + } + + // getLowZ() / getHighZ() - These two functions were added to cope with the demand of the new fishing algorithms, wich are now able to correctly place the hook in the water, thanks to getHighZ(). getLowZ() was added, considering potential future modifications. + @Override + public int getLowZ() + { + return _z1; + } + + @Override + public int getHighZ() + { + return _z2; + } + + @Override + public void visualizeZone(int z) + { + for (int i = 0; i < _p.npoints; i++) + { + final int nextIndex = (i + 1) == _p.xpoints.length ? 0 : i + 1; + final int vx = _p.xpoints[nextIndex] - _p.xpoints[i]; + final int vy = _p.ypoints[nextIndex] - _p.ypoints[i]; + final float length = (float) Math.sqrt((vx * vx) + (vy * vy)) / STEP; + for (int o = 1; o <= length; o++) + { + dropDebugItem(Inventory.ADENA_ID, 1, (int) (_p.xpoints[i] + ((o / length) * vx)), (int) (_p.ypoints[i] + ((o / length) * vy)), z); + } + } + } + + @Override + public Location getRandomPoint() + { + final int minX = _p.getBounds().x; + final int maxX = _p.getBounds().x + _p.getBounds().width; + final int minY = _p.getBounds().y; + final int maxY = _p.getBounds().y + _p.getBounds().height; + + int x = Rnd.get(minX, maxX); + int y = Rnd.get(minY, maxY); + + int antiBlocker = 0; + while (!_p.contains(x, y) && (antiBlocker++ < 1000)) + { + x = Rnd.get(minX, maxX); + y = Rnd.get(minY, maxY); + } + + return new Location(x, y, GeoEngine.getInstance().getHeight(x, y, (_z1 + _z2) / 2)); + } + + public int[] getX() + { + return _p.xpoints; + } + + public int[] getY() + { + return _p.ypoints; + } + + @Override + public Location getCenterPoint() + { + return _centerPoint; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/ArenaZone.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/ArenaZone.java new file mode 100644 index 0000000..4b9a708 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/ArenaZone.java @@ -0,0 +1,55 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.zone.type; + +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.zone.ZoneId; +import org.l2jmobius.gameserver.entity.zone.ZoneType; +import org.l2jmobius.gameserver.network.SystemMessageId; + +/** + * A PVP Zone + * @author durgus + */ +public class ArenaZone extends ZoneType +{ + public ArenaZone(int id) + { + super(id); + } + + @Override + protected void onEnter(Creature creature) + { + if (creature.isPlayer() && !creature.isInsideZone(ZoneId.PVP)) + { + creature.sendPacket(SystemMessageId.YOU_HAVE_ENTERED_A_COMBAT_ZONE); + } + + creature.setInsideZone(ZoneId.PVP, true); + } + + @Override + protected void onExit(Creature creature) + { + creature.setInsideZone(ZoneId.PVP, false); + if (creature.isPlayer() && !creature.isInsideZone(ZoneId.PVP)) + { + creature.sendPacket(SystemMessageId.YOU_HAVE_LEFT_A_COMBAT_ZONE); + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/BossZone.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/BossZone.java new file mode 100644 index 0000000..81a19df --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/BossZone.java @@ -0,0 +1,449 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.zone.type; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; + +import org.l2jmobius.gameserver.GameServer; +import org.l2jmobius.gameserver.entity.Location; +import org.l2jmobius.gameserver.entity.actor.Attackable; +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.enums.player.TeleportWhereType; +import org.l2jmobius.gameserver.entity.zone.AbstractZoneSettings; +import org.l2jmobius.gameserver.entity.zone.ZoneType; +import org.l2jmobius.gameserver.managers.GrandBossManager; +import org.l2jmobius.gameserver.managers.ZoneManager; + +/** + * @author DaRkRaGe + */ +public class BossZone extends ZoneType +{ + private int _timeInvade; + + private int[] _oustLoc = + { + 0, + 0, + 0 + }; + + public class Settings extends AbstractZoneSettings + { + // track the times that players got disconnected. Players are allowed + // to log back into the zone as long as their log-out was within _timeInvade time... + // + private final Map _playerAllowedReEntryTimes = new ConcurrentHashMap<>(); + + // track the players admitted to the zone who should be allowed back in + // after reboot/server downtime (outside of their control), within 30 of server restart + private final List _playersAllowed = new CopyOnWriteArrayList<>(); + + private final List _raidList = new CopyOnWriteArrayList<>(); + + protected Settings() + { + } + + public Map getPlayerAllowedReEntryTimes() + { + return _playerAllowedReEntryTimes; + } + + public List getPlayersAllowed() + { + return _playersAllowed; + } + + public List getRaidList() + { + return _raidList; + } + + @Override + public void clear() + { + _playerAllowedReEntryTimes.clear(); + _playersAllowed.clear(); + _raidList.clear(); + } + } + + public BossZone(int id) + { + super(id); + _oustLoc = new int[3]; + AbstractZoneSettings settings = ZoneManager.getSettings(getName()); + if (settings == null) + { + settings = new Settings(); + } + + setSettings(settings); + GrandBossManager.getInstance().addZone(this); + } + + @Override + public Settings getSettings() + { + return (Settings) super.getSettings(); + } + + @Override + public void setParameter(String name, String value) + { + if (name.equals("InvadeTime")) + { + _timeInvade = Integer.parseInt(value); + } + else if (name.equals("oustX")) + { + _oustLoc[0] = Integer.parseInt(value); + } + else if (name.equals("oustY")) + { + _oustLoc[1] = Integer.parseInt(value); + } + else if (name.equals("oustZ")) + { + _oustLoc[2] = Integer.parseInt(value); + } + else + { + super.setParameter(name, value); + } + } + + /** + * Boss zones have special behaviors for player characters.
        + * Players are automatically teleported out when the attempt to enter these zones, except if the time at which they enter the zone is prior to the entry expiration time set for that player.
        + * Entry expiration times are set by any one of the following:
        + * 1) A player logs out while in a zone (Expiration gets set to logoutTime + _timeInvade)
        + * 2) An external source (such as a quest or AI of NPC) set up the player for entry.
        + * There exists one more case in which the player will be allowed to enter.
        + * That is if the server recently rebooted (boot-up time more recent than currentTime - _timeInvade) AND the player was in the zone prior to reboot. + */ + @Override + protected void onEnter(Creature creature) + { + if (creature.isPlayer()) + { + final Player player = creature.asPlayer(); + if (player.isGM()) + { + return; + } + + // if player has been (previously) cleared by npc/ai for entry and the zone is + // set to receive players (aka not waiting for boss to respawn) + if (getSettings().getPlayersAllowed().contains(player.getObjectId())) + { + // Get the information about this player's last logout-exit from + // this zone. + final Long expirationTime = getSettings().getPlayerAllowedReEntryTimes().get(player.getObjectId()); + + // with legal entries, do nothing. + if (expirationTime == null) // legal null expirationTime entries + { + if (GameServer.getStartTime() > (System.currentTimeMillis() - _timeInvade)) + { + return; + } + } + else + { + // legal non-null logoutTime entries + getSettings().getPlayerAllowedReEntryTimes().remove(player.getObjectId()); + if (expirationTime.longValue() > System.currentTimeMillis()) + { + return; + } + } + + getSettings().getPlayersAllowed().remove(getSettings().getPlayersAllowed().indexOf(player.getObjectId())); + } + + // teleport out all players who attempt "illegal" (re-)entry + if ((_oustLoc[0] != 0) && (_oustLoc[1] != 0) && (_oustLoc[2] != 0)) + { + player.teleToLocation(_oustLoc[0], _oustLoc[1], _oustLoc[2]); + } + else + { + player.teleToLocation(TeleportWhereType.TOWN); + } + } + else if (creature.isSummon()) + { + final Player player = creature.asPlayer(); + if (player != null) + { + if (getSettings().getPlayersAllowed().contains(player.getObjectId()) || player.isGM()) + { + return; + } + + // remove summon and teleport out owner + // who attempt "illegal" (re-)entry + if ((_oustLoc[0] != 0) && (_oustLoc[1] != 0) && (_oustLoc[2] != 0)) + { + player.teleToLocation(_oustLoc[0], _oustLoc[1], _oustLoc[2]); + } + else + { + player.teleToLocation(TeleportWhereType.TOWN); + } + } + + creature.asSummon().unSummon(player); + } + } + + @Override + protected void onExit(Creature creature) + { + if (creature.isPlayer()) + { + final Player player = creature.asPlayer(); + if (player.isGM()) + { + return; + } + + // If the player just got disconnected/logged out, store the dc time so that + // decisions can be made later about allowing or not the player to log into the zone. + if (!player.isOnline() && getSettings().getPlayersAllowed().contains(player.getObjectId())) + { + // mark the time that the player left the zone + getSettings().getPlayerAllowedReEntryTimes().put(player.getObjectId(), System.currentTimeMillis() + _timeInvade); + } + else + { + if (getSettings().getPlayersAllowed().contains(player.getObjectId())) + { + getSettings().getPlayersAllowed().remove(getSettings().getPlayersAllowed().indexOf(player.getObjectId())); + } + + getSettings().getPlayerAllowedReEntryTimes().remove(player.getObjectId()); + } + } + + if (creature.isPlayable() && (getCharactersInside() != null) && !getCharactersInside().isEmpty()) + { + getSettings().getRaidList().clear(); + int count = 0; + for (Creature obj : getCharactersInside()) + { + if (obj == null) + { + continue; + } + + if (obj.isPlayable()) + { + count++; + } + else if (obj.isAttackable() && obj.isRaid()) + { + getSettings().getRaidList().add(obj); + } + } + + // If inside zone is not any player, force all boss instance return to its spawn points. + if ((count == 0) && !getSettings().getRaidList().isEmpty()) + { + for (int i = 0; i < getSettings().getRaidList().size(); i++) + { + final Attackable raid = getSettings().getRaidList().get(i).asAttackable(); + if ((raid == null) || (raid.getSpawn() == null) || raid.isDead()) + { + continue; + } + + if (!raid.isInsideRadius2D(raid.getSpawn(), 150)) + { + raid.returnHome(); + } + } + } + } + + if (creature.isAttackable() && creature.isRaid() && !creature.isDead()) + { + creature.asAttackable().returnHome(); + } + } + + @Override + public void setEnabled(boolean flag) + { + if (isEnabled() != flag) + { + oustAllPlayers(); + } + + super.setEnabled(flag); + } + + public int getTimeInvade() + { + return _timeInvade; + } + + public void setAllowedPlayers(List players) + { + if (players != null) + { + getSettings().getPlayersAllowed().clear(); + getSettings().getPlayersAllowed().addAll(players); + } + } + + public List getAllowedPlayers() + { + return getSettings().getPlayersAllowed(); + } + + public boolean isPlayerAllowed(Player player) + { + if (player.isGM() || getSettings().getPlayersAllowed().contains(player.getObjectId())) + { + return true; + } + + if ((_oustLoc[0] != 0) && (_oustLoc[1] != 0) && (_oustLoc[2] != 0)) + { + player.teleToLocation(_oustLoc[0], _oustLoc[1], _oustLoc[2]); + } + else + { + player.teleToLocation(TeleportWhereType.TOWN); + } + + return false; + } + + /** + * Some GrandBosses send all players in zone to a specific part of the zone, rather than just removing them all. If this is the case, this command should be used. If this is no the case, then use oustAllPlayers(). + * @param loc + */ + public void movePlayersTo(Location loc) + { + for (Creature creature : getCharactersInside()) + { + if ((creature != null) && creature.isPlayer()) + { + final Player player = creature.asPlayer(); + if (player.isOnline()) + { + player.teleToLocation(loc); + } + } + } + } + + /** + * Occasionally, all players need to be sent out of the zone (for example, if the players are just running around without fighting for too long, or if all players die, etc). This call sends all online players to town and marks offline players to be teleported (by clearing their relog expiration + * times) when they log back in (no real need for off-line teleport). + */ + @Override + public void oustAllPlayers() + { + if (getCharactersInside().isEmpty()) + { + return; + } + + for (Creature creature : getCharactersInside()) + { + if ((creature != null) && creature.isPlayer()) + { + final Player player = creature.asPlayer(); + if (player.isOnline()) + { + if ((_oustLoc[0] != 0) && (_oustLoc[1] != 0) && (_oustLoc[2] != 0)) + { + player.teleToLocation(_oustLoc[0], _oustLoc[1], _oustLoc[2]); + } + else + { + player.teleToLocation(TeleportWhereType.TOWN); + } + } + } + } + + getSettings().getPlayerAllowedReEntryTimes().clear(); + getSettings().getPlayersAllowed().clear(); + } + + /** + * This function is to be used by external sources, such as quests and AI in order to allow a player for entry into the zone for some time. Naturally if the player does not enter within the allowed time, he/she will be teleported out again... + * @param player reference to the player we wish to allow + * @param durationInSec amount of time in seconds during which entry is valid. + */ + public void allowPlayerEntry(Player player, int durationInSec) + { + if (player.isGM()) + { + return; + } + + if (!getSettings().getPlayersAllowed().contains(player.getObjectId())) + { + getSettings().getPlayersAllowed().add(player.getObjectId()); + } + + getSettings().getPlayerAllowedReEntryTimes().put(player.getObjectId(), System.currentTimeMillis() + (durationInSec * 1000)); + } + + public void removePlayer(Player player) + { + if (player.isGM()) + { + return; + } + + getSettings().getPlayersAllowed().remove(Integer.valueOf(player.getObjectId())); + getSettings().getPlayerAllowedReEntryTimes().remove(player.getObjectId()); + } + + // public void updateKnownList(Npc npc) + // { + // if ((_characterList == null) || _characterList.isEmpty()) + // { + // return; + // } + // + // final Map npcKnownPlayers = npc.getKnownList().getKnownPlayers(); + // for (Creature creature : getCharactersInside()) + // { + // if ((character != null) && character.isPlayer()) + // { + // final Player player = character.asPlayer(); + // if (player.isOnline()) + // { + // npcKnownPlayers.put(player.getObjectId(), player); + // } + // } + // } + // } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/CastleZone.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/CastleZone.java new file mode 100644 index 0000000..ab92a80 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/CastleZone.java @@ -0,0 +1,57 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.zone.type; + +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.zone.ZoneId; + +/** + * A castle zone + * @author durgus + */ +public class CastleZone extends ResidenceZone +{ + public CastleZone(int id) + { + super(id); + } + + @Override + public void setParameter(String name, String value) + { + if (name.equals("castleId")) + { + setResidenceId(Integer.parseInt(value)); + } + else + { + super.setParameter(name, value); + } + } + + @Override + protected void onEnter(Creature creature) + { + creature.setInsideZone(ZoneId.CASTLE, true); + } + + @Override + protected void onExit(Creature creature) + { + creature.setInsideZone(ZoneId.CASTLE, false); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/ClanHallZone.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/ClanHallZone.java new file mode 100644 index 0000000..ab84ea7 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/ClanHallZone.java @@ -0,0 +1,89 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.zone.type; + +import org.l2jmobius.gameserver.data.sql.ClanHallTable; +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.residences.AuctionableHall; +import org.l2jmobius.gameserver.entity.residences.ClanHall; +import org.l2jmobius.gameserver.entity.zone.ZoneId; +import org.l2jmobius.gameserver.network.serverpackets.AgitDecoInfo; + +/** + * A clan hall zone + * @author durgus + */ +public class ClanHallZone extends ResidenceZone +{ + public ClanHallZone(int id) + { + super(id); + } + + @Override + public void setParameter(String name, String value) + { + if (name.equals("clanHallId")) + { + setResidenceId(Integer.parseInt(value)); + + // Register self to the correct clan hall. + final ClanHall hall = ClanHallTable.getInstance().getClanHallById(getResidenceId()); + if (hall != null) + { + hall.setZone(this); + } + else + { + LOGGER.warning("ClanHallZone: Clan hall with id " + getResidenceId() + " does not exist!"); + } + } + else + { + super.setParameter(name, value); + } + } + + @Override + protected void onEnter(Creature creature) + { + if (!creature.isPlayer()) + { + return; + } + + // Set as in clan hall. + creature.setInsideZone(ZoneId.CLAN_HALL, true); + final AuctionableHall clanHall = ClanHallTable.getInstance().getAuctionableHallById(getResidenceId()); + if (clanHall == null) + { + return; + } + + // Send decoration packet + creature.sendPacket(new AgitDecoInfo(clanHall)); + } + + @Override + protected void onExit(Creature creature) + { + if (creature.isPlayer()) + { + creature.setInsideZone(ZoneId.CLAN_HALL, false); + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/ConditionZone.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/ConditionZone.java new file mode 100644 index 0000000..fc5839f --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/ConditionZone.java @@ -0,0 +1,86 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.zone.type; + +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.zone.ZoneId; +import org.l2jmobius.gameserver.entity.zone.ZoneType; + +/** + * @author UnAfraid + */ +public class ConditionZone extends ZoneType +{ + private boolean NO_ITEM_DROP = false; + private boolean NO_BOOKMARK = false; + + public ConditionZone(int id) + { + super(id); + } + + @Override + public void setParameter(String name, String value) + { + if (name.equalsIgnoreCase("NoBookmark")) + { + NO_BOOKMARK = Boolean.parseBoolean(value); + } + else if (name.equalsIgnoreCase("NoItemDrop")) + { + NO_ITEM_DROP = Boolean.parseBoolean(value); + } + else + { + super.setParameter(name, value); + } + } + + @Override + protected void onEnter(Creature creature) + { + if (creature.isPlayer()) + { + if (NO_BOOKMARK) + { + creature.setInsideZone(ZoneId.NO_BOOKMARK, true); + } + + if (NO_ITEM_DROP) + { + creature.setInsideZone(ZoneId.NO_ITEM_DROP, true); + } + } + } + + @Override + protected void onExit(Creature creature) + { + if (creature.isPlayer()) + { + if (NO_BOOKMARK) + { + creature.setInsideZone(ZoneId.NO_BOOKMARK, false); + } + + if (NO_ITEM_DROP) + { + creature.setInsideZone(ZoneId.NO_ITEM_DROP, false); + } + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/DamageZone.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/DamageZone.java new file mode 100644 index 0000000..04b39b5 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/DamageZone.java @@ -0,0 +1,220 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.zone.type; + +import java.util.concurrent.Future; + +import org.l2jmobius.commons.threads.ThreadPool; +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.enums.creature.InstanceType; +import org.l2jmobius.gameserver.entity.zone.ZoneType; +import org.l2jmobius.gameserver.managers.CastleManager; +import org.l2jmobius.gameserver.mechanics.siege.Castle; +import org.l2jmobius.gameserver.mechanics.stats.Stat; + +/** + * A damage zone + * @author durgus + */ +public class DamageZone extends ZoneType +{ + private int _damageHPPerSec; + private int _damageMPPerSec; + + private int _castleId; + private Castle _castle; + + private int _startTask; + private int _reuseTask; + protected volatile Future _task; + + public DamageZone(int id) + { + super(id); + + // Setup default damage + _damageHPPerSec = 200; + _damageMPPerSec = 0; + + // Setup default start / reuse time. + _startTask = 10; + _reuseTask = 5000; + + // no castle by default + _castleId = 0; + _castle = null; + + setTargetType(InstanceType.Playable); // default only playabale + } + + @Override + public void setParameter(String name, String value) + { + if (name.equals("dmgHPSec")) + { + _damageHPPerSec = Integer.parseInt(value); + } + else if (name.equals("dmgMPSec")) + { + _damageMPPerSec = Integer.parseInt(value); + } + else if (name.equals("castleId")) + { + _castleId = Integer.parseInt(value); + } + else if (name.equalsIgnoreCase("initialDelay")) + { + _startTask = Integer.parseInt(value); + } + else if (name.equalsIgnoreCase("reuse")) + { + _reuseTask = Integer.parseInt(value); + } + else + { + super.setParameter(name, value); + } + } + + @Override + protected void onEnter(Creature creature) + { + Future task = _task; + if ((task == null) && ((_damageHPPerSec != 0) || (_damageMPPerSec != 0))) + { + final Player player = creature.asPlayer(); + final Castle castle = getCastle(); + if (castle != null) // Castle zone + { + if (!(castle.getSiege().isInProgress() && (player != null) && (player.getSiegeState() != 2))) // Siege and no defender + { + return; + } + } + + synchronized (this) + { + task = _task; + if (task == null) + { + _task = task = ThreadPool.scheduleAtFixedRate(new ApplyDamage(), _startTask, _reuseTask); + } + } + } + } + + @Override + protected void onExit(Creature creature) + { + if (getCharactersInside().isEmpty() && (_task != null)) + { + _task.cancel(true); + _task = null; + } + } + + protected int getHPDamagePerSecond() + { + return _damageHPPerSec; + } + + protected int getMPDamagePerSecond() + { + return _damageMPPerSec; + } + + protected Castle getCastle() + { + if ((_castleId > 0) && (_castle == null)) + { + _castle = CastleManager.getInstance().getCastleById(_castleId); + } + + return _castle; + } + + private class ApplyDamage implements Runnable + { + private final Castle _castle; + + protected ApplyDamage() + { + _castle = getCastle(); + } + + @Override + public void run() + { + if (getCharactersInside().isEmpty()) + { + if (_task != null) + { + _task.cancel(false); + _task = null; + } + return; + } + + if (!isEnabled()) + { + return; + } + + boolean siege = false; + if (_castle != null) + { + siege = _castle.getSiege().isInProgress(); + + // Castle zones active only during siege. + if (!siege) + { + _task.cancel(false); + _task = null; + return; + } + } + + for (Creature character : getCharactersInside()) + { + if ((character != null) && character.isPlayer() && !character.isDead()) + { + if (siege) + { + // During siege defenders not affected. + final Player player = character.asPlayer(); + if ((player != null) && player.isInSiege() && (player.getSiegeState() == 2)) + { + continue; + } + } + + final double multiplier = 1 + (character.calcStat(Stat.DAMAGE_ZONE_VULN, 0, null, null) / 100); + if (getHPDamagePerSecond() != 0) + { + character.reduceCurrentHp(getHPDamagePerSecond() * multiplier, character, null); + } + + if (getMPDamagePerSecond() != 0) + { + character.reduceCurrentMp(getMPDamagePerSecond() * multiplier); + } + } + } + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/DerbyTrackZone.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/DerbyTrackZone.java new file mode 100644 index 0000000..91f03d2 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/DerbyTrackZone.java @@ -0,0 +1,51 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.zone.type; + +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.zone.ZoneId; +import org.l2jmobius.gameserver.entity.zone.ZoneType; + +/** + * The Monster Derby Track Zone + * @author durgus + */ +public class DerbyTrackZone extends ZoneType +{ + public DerbyTrackZone(int id) + { + super(id); + } + + @Override + protected void onEnter(Creature creature) + { + if (creature.isPlayable()) + { + creature.setInsideZone(ZoneId.MONSTER_TRACK, true); + } + } + + @Override + protected void onExit(Creature creature) + { + if (creature.isPlayable()) + { + creature.setInsideZone(ZoneId.MONSTER_TRACK, false); + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/EffectZone.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/EffectZone.java new file mode 100644 index 0000000..30c02ed --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/EffectZone.java @@ -0,0 +1,302 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.zone.type; + +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Future; + +import org.l2jmobius.commons.threads.ThreadPool; +import org.l2jmobius.commons.util.Rnd; +import org.l2jmobius.gameserver.data.xml.SkillData; +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.enums.creature.InstanceType; +import org.l2jmobius.gameserver.entity.zone.ZoneId; +import org.l2jmobius.gameserver.entity.zone.ZoneType; +import org.l2jmobius.gameserver.mechanics.skill.Skill; +import org.l2jmobius.gameserver.mechanics.skill.enums.SkillFinishType; +import org.l2jmobius.gameserver.network.serverpackets.EtcStatusUpdate; + +/** + * Another type of damage zone with skills. + * @author Kerberos + */ +public class EffectZone extends ZoneType +{ + int _chance; + private int _initialDelay; + private int _reuse; + protected boolean _bypassConditions; + private boolean _isShowDangerIcon; + private boolean _removeEffectsOnExit; + protected Map _skills; + protected volatile Future _task; + + public EffectZone(int id) + { + super(id); + _chance = 100; + _initialDelay = 0; + _reuse = 30000; + setTargetType(InstanceType.Playable); // default only playable + _bypassConditions = false; + _isShowDangerIcon = true; + _removeEffectsOnExit = false; + } + + @Override + public void setParameter(String name, String value) + { + switch (name) + { + case "chance": + { + _chance = Integer.parseInt(value); + break; + } + case "initialDelay": + { + _initialDelay = Integer.parseInt(value); + break; + } + case "reuse": + { + _reuse = Integer.parseInt(value); + break; + } + case "bypassSkillConditions": + { + _bypassConditions = Boolean.parseBoolean(value); + break; + } + case "maxDynamicSkillCount": + { + _skills = new ConcurrentHashMap<>(Integer.parseInt(value)); + break; + } + case "showDangerIcon": + { + _isShowDangerIcon = Boolean.parseBoolean(value); + break; + } + case "skillIdLvl": + { + final String[] propertySplit = value.split(";"); + _skills = new ConcurrentHashMap<>(propertySplit.length); + for (String skill : propertySplit) + { + final String[] skillSplit = skill.split("-"); + if (skillSplit.length != 2) + { + LOGGER.warning(getClass().getSimpleName() + ": invalid config property -> skillsIdLvl \"" + skill + "\""); + } + else + { + try + { + _skills.put(Integer.parseInt(skillSplit[0]), Integer.parseInt(skillSplit[1])); + } + catch (NumberFormatException nfe) + { + if (!skill.isEmpty()) + { + LOGGER.warning(getClass().getSimpleName() + ": invalid config property -> skillsIdLvl \"" + skillSplit[0] + "\"" + skillSplit[1]); + } + } + } + } + break; + } + case "removeEffectsOnExit": + { + _removeEffectsOnExit = Boolean.parseBoolean(value); + break; + } + default: + { + super.setParameter(name, value); + } + } + } + + @Override + protected void onEnter(Creature creature) + { + if (_skills != null) + { + Future task = _task; + if (task == null) + { + synchronized (this) + { + task = _task; + if (task == null) + { + _task = task = ThreadPool.scheduleAtFixedRate(new ApplySkill(), _initialDelay, _reuse); + } + } + } + } + + if (creature.isPlayer()) + { + creature.setInsideZone(ZoneId.ALTERED, true); + if (_isShowDangerIcon) + { + creature.setInsideZone(ZoneId.DANGER_AREA, true); + creature.sendPacket(new EtcStatusUpdate(creature.asPlayer())); + } + } + } + + @Override + protected void onExit(Creature creature) + { + if (creature.isPlayer()) + { + creature.setInsideZone(ZoneId.ALTERED, false); + if (_isShowDangerIcon) + { + creature.setInsideZone(ZoneId.DANGER_AREA, false); + if (!creature.isInsideZone(ZoneId.DANGER_AREA)) + { + creature.sendPacket(new EtcStatusUpdate(creature.asPlayer())); + } + } + + if (_removeEffectsOnExit && (_skills != null)) + { + for (Entry e : _skills.entrySet()) + { + final Skill skill = SkillData.getInstance().getSkill(e.getKey().intValue(), e.getValue().intValue()); + if ((skill != null) && creature.isAffectedBySkill(skill.getId())) + { + creature.stopSkillEffects(SkillFinishType.REMOVED, skill.getId()); + } + } + } + } + + if (getCharactersInside().isEmpty() && (_task != null)) + { + _task.cancel(true); + _task = null; + } + } + + public int getChance() + { + return _chance; + } + + public void addSkill(int skillId, int skillLevel) + { + if (skillLevel < 1) // remove skill + { + removeSkill(skillId); + return; + } + + if (_skills == null) + { + synchronized (this) + { + if (_skills == null) + { + _skills = new ConcurrentHashMap<>(3); + } + } + } + + _skills.put(skillId, skillLevel); + } + + public void removeSkill(int skillId) + { + if (_skills != null) + { + _skills.remove(skillId); + } + } + + public void clearSkills() + { + if (_skills != null) + { + _skills.clear(); + } + } + + public int getSkillLevel(int skillId) + { + if ((_skills == null) || !_skills.containsKey(skillId)) + { + return 0; + } + + return _skills.get(skillId); + } + + private class ApplySkill implements Runnable + { + protected ApplySkill() + { + if (_skills == null) + { + throw new IllegalStateException("No skills defined."); + } + } + + @Override + public void run() + { + if (getCharactersInside().isEmpty()) + { + if (_task != null) + { + _task.cancel(false); + _task = null; + } + return; + } + + if (!isEnabled()) + { + return; + } + + for (Creature character : getCharactersInside()) + { + if ((character != null) && character.isPlayer() && !character.isDead() && (Rnd.get(100) < _chance)) + { + for (Entry e : _skills.entrySet()) + { + final Skill skill = SkillData.getInstance().getSkill(e.getKey().intValue(), e.getValue().intValue()); + if ((skill != null) && (_bypassConditions || skill.checkCondition(character, character, false))) + { + if (!character.isAffectedBySkill(skill.getId())) + { + skill.applyEffects(character, character); + } + } + } + } + } + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/FishingZone.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/FishingZone.java new file mode 100644 index 0000000..bc41f30 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/FishingZone.java @@ -0,0 +1,50 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.zone.type; + +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.zone.ZoneType; + +/** + * A fishing zone + * @author durgus + */ +public class FishingZone extends ZoneType +{ + public FishingZone(int id) + { + super(id); + } + + @Override + protected void onEnter(Creature creature) + { + } + + @Override + protected void onExit(Creature creature) + { + } + + /* + * getWaterZ() this added function returns the Z value for the water surface. In effect this simply returns the upper Z value of the zone. This required some modification of ZoneForm, and zone form extentions. + */ + public int getWaterZ() + { + return getZone().getHighZ(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/FlagPvPZone.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/FlagPvPZone.java new file mode 100644 index 0000000..7330403 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/FlagPvPZone.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.zone.type; + +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.zone.ZoneType; + +/** + * A zone where PvP flag is automatically enabled. + * @author Stayway + */ +public class FlagPvPZone extends ZoneType +{ + public FlagPvPZone(int id) + { + super(id); + } + + @Override + protected void onEnter(Creature creature) + { + if (creature.isPlayer()) + { + final Player player = creature.asPlayer(); + player.updatePvPFlag(1); + player.sendMessage("You entered a PvP Flag zone."); + player.broadcastUserInfo(); + } + } + + @Override + protected void onExit(Creature creature) + { + if (creature.isPlayer()) + { + final Player player = creature.asPlayer(); + if (player.getPvpFlag() > 0) + { + player.updatePvPFlag(0); + } + player.sendMessage("You left the PvP Flag zone."); + player.broadcastUserInfo(); + } + } + + @Override + public void onDieInside(Creature creature) + { + // When creature dies inside the zone, treat as if it had exited. + onExit(creature); + } + + @Override + public void onReviveInside(Creature creature) + { + // When creature revives inside the zone, treat as if it had entered. + onEnter(creature); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/HqZone.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/HqZone.java new file mode 100644 index 0000000..d263cae --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/HqZone.java @@ -0,0 +1,76 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.zone.type; + +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.zone.ZoneId; +import org.l2jmobius.gameserver.entity.zone.ZoneType; + +/** + * Zone where 'Build Headquarters' is allowed. + * @author Gnacik + */ +public class HqZone extends ZoneType +{ + public HqZone(int id) + { + super(id); + } + + @Override + public void setParameter(String name, String value) + { + if ("castleId".equals(name)) + { + // TODO + } + else if ("fortId".equals(name)) + { + // TODO + } + else if ("clanHallId".equals(name)) + { + // TODO + } + else if ("territoryId".equals(name)) + { + // TODO + } + else + { + super.setParameter(name, value); + } + } + + @Override + protected void onEnter(Creature creature) + { + if (creature.isPlayer()) + { + creature.setInsideZone(ZoneId.HQ, true); + } + } + + @Override + protected void onExit(Creature creature) + { + if (creature.isPlayer()) + { + creature.setInsideZone(ZoneId.HQ, false); + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/JailZone.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/JailZone.java new file mode 100644 index 0000000..25be642 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/JailZone.java @@ -0,0 +1,101 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.zone.type; + +import org.l2jmobius.commons.threads.ThreadPool; +import org.l2jmobius.gameserver.config.GeneralConfig; +import org.l2jmobius.gameserver.entity.Location; +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.tasks.player.TeleportTask; +import org.l2jmobius.gameserver.entity.zone.ZoneId; +import org.l2jmobius.gameserver.entity.zone.ZoneType; +import org.l2jmobius.gameserver.network.SystemMessageId; + +/** + * A jail zone + * @author durgus + */ +public class JailZone extends ZoneType +{ + private static final Location JAIL_IN_LOC = new Location(-114356, -249645, -2984); + private static final Location JAIL_OUT_LOC = new Location(17836, 170178, -3507); + + public JailZone(int id) + { + super(id); + } + + @Override + protected void onEnter(Creature creature) + { + if (creature.isPlayer()) + { + creature.setInsideZone(ZoneId.JAIL, true); + creature.setInsideZone(ZoneId.NO_SUMMON_FRIEND, true); + if (GeneralConfig.JAIL_IS_PVP) + { + creature.setInsideZone(ZoneId.PVP, true); + creature.sendPacket(SystemMessageId.YOU_HAVE_ENTERED_A_COMBAT_ZONE); + } + + if (GeneralConfig.JAIL_DISABLE_TRANSACTION) + { + creature.setInsideZone(ZoneId.NO_STORE, true); + } + } + } + + @Override + protected void onExit(Creature creature) + { + if (creature.isPlayer()) + { + final Player player = creature.asPlayer(); + player.setInsideZone(ZoneId.JAIL, false); + player.setInsideZone(ZoneId.NO_SUMMON_FRIEND, false); + + if (GeneralConfig.JAIL_IS_PVP) + { + creature.setInsideZone(ZoneId.PVP, false); + creature.sendPacket(SystemMessageId.YOU_HAVE_LEFT_A_COMBAT_ZONE); + } + + if (player.isJailed()) + { + // When a player wants to exit jail even if he is still jailed, teleport him back to jail. + ThreadPool.schedule(new TeleportTask(player, JAIL_IN_LOC), 2000); + creature.sendMessage("You cannot cheat your way out of here. You must wait until your jail time is over."); + } + + if (GeneralConfig.JAIL_DISABLE_TRANSACTION) + { + creature.setInsideZone(ZoneId.NO_STORE, false); + } + } + } + + public static Location getLocationIn() + { + return JAIL_IN_LOC; + } + + public static Location getLocationOut() + { + return JAIL_OUT_LOC; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/MotherTreeZone.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/MotherTreeZone.java new file mode 100644 index 0000000..04a1323 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/MotherTreeZone.java @@ -0,0 +1,109 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.zone.type; + +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.zone.ZoneId; +import org.l2jmobius.gameserver.entity.zone.ZoneType; +import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; + +/** + * A mother-trees zone Basic type zone for Hp, MP regen + * @author durgus + */ +public class MotherTreeZone extends ZoneType +{ + private int _enterMsg; + private int _leaveMsg; + private int _mpRegen; + private int _hpRegen; + + public MotherTreeZone(int id) + { + super(id); + } + + @Override + public void setParameter(String name, String value) + { + if (name.equals("enterMsgId")) + { + _enterMsg = Integer.parseInt(value); + } + else if (name.equals("leaveMsgId")) + { + _leaveMsg = Integer.parseInt(value); + } + else if (name.equals("MpRegenBonus")) + { + _mpRegen = Integer.parseInt(value); + } + else if (name.equals("HpRegenBonus")) + { + _hpRegen = Integer.parseInt(value); + } + else + { + super.setParameter(name, value); + } + } + + @Override + protected void onEnter(Creature creature) + { + if (creature.isPlayer()) + { + final Player player = creature.asPlayer(); + creature.setInsideZone(ZoneId.MOTHER_TREE, true); + if (_enterMsg != 0) + { + player.sendPacket(new SystemMessage(_enterMsg)); + } + } + } + + @Override + protected void onExit(Creature creature) + { + if (creature.isPlayer()) + { + final Player player = creature.asPlayer(); + player.setInsideZone(ZoneId.MOTHER_TREE, false); + if (_leaveMsg != 0) + { + player.sendPacket(new SystemMessage(_leaveMsg)); + } + } + } + + /** + * @return the _mpRegen + */ + public int getMpRegenBonus() + { + return _mpRegen; + } + + /** + * @return the _hpRegen + */ + public int getHpRegenBonus() + { + return _hpRegen; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/NoLandingZone.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/NoLandingZone.java new file mode 100644 index 0000000..140a985 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/NoLandingZone.java @@ -0,0 +1,89 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.zone.type; + +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.enums.player.MountType; +import org.l2jmobius.gameserver.entity.zone.ZoneId; +import org.l2jmobius.gameserver.entity.zone.ZoneType; +import org.l2jmobius.gameserver.network.SystemMessageId; + +/** + * A no landing zone + * @author durgus + */ +public class NoLandingZone extends ZoneType +{ + private int dismountDelay = 5; + + public NoLandingZone(int id) + { + super(id); + } + + @Override + public void setParameter(String name, String value) + { + if (name.equals("dismountDelay")) + { + dismountDelay = Integer.parseInt(value); + } + else + { + super.setParameter(name, value); + } + } + + @Override + protected void onEnter(Creature creature) + { + if (creature.isPlayer()) + { + creature.setInsideZone(ZoneId.NO_LANDING, true); + + final Player player = creature.asPlayer(); + + if (player.isGM()) + { + player.sendMessage("You have entered a no-landing zone. Dismount restrictions are ignored for GMs."); + return; + } + + if (player.getMountType() == MountType.WYVERN) + { + player.sendPacket(SystemMessageId.THIS_AREA_CANNOT_BE_ENTERED_WHILE_MOUNTED_ATOP_OF_A_WYVERN_YOU_WILL_BE_DISMOUNTED_FROM_YOUR_WYVERN_IF_YOU_DO_NOT_LEAVE); + player.enteredNoLanding(dismountDelay); + } + } + } + + @Override + protected void onExit(Creature creature) + { + if (creature.isPlayer()) + { + creature.setInsideZone(ZoneId.NO_LANDING, false); + + final Player player = creature.asPlayer(); + if (player.getMountType() == MountType.WYVERN) + { + player.exitedNoLanding(); + } + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/NoPvPZone.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/NoPvPZone.java new file mode 100644 index 0000000..390cac9 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/NoPvPZone.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.zone.type; + +import org.l2jmobius.gameserver.config.GeneralConfig; +import org.l2jmobius.gameserver.entity.World; +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.zone.ZoneId; +import org.l2jmobius.gameserver.entity.zone.ZoneType; + +/** + * No PVP Zone + * @author Edoo + */ +public class NoPvPZone extends ZoneType +{ + public NoPvPZone(int id) + { + super(id); + } + + @Override + protected void onEnter(Creature creature) + { + if (creature.isPlayer()) + { + final Player player = creature.asPlayer(); + + // PVP possible during siege, now for siege participants only. + // Could also check if this town is in siege, or if any siege is going on. + if ((player.getSiegeState() != 0) && (GeneralConfig.PEACE_ZONE_MODE == 1)) + { + return; + } + } + + if (GeneralConfig.PEACE_ZONE_MODE != 2) + { + creature.setInsideZone(ZoneId.NO_PVP, true); + } + + // Send player info to nearby players. + if (creature.isPlayer()) + { + creature.broadcastInfo(); + } + } + + @Override + protected void onExit(Creature creature) + { + if (GeneralConfig.PEACE_ZONE_MODE != 2) + { + creature.setInsideZone(ZoneId.NO_PVP, false); + } + + // Send player info to nearby players. + if (creature.isPlayer() && !creature.isTeleporting()) + { + creature.broadcastInfo(); + } + } + + @Override + public void setEnabled(boolean value) + { + super.setEnabled(value); + if (value) + { + for (Player player : World.getPlayers()) + { + if ((player != null) && isInsideZone(player)) + { + revalidateInZone(player); + + if (player.getSummon() != null) + { + revalidateInZone(player.getSummon()); + } + } + } + } + else + { + for (Creature creature : getCharactersInside()) + { + if (creature != null) + { + removeCharacter(creature); + } + } + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/NoRestartZone.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/NoRestartZone.java new file mode 100644 index 0000000..ae4cdf1 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/NoRestartZone.java @@ -0,0 +1,113 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.zone.type; + +import org.l2jmobius.gameserver.GameServer; +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.enums.player.TeleportWhereType; +import org.l2jmobius.gameserver.entity.zone.ZoneId; +import org.l2jmobius.gameserver.entity.zone.ZoneType; + +/** + * A simple no restart zone + * @author GKR + */ +public class NoRestartZone extends ZoneType +{ + private int _restartAllowedTime = 0; + private int _restartTime = 0; + + public NoRestartZone(int id) + { + super(id); + } + + @Override + public void setParameter(String name, String value) + { + if (name.equalsIgnoreCase("restartAllowedTime")) + { + _restartAllowedTime = Integer.parseInt(value) * 1000; + } + else if (name.equalsIgnoreCase("restartTime")) + { + _restartTime = Integer.parseInt(value) * 1000; + } + else if (name.equalsIgnoreCase("instanceId")) + { + // Do nothing. + } + else + { + super.setParameter(name, value); + } + } + + @Override + protected void onEnter(Creature creature) + { + if (creature.isPlayer()) + { + creature.setInsideZone(ZoneId.NO_RESTART, true); + } + } + + @Override + protected void onExit(Creature creature) + { + if (creature.isPlayer()) + { + creature.setInsideZone(ZoneId.NO_RESTART, false); + } + } + + @Override + public void onPlayerLoginInside(Player player) + { + if (!isEnabled()) + { + return; + } + + final long currentTime = System.currentTimeMillis(); + if (((currentTime - player.getLastAccess()) > _restartTime) && ((currentTime - GameServer.getStartTime()) > _restartAllowedTime)) + { + player.teleToLocation(TeleportWhereType.TOWN); + } + } + + public int getRestartAllowedTime() + { + return _restartAllowedTime; + } + + public void setRestartAllowedTime(int time) + { + _restartAllowedTime = time; + } + + public int getRestartTime() + { + return _restartTime; + } + + public void setRestartTime(int time) + { + _restartTime = time; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/NoStoreZone.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/NoStoreZone.java new file mode 100644 index 0000000..2c89514 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/NoStoreZone.java @@ -0,0 +1,51 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.zone.type; + +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.zone.ZoneId; +import org.l2jmobius.gameserver.entity.zone.ZoneType; + +/** + * Zone where store is not allowed. + * @author fordfrog + */ +public class NoStoreZone extends ZoneType +{ + public NoStoreZone(int id) + { + super(id); + } + + @Override + protected void onEnter(Creature creature) + { + if (creature.isPlayer()) + { + creature.setInsideZone(ZoneId.NO_STORE, true); + } + } + + @Override + protected void onExit(Creature creature) + { + if (creature.isPlayer()) + { + creature.setInsideZone(ZoneId.NO_STORE, false); + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/NoSummonFriendZone.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/NoSummonFriendZone.java new file mode 100644 index 0000000..1785a84 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/NoSummonFriendZone.java @@ -0,0 +1,45 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.zone.type; + +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.zone.ZoneId; +import org.l2jmobius.gameserver.entity.zone.ZoneType; + +/** + * A simple no summon zone + * @author JIV + */ +public class NoSummonFriendZone extends ZoneType +{ + public NoSummonFriendZone(int id) + { + super(id); + } + + @Override + protected void onEnter(Creature creature) + { + creature.setInsideZone(ZoneId.NO_SUMMON_FRIEND, true); + } + + @Override + protected void onExit(Creature creature) + { + creature.setInsideZone(ZoneId.NO_SUMMON_FRIEND, false); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/NpcSpawnTerritory.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/NpcSpawnTerritory.java new file mode 100644 index 0000000..75b7f1f --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/NpcSpawnTerritory.java @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.zone.type; + +import java.util.ArrayList; +import java.util.List; + +import org.l2jmobius.gameserver.entity.Location; +import org.l2jmobius.gameserver.entity.zone.ZoneForm; +import org.l2jmobius.gameserver.geoengine.GeoEngine; + +/** + * Just dummy zone, needs only for geometry calculations + * @author GKR, Mobius + */ +public class NpcSpawnTerritory +{ + private final String _name; + private final ZoneForm _territory; + private List _bannedTerritories; + + public NpcSpawnTerritory(String name, ZoneForm territory) + { + _name = name; + _territory = territory; + } + + public void addBannedTerritory(ZoneForm territory) + { + if (_bannedTerritories == null) + { + _bannedTerritories = new ArrayList<>(1); + } + + _bannedTerritories.add(territory); + } + + public String getName() + { + return _name; + } + + public Location getRandomPoint() + { + int count = 0; // Prevent infinite loop. + Location location; + int randomX; + int randomY; + int randomZ; + + if (_bannedTerritories != null) + { + SEARCH: while (count++ < 100) + { + location = _territory.getRandomPoint(); + randomX = location.getX(); + randomY = location.getY(); + randomZ = location.getZ(); + + for (ZoneForm territory : _bannedTerritories) + { + if (territory.isInsideZone(randomX, randomY, randomZ)) + { + continue SEARCH; + } + } + + if (GeoEngine.getInstance().getHeight(randomX, randomY, randomZ) > _territory.getHighZ()) + { + continue; + } + + return location; + } + + count = 0; + SEARCH_NO_GEO: while (count++ < 100) + { + location = _territory.getRandomPoint(); + randomX = location.getX(); + randomY = location.getY(); + randomZ = location.getZ(); + + for (ZoneForm territory : _bannedTerritories) + { + if (territory.isInsideZone(randomX, randomY, randomZ)) + { + continue SEARCH_NO_GEO; + } + } + + return location; + } + } + + count = 0; + while (count++ < 100) + { + location = _territory.getRandomPoint(); + randomX = location.getX(); + randomY = location.getY(); + randomZ = location.getZ(); + + if (GeoEngine.getInstance().getHeight(randomX, randomY, randomZ) > _territory.getHighZ()) + { + continue; + } + + return location; + } + + return _territory.getRandomPoint(); + } + + public boolean isInsideZone(int x, int y, int z) + { + return _territory.isInsideZone(x, y, z); + } + + /** + * Tests containment on the XY plane only, ignoring the Z band.
        + * Banned territories are excluded, like getRandomPoint does. + * @param x + * @param y + * @return {@code true} if the point lies inside the territory polygon + */ + public boolean isInsideZone(int x, int y) + { + final int z = _territory.getLowZ(); + if (!_territory.isInsideZone(x, y, z)) + { + return false; + } + + if (_bannedTerritories != null) + { + for (ZoneForm territory : _bannedTerritories) + { + if (territory.isInsideZone(x, y, z)) + { + return false; + } + } + } + + return true; + } + + public int getHighZ() + { + return _territory.getHighZ(); + } + + public void visualizeZone(int z) + { + _territory.visualizeZone(z); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/OlympiadStadiumZone.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/OlympiadStadiumZone.java new file mode 100644 index 0000000..cf81906 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/OlympiadStadiumZone.java @@ -0,0 +1,117 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.zone.type; + +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.Playable; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.Summon; +import org.l2jmobius.gameserver.entity.actor.enums.player.TeleportWhereType; +import org.l2jmobius.gameserver.entity.zone.ZoneId; +import org.l2jmobius.gameserver.entity.zone.ZoneType; +import org.l2jmobius.gameserver.network.SystemMessageId; + +/** + * An olympiad stadium + * @author durgus + */ +public class OlympiadStadiumZone extends ZoneType +{ + private int _stadiumId; + + public OlympiadStadiumZone(int id) + { + super(id); + } + + @Override + public void setParameter(String name, String value) + { + if (name.equals("stadiumId")) + { + _stadiumId = Integer.parseInt(value); + } + else + { + super.setParameter(name, value); + } + } + + @Override + protected void onEnter(Creature character) + { + character.setInsideZone(ZoneId.PVP, true); + character.setInsideZone(ZoneId.NO_SUMMON_FRIEND, true); + character.setInsideZone(ZoneId.NO_LANDING, true); + character.setInsideZone(ZoneId.NO_RESTART, true); + character.setInsideZone(ZoneId.NO_BOOKMARK, true); + + if (character instanceof Player) + { + character.sendPacket(SystemMessageId.YOU_HAVE_ENTERED_A_COMBAT_ZONE); + } + + if (character instanceof Playable) + { + // Only participants, observers and GMs allowed. + final Player player = character.asPlayer(); + if ((player != null) && !player.isGM() && !player.isInOlympiadMode() && !player.inObserverMode()) + { + if (character instanceof Summon) + { + character.asSummon().unSummon(player); + } + + player.teleToLocation(TeleportWhereType.TOWN); + } + } + } + + @Override + protected void onExit(Creature character) + { + character.setInsideZone(ZoneId.PVP, false); + character.setInsideZone(ZoneId.NO_SUMMON_FRIEND, false); + character.setInsideZone(ZoneId.NO_LANDING, false); + character.setInsideZone(ZoneId.NO_RESTART, false); + character.setInsideZone(ZoneId.NO_BOOKMARK, false); + + if (character instanceof Player) + { + character.sendPacket(SystemMessageId.YOU_HAVE_LEFT_A_COMBAT_ZONE); + } + } + + @Override + public void onDieInside(Creature character) + { + } + + @Override + public void onReviveInside(Creature character) + { + } + + /** + * Returns this zones stadium id (if any) + * @return + */ + public int getStadiumId() + { + return _stadiumId; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/PeaceZone.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/PeaceZone.java new file mode 100644 index 0000000..6466b43 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/PeaceZone.java @@ -0,0 +1,72 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.zone.type; + +import org.l2jmobius.gameserver.config.GeneralConfig; +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.zone.ZoneId; +import org.l2jmobius.gameserver.entity.zone.ZoneType; + +/** + * A Peace Zone + * @author durgus + */ +public class PeaceZone extends ZoneType +{ + public PeaceZone(int id) + { + super(id); + } + + @Override + protected void onEnter(Creature creature) + { + if (creature.isPlayer()) + { + // PVP possible during siege, now for siege participants only. + // Could also check if this town is in siege, or if any siege is going on. + if ((creature.asPlayer().getSiegeState() != 0) && (GeneralConfig.PEACE_ZONE_MODE == 1)) + { + return; + } + } + + if (GeneralConfig.PEACE_ZONE_MODE != 2) + { + creature.setInsideZone(ZoneId.PEACE, true); + } + + if (!getAllowStore()) + { + creature.setInsideZone(ZoneId.NO_STORE, true); + } + } + + @Override + protected void onExit(Creature creature) + { + if (GeneralConfig.PEACE_ZONE_MODE != 2) + { + creature.setInsideZone(ZoneId.PEACE, false); + } + + if (!getAllowStore()) + { + creature.setInsideZone(ZoneId.NO_STORE, false); + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/ResidenceHallTeleportZone.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/ResidenceHallTeleportZone.java new file mode 100644 index 0000000..8f361ff --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/ResidenceHallTeleportZone.java @@ -0,0 +1,90 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.zone.type; + +import java.util.concurrent.ScheduledFuture; + +import org.l2jmobius.commons.threads.ThreadPool; +import org.l2jmobius.commons.util.Rnd; +import org.l2jmobius.gameserver.entity.Location; +import org.l2jmobius.gameserver.entity.actor.Player; + +/** + * Teleport residence zone for clan hall sieges + * @author BiggBoss + */ +public class ResidenceHallTeleportZone extends ResidenceTeleportZone +{ + private int _id; + private ScheduledFuture _teleTask; + + /** + * @param id + */ + public ResidenceHallTeleportZone(int id) + { + super(id); + } + + @Override + public void setParameter(String name, String value) + { + if (name.equals("residenceZoneId")) + { + _id = Integer.parseInt(value); + } + else + { + super.setParameter(name, value); + } + } + + public int getResidenceZoneId() + { + return _id; + } + + public synchronized void checkTeleportTask() + { + if ((_teleTask == null) || _teleTask.isDone()) + { + _teleTask = ThreadPool.schedule(new TeleportTask(), 30000); + } + } + + protected class TeleportTask implements Runnable + { + @Override + public void run() + { + final int index = getSpawns().size() > 1 ? Rnd.get(getSpawns().size()) : 0; + final Location loc = getSpawns().get(index); + if (loc == null) + { + throw new NullPointerException(); + } + + for (Player pc : getPlayersInside()) + { + if (pc != null) + { + pc.teleToLocation(loc, false); + } + } + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/ResidenceTeleportZone.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/ResidenceTeleportZone.java new file mode 100644 index 0000000..c81d067 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/ResidenceTeleportZone.java @@ -0,0 +1,78 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.zone.type; + +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.zone.ZoneId; +import org.l2jmobius.gameserver.entity.zone.ZoneRespawn; + +/** + * based on Kerberos work for custom CastleTeleportZone + * @author Nyaran + */ +public class ResidenceTeleportZone extends ZoneRespawn +{ + private int _residenceId; + + public ResidenceTeleportZone(int id) + { + super(id); + } + + @Override + public void setParameter(String name, String value) + { + if (name.equals("residenceId")) + { + _residenceId = Integer.parseInt(value); + } + else + { + super.setParameter(name, value); + } + } + + @Override + protected void onEnter(Creature creature) + { + creature.setInsideZone(ZoneId.NO_SUMMON_FRIEND, true); // FIXME: Custom ? + } + + @Override + protected void onExit(Creature creature) + { + creature.setInsideZone(ZoneId.NO_SUMMON_FRIEND, false); // FIXME: Custom ? + } + + @Override + public void oustAllPlayers() + { + for (Player player : getPlayersInside()) + { + if ((player != null) && player.isOnline()) + { + player.teleToLocation(getSpawnLoc(), 200); + } + } + } + + public int getResidenceId() + { + return _residenceId; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/ResidenceZone.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/ResidenceZone.java new file mode 100644 index 0000000..aa6021b --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/ResidenceZone.java @@ -0,0 +1,56 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.zone.type; + +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.zone.ZoneRespawn; + +/** + * @author xban1x + */ +public abstract class ResidenceZone extends ZoneRespawn +{ + private int _residenceId; + + protected ResidenceZone(int id) + { + super(id); + } + + public void banishForeigners(int owningClanId) + { + for (Player temp : getPlayersInside()) + { + if ((owningClanId != 0) && (temp.getClanId() == owningClanId)) + { + continue; + } + + temp.teleToLocation(getBanishSpawnLoc(), true); + } + } + + protected void setResidenceId(int residenceId) + { + _residenceId = residenceId; + } + + public int getResidenceId() + { + return _residenceId; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/RespawnZone.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/RespawnZone.java new file mode 100644 index 0000000..3f70fe5 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/RespawnZone.java @@ -0,0 +1,64 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.zone.type; + +import java.util.EnumMap; +import java.util.Map; + +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.enums.creature.Race; +import org.l2jmobius.gameserver.entity.zone.ZoneType; + +/** + * Respawn zone implementation. + * @author Nyaran + */ +public class RespawnZone extends ZoneType +{ + private final Map _raceRespawnPoint = new EnumMap<>(Race.class); + + public RespawnZone(int id) + { + super(id); + } + + @Override + protected void onEnter(Creature creature) + { + } + + @Override + protected void onExit(Creature creature) + { + } + + public void addRaceRespawnPoint(String race, String point) + { + _raceRespawnPoint.put(Race.valueOf(race), point); + } + + public Map getAllRespawnPoints() + { + return _raceRespawnPoint; + } + + public String getRespawnPoint(Player player) + { + return _raceRespawnPoint.get(player.getRace()); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/ScriptZone.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/ScriptZone.java new file mode 100644 index 0000000..6ab86ff --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/ScriptZone.java @@ -0,0 +1,45 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.zone.type; + +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.zone.ZoneId; +import org.l2jmobius.gameserver.entity.zone.ZoneType; + +/** + * A scripted zone... Creation of such a zone should require somekind of script reference which can handle onEnter() / onExit() + * @author durgus + */ +public class ScriptZone extends ZoneType +{ + public ScriptZone(int id) + { + super(id); + } + + @Override + protected void onEnter(Creature creature) + { + creature.setInsideZone(ZoneId.SCRIPT, true); + } + + @Override + protected void onExit(Creature creature) + { + creature.setInsideZone(ZoneId.SCRIPT, false); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/SiegableHallZone.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/SiegableHallZone.java new file mode 100644 index 0000000..9cc09d7 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/SiegableHallZone.java @@ -0,0 +1,70 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.zone.type; + +import java.util.ArrayList; +import java.util.List; + +import org.l2jmobius.gameserver.entity.Location; +import org.l2jmobius.gameserver.entity.actor.Player; + +/** + * @author BiggBoss + */ +public class SiegableHallZone extends ClanHallZone +{ + private List _challengerLocations; + + public SiegableHallZone(int id) + { + super(id); + } + + @Override + public void parseLoc(int x, int y, int z, String type) + { + if ((type != null) && type.equals("challenger")) + { + if (_challengerLocations == null) + { + _challengerLocations = new ArrayList<>(); + } + + _challengerLocations.add(new Location(x, y, z)); + } + else + { + super.parseLoc(x, y, z, type); + } + } + + public List getChallengerSpawns() + { + return _challengerLocations; + } + + public void banishNonSiegeParticipants() + { + for (Player player : getPlayersInside()) + { + if ((player != null) && player.isInHideoutSiege()) + { + player.teleToLocation(getBanishSpawnLoc(), true); + } + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/SiegeZone.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/SiegeZone.java new file mode 100644 index 0000000..7a19487 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/SiegeZone.java @@ -0,0 +1,370 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.zone.type; + +import org.l2jmobius.gameserver.config.FeatureConfig; +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.Summon; +import org.l2jmobius.gameserver.entity.actor.enums.player.MountType; +import org.l2jmobius.gameserver.entity.actor.enums.player.TeleportWhereType; +import org.l2jmobius.gameserver.entity.zone.AbstractZoneSettings; +import org.l2jmobius.gameserver.entity.zone.ZoneId; +import org.l2jmobius.gameserver.entity.zone.ZoneType; +import org.l2jmobius.gameserver.managers.CHSiegeManager; +import org.l2jmobius.gameserver.managers.CastleManager; +import org.l2jmobius.gameserver.managers.ZoneManager; +import org.l2jmobius.gameserver.mechanics.siege.Castle; +import org.l2jmobius.gameserver.mechanics.siege.Siegable; +import org.l2jmobius.gameserver.mechanics.siege.clanhalls.SiegableHall; +import org.l2jmobius.gameserver.network.SystemMessageId; + +/** + * A siege zone + * @author durgus, Skache + */ +public class SiegeZone extends ZoneType +{ + private static final int DISMOUNT_DELAY = 5; + + public SiegeZone(int id) + { + super(id); + AbstractZoneSettings settings = ZoneManager.getSettings(getName()); + if (settings == null) + { + settings = new Settings(); + } + + setSettings(settings); + } + + public class Settings extends AbstractZoneSettings + { + private int _siegableId = -1; + private Siegable _siege = null; + private boolean _isActiveSiege = false; + + protected Settings() + { + } + + public int getSiegeableId() + { + return _siegableId; + } + + protected void setSiegeableId(int id) + { + _siegableId = id; + } + + public Siegable getSiege() + { + return _siege; + } + + public void setSiege(Siegable s) + { + _siege = s; + } + + public boolean isActiveSiege() + { + return _isActiveSiege; + } + + public void setActiveSiege(boolean value) + { + _isActiveSiege = value; + } + + @Override + public void clear() + { + _siegableId = -1; + _siege = null; + _isActiveSiege = false; + } + } + + @Override + public Settings getSettings() + { + return (Settings) super.getSettings(); + } + + @Override + public void setParameter(String name, String value) + { + if (name.equals("castleId")) + { + if (getSettings().getSiegeableId() != -1) + { + throw new IllegalArgumentException("Siege object already defined!"); + } + + getSettings().setSiegeableId(Integer.parseInt(value)); + } + else if (name.equals("fortId")) + { + if (getSettings().getSiegeableId() != -1) + { + throw new IllegalArgumentException("Siege object already defined!"); + } + + getSettings().setSiegeableId(Integer.parseInt(value)); + } + else if (name.equals("clanHallId")) + { + if (getSettings().getSiegeableId() != -1) + { + throw new IllegalArgumentException("Siege object already defined!"); + } + + getSettings().setSiegeableId(Integer.parseInt(value)); + final SiegableHall hall = CHSiegeManager.getInstance().getConquerableHalls().get(getSettings().getSiegeableId()); + if (hall != null) + { + hall.setSiegeZone(this); + } + else + { + LOGGER.warning("SiegeZone: Siegable clan hall with id " + value + " does not exist!"); + } + } + else + { + super.setParameter(name, value); + } + } + + @Override + protected void onEnter(Creature creature) + { + if (!getSettings().isActiveSiege()) + { + return; + } + + creature.setInsideZone(ZoneId.PVP, true); + creature.setInsideZone(ZoneId.SIEGE, true); + creature.setInsideZone(ZoneId.NO_SUMMON_FRIEND, true); // FIXME: Custom ? + + if (!creature.isPlayer()) + { + return; + } + + final Player player = creature.asPlayer(); + if (player.isRegisteredOnThisSiegeField(getSettings().getSiegeableId())) + { + player.setInSiege(true); // in siege + if (getSettings().getSiege().giveFame() && (getSettings().getSiege().getFameFrequency() > 0)) + { + player.startFameTask(getSettings().getSiege().getFameFrequency() * 1000, getSettings().getSiege().getFameAmount()); + } + } + + creature.sendPacket(SystemMessageId.YOU_HAVE_ENTERED_A_COMBAT_ZONE); + + if (FeatureConfig.ALLOW_MOUNTS_DURING_SIEGE) + { + return; + } + + if (player.isGM()) + { + player.sendMessage("You have entered a siege zone. GM dismount restrictions are ignored."); + return; + } + + // Check if wyvern riding is disallowed during siege and player is currently mounted on a wyvern. + final Castle castle = CastleManager.getInstance().getCastleById(getSettings().getSiegeableId()); + final boolean isCastleLord = (castle != null) && player.isClanLeader() && (player.getClanId() == castle.getOwnerId()); + if (player.getMountType() == MountType.WYVERN) + { + // Only allow castle lord to ride wyvern inside siege zone. + if (!isCastleLord) + { + player.sendPacket(SystemMessageId.THIS_AREA_CANNOT_BE_ENTERED_WHILE_MOUNTED_ATOP_OF_A_WYVERN_YOU_WILL_BE_DISMOUNTED_FROM_YOUR_WYVERN_IF_YOU_DO_NOT_LEAVE); + player.enteredNoLanding(DISMOUNT_DELAY); + } + } + // If normal mounts (excluding wyvern) are disallowed during siege and player is mounted on such a mount. + else if (player.isMounted() && (player.getMountType() != MountType.WYVERN)) + { + // Allow castle lord to be mounted on a strider during siege, disallow others. + // This is because, on retail servers, the inside of a castle is a no-fly zone during a siege. + // However, to be able to **summon and ride a wyvern**, the castle lord must first be mounted on a strider. + // So, the castle lord is permitted to mount a strider during the siege, even when mounts are generally disallowed. + // This enables the castle lord to meet the precondition to summon their wyvern inside the siege zone. + final boolean isCastleLordOnStrider = (player.getMountType() == MountType.STRIDER) && isCastleLord; + if (!isCastleLordOnStrider) + { + // Dismount the player if they are not allowed to be mounted in siege zone. + player.dismount(); + } + } + } + + @Override + protected void onExit(Creature creature) + { + creature.setInsideZone(ZoneId.PVP, false); + creature.setInsideZone(ZoneId.SIEGE, false); + creature.setInsideZone(ZoneId.NO_SUMMON_FRIEND, false); // FIXME: Custom ? + if (getSettings().isActiveSiege() && creature.isPlayer()) + { + final Player player = creature.asPlayer(); + creature.sendPacket(SystemMessageId.YOU_HAVE_LEFT_A_COMBAT_ZONE); + if (player.getMountType() == MountType.WYVERN) + { + player.exitedNoLanding(); + } + + // Set pvp flag. + if (player.getPvpFlag() == 0) + { + player.startPvPFlag(); + } + } + + if (!creature.isPlayer()) + { + return; + } + + final Player player = creature.asPlayer(); + player.stopFameTask(); + player.setInSiege(false); + + final Summon summon = player.getSummon(); + if (summon != null) + { + summon.abortAttack(); + summon.abortCast(); + summon.stopAllEffects(); + summon.unSummon(player); + } + } + + @Override + public void onPlayerLogoutInside(Player player) + { + if (player.getClanId() != getSettings().getSiegeableId()) + { + player.teleToLocation(TeleportWhereType.TOWN); + } + } + + public void updateZoneStatusForCharactersInside() + { + if (getSettings().isActiveSiege()) + { + for (Creature creature : getCharactersInside()) + { + if (creature != null) + { + onEnter(creature); + } + } + } + else + { + Player player; + for (Creature creature : getCharactersInside()) + { + if (creature == null) + { + continue; + } + + creature.setInsideZone(ZoneId.PVP, false); + creature.setInsideZone(ZoneId.SIEGE, false); + creature.setInsideZone(ZoneId.NO_SUMMON_FRIEND, false); + + if (creature.isPlayer()) + { + player = creature.asPlayer(); + creature.sendPacket(SystemMessageId.YOU_HAVE_LEFT_A_COMBAT_ZONE); + player.stopFameTask(); + if (player.getMountType() == MountType.WYVERN) + { + player.exitedNoLanding(); + } + } + } + } + } + + /** + * Sends a message to all players in this zone + * @param message + */ + public void announceToPlayers(String message) + { + for (Player player : getPlayersInside()) + { + if (player != null) + { + player.sendMessage(message); + } + } + } + + public int getSiegeObjectId() + { + return getSettings().getSiegeableId(); + } + + public boolean isActive() + { + return getSettings().isActiveSiege(); + } + + public void setActive(boolean value) + { + getSettings().setActiveSiege(value); + } + + public void setSiegeInstance(Siegable siege) + { + getSettings().setSiege(siege); + } + + /** + * Removes all foreigners from the zone + * @param owningClanId + */ + public void banishForeigners(int owningClanId) + { + for (Player temp : getPlayersInside()) + { + if (temp.getClanId() == owningClanId) + { + continue; + } + + temp.teleToLocation(TeleportWhereType.TOWN); + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/SwampZone.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/SwampZone.java new file mode 100644 index 0000000..2684908 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/SwampZone.java @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.zone.type; + +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.zone.ZoneId; +import org.l2jmobius.gameserver.entity.zone.ZoneType; +import org.l2jmobius.gameserver.managers.CastleManager; +import org.l2jmobius.gameserver.mechanics.siege.Castle; +import org.l2jmobius.gameserver.network.serverpackets.OnEventTrigger; + +/** + * another type of zone where your speed is changed + * @author Kerberos, Mobius + */ +public class SwampZone extends ZoneType +{ + private double _move_bonus; + private int _castleId; + private Castle _castle; + private int _eventId; + + public SwampZone(int id) + { + super(id); + + // Setup default speed reduce (in %). + _move_bonus = 0.5; + + // no castle by default + _castleId = 0; + _castle = null; + + // no event by default + _eventId = 0; + } + + @Override + public void setParameter(String name, String value) + { + if (name.equals("move_bonus")) + { + _move_bonus = Double.parseDouble(value); + } + else if (name.equals("castleId")) + { + _castleId = Integer.parseInt(value); + } + else if (name.equals("eventId")) + { + _eventId = Integer.parseInt(value); + } + else + { + super.setParameter(name, value); + } + } + + private Castle getCastle() + { + if ((_castleId > 0) && (_castle == null)) + { + _castle = CastleManager.getInstance().getCastleById(_castleId); + } + + return _castle; + } + + @Override + protected void onEnter(Creature creature) + { + if (getCastle() != null) + { + // castle zones active only during siege + if (!getCastle().getSiege().isInProgress()) + { + return; + } + + // defenders not affected + final Player player = creature.asPlayer(); + if ((player != null) && player.isInSiege() && (player.getSiegeState() == 2)) + { + return; + } + } + + creature.setInsideZone(ZoneId.SWAMP, true); + if (creature.isPlayer()) + { + if (_eventId > 0) + { + creature.sendPacket(new OnEventTrigger(_eventId, true)); + } + + creature.asPlayer().broadcastUserInfo(); + } + } + + @Override + protected void onExit(Creature creature) + { + // don't broadcast info if not needed + if (creature.isInsideZone(ZoneId.SWAMP)) + { + creature.setInsideZone(ZoneId.SWAMP, false); + if (creature.isPlayer()) + { + if (_eventId > 0) + { + creature.sendPacket(new OnEventTrigger(_eventId, false)); + } + + if (!creature.isTeleporting()) + { + creature.asPlayer().broadcastUserInfo(); + } + } + } + } + + public double getMoveBonus() + { + return _move_bonus; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/TownZone.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/TownZone.java new file mode 100644 index 0000000..89e3652 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/TownZone.java @@ -0,0 +1,85 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.entity.zone.type; + +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.zone.ZoneId; +import org.l2jmobius.gameserver.entity.zone.ZoneType; + +/** + * A Town zone + * @author durgus + */ +public class TownZone extends ZoneType +{ + private int _townId; + private int _taxById; + + public TownZone(int id) + { + super(id); + + _taxById = 0; + } + + @Override + public void setParameter(String name, String value) + { + if (name.equals("townId")) + { + _townId = Integer.parseInt(value); + } + else if (name.equals("taxById")) + { + _taxById = Integer.parseInt(value); + } + else + { + super.setParameter(name, value); + } + } + + @Override + protected void onEnter(Creature creature) + { + creature.setInsideZone(ZoneId.TOWN, true); + } + + @Override + protected void onExit(Creature creature) + { + creature.setInsideZone(ZoneId.TOWN, false); + } + + /** + * Returns this zones town id (if any) + * @return + */ + public int getTownId() + { + return _townId; + } + + /** + * Returns this town zones castle id + * @return + */ + public int getTaxById() + { + return _taxById; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/WaterZone.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/WaterZone.java new file mode 100644 index 0000000..d0aac7b --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/entity/zone/type/WaterZone.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.entity.zone.type; + +import org.l2jmobius.gameserver.entity.World; +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.zone.ZoneId; +import org.l2jmobius.gameserver.entity.zone.ZoneType; +import org.l2jmobius.gameserver.network.serverpackets.AbstractNpcInfo; +import org.l2jmobius.gameserver.network.serverpackets.FakePlayerInfo; +import org.l2jmobius.gameserver.network.serverpackets.ServerObjectInfo; + +public class WaterZone extends ZoneType +{ + public WaterZone(int id) + { + super(id); + } + + @Override + protected void onEnter(Creature creature) + { + creature.setInsideZone(ZoneId.WATER, true); + + // TODO: update to only send speed status when that packet is known + if (creature.isPlayer()) + { + creature.asPlayer().broadcastUserInfo(); + } + else if (creature.isNpc()) + { + World.forEachVisibleObject(creature, Player.class, player -> + { + if (creature.isFakePlayer()) + { + player.sendPacket(new FakePlayerInfo(creature.asNpc())); + } + else if (creature.getRunSpeed() == 0) + { + player.sendPacket(new ServerObjectInfo(creature.asNpc(), player)); + } + else + { + player.sendPacket(new AbstractNpcInfo.NpcInfo(creature.asNpc(), player)); + } + }); + } + } + + @Override + protected void onExit(Creature creature) + { + creature.setInsideZone(ZoneId.WATER, false); + + // TODO: update to only send speed status when that packet is known + if (creature.isPlayer()) + { + // Mobius: Attempt to stop water task. + if (!creature.isInsideZone(ZoneId.WATER)) + { + creature.asPlayer().stopWaterTask(); + } + + if (!creature.isTeleporting()) + { + creature.asPlayer().broadcastUserInfo(); + } + } + else if (creature.isNpc()) + { + World.forEachVisibleObject(creature, Player.class, player -> + { + if (creature.isFakePlayer()) + { + player.sendPacket(new FakePlayerInfo(creature.asNpc())); + } + else if (creature.getRunSpeed() == 0) + { + player.sendPacket(new ServerObjectInfo(creature.asNpc(), player)); + } + else + { + player.sendPacket(new AbstractNpcInfo.NpcInfo(creature.asNpc(), player)); + } + }); + } + } + + public int getWaterZ() + { + return getZone().getHighZ(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java new file mode 100644 index 0000000..4028488 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/GeoEngine.java @@ -0,0 +1,911 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.geoengine; + +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.channels.FileChannel.MapMode; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.concurrent.atomic.AtomicReferenceArray; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.gameserver.config.GeoEngineConfig; +import org.l2jmobius.gameserver.data.xml.DoorData; +import org.l2jmobius.gameserver.data.xml.FenceData; +import org.l2jmobius.gameserver.entity.Location; +import org.l2jmobius.gameserver.entity.World; +import org.l2jmobius.gameserver.entity.WorldObject; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; +import org.l2jmobius.gameserver.geoengine.geodata.IRegion; +import org.l2jmobius.gameserver.geoengine.geodata.regions.NullRegion; +import org.l2jmobius.gameserver.geoengine.geodata.regions.Region; +import org.l2jmobius.gameserver.geoengine.util.GridLineIterator2D; +import org.l2jmobius.gameserver.geoengine.util.GridLineIterator3D; +import org.l2jmobius.gameserver.interfaces.ILocational; +import org.l2jmobius.gameserver.util.GeoUtils; + +/** + * The {@code GeoEngine} class is responsible for managing geospatial data used in the game.
        + * It handles geodata loading, movement validation, line-of-sight (LOS) checks and other spatial calculations related to the game's 3D world. + *

        + * Key functionalities include: + *

          + *
        • Loading geodata files for specific regions.
        • + *
        • Converting between world and geodata coordinates.
        • + *
        • Validating movement paths and detecting obstacles.
        • + *
        • Checking line-of-sight (LOS) between two positions.
        • + *
        • Retrieving terrain height and handling elevation-related calculations.
        • + *
        + * @author -Nemesiss-, HorridoJoho, Mobius + */ +public class GeoEngine +{ + private static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); + + // Constants. + public static final String FILE_NAME_FORMAT = "%d_%d.l2j"; + private static final int ELEVATED_SEE_OVER_DISTANCE = 2; + private static final int MAX_SEE_OVER_HEIGHT = 48; + private static final int SPAWN_Z_DELTA_LIMIT = 100; + private static final int WORLD_MIN_X = -655360; + private static final int WORLD_MIN_Y = -589824; + private static final int GEO_REGIONS_X = 32; + private static final int GEO_REGIONS_Y = 32; + private static final int GEO_REGIONS = GEO_REGIONS_X * GEO_REGIONS_Y; + private static final int COORDINATE_SCALE = 16; + private static final int COORDINATE_OFFSET = 8; + private static final int HEIGHT_INCREASE_LIMIT = 40; + private static final int SPAWN_HEIGHT_OFFSET = 20; + /** When the height check in pathing fails on a single cell, accept the move if any NSWE neighbour of that cell has a layer within this many game units of the source Z - the cell is then treated as a continuous-surface gap. */ + private static final int PATH_CONTINUITY_TOLERANCE = 16; + + // Region Management. + private static final AtomicReferenceArray REGIONS = new AtomicReferenceArray<>(GEO_REGIONS); + + protected GeoEngine() + { + // Initially set all regions to NullRegion. + for (int i = 0; i < GEO_REGIONS; i++) + { + REGIONS.set(i, NullRegion.INSTANCE); + } + + int loadedRegions = 0; + try + { + for (int regionX = World.TILE_X_MIN; regionX <= World.TILE_X_MAX; regionX++) + { + for (int regionY = World.TILE_Y_MIN; regionY <= World.TILE_Y_MAX; regionY++) + { + final Path geoFilePath = GeoEngineConfig.GEODATA_PATH.resolve(String.format(FILE_NAME_FORMAT, regionX, regionY)); + if (Files.exists(geoFilePath)) + { + try + { + // LOGGER.info(getClass().getSimpleName() + ": Loading " + geoFilePath.getFileName() + "..."); + loadRegion(geoFilePath, regionX, regionY); + loadedRegions++; + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Failed to load " + geoFilePath.getFileName() + "!", e); + } + } + } + } + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, getClass().getSimpleName() + ": Critical error during geodata initialization!", e); + System.exit(1); + } + + LOGGER.info(getClass().getSimpleName() + ": Loaded " + loadedRegions + " regions."); + + // Avoid wrong configuration when no files are loaded. + if ((loadedRegions == 0) && (GeoEngineConfig.PATHFINDING > 0)) + { + GeoEngineConfig.PATHFINDING = 0; + LOGGER.info(getClass().getSimpleName() + ": Pathfinding is disabled."); + } + } + + /** + * Loads geodata for a specific region from the provided file path. + * @param filePath The path to the geodata file + * @param regionX The X coordinate of the region + * @param regionY The Y coordinate of the region + * @throws IOException if an error occurs while reading the file + */ + private void loadRegion(Path filePath, int regionX, int regionY) throws IOException + { + final int regionOffset = (regionX * GEO_REGIONS_Y) + regionY; + try (RandomAccessFile randomAccessFile = new RandomAccessFile(filePath.toFile(), "r")) + { + REGIONS.set(regionOffset, new Region(randomAccessFile.getChannel().map(MapMode.READ_ONLY, 0, randomAccessFile.length()).order(ByteOrder.LITTLE_ENDIAN))); + } + } + + /** + * Reloads a specific geodata region from file. + * @param regionX The X coordinate of the region + * @param regionY The Y coordinate of the region + * @return true if reloaded successfully, false otherwise + */ + public boolean reloadRegion(int regionX, int regionY) + { + final Path geoFilePath = GeoEngineConfig.GEODATA_PATH.resolve(String.format(FILE_NAME_FORMAT, regionX, regionY)); + if (!Files.exists(geoFilePath)) + { + LOGGER.warning(getClass().getSimpleName() + ": Cannot reload, file not found for region " + regionX + "_" + regionY); + return false; + } + + final int regionOffset = (regionX * GEO_REGIONS_Y) + regionY; + try + { + final IRegion region = REGIONS.get(regionOffset); + if (region instanceof Region) + { + final ByteBuffer buffer = ByteBuffer.wrap(Files.readAllBytes(geoFilePath)).order(ByteOrder.LITTLE_ENDIAN); + ((Region) region).load(buffer); + LOGGER.info(getClass().getSimpleName() + ": Reloaded region " + regionX + "_" + regionY + " from bytes."); + return true; + } + + // Not a real region? fallback load. + loadRegion(geoFilePath, regionX, regionY); + LOGGER.info(getClass().getSimpleName() + ": Replaced NullRegion with new region " + regionX + "_" + regionY); + return true; + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Failed to reload region " + regionX + "_" + regionY + "!", e); + return false; + } + } + + /** + * Sets a geodata region at the specified coordinates. + * @param regionX The X coordinate of the region + * @param regionY The Y coordinate of the region + * @param region The geodata region to set + */ + public void setRegion(int regionX, int regionY, Region region) + { + final int regionOffset = (regionX * GEO_REGIONS_Y) + regionY; + REGIONS.set(regionOffset, region); + } + + /** + * Retrieves the geodata region for the specified coordinates. + * @param geoX The geodata X coordinate + * @param geoY The geodata Y coordinate + * @return the geodata region + */ + public IRegion getRegion(int geoX, int geoY) + { + return REGIONS.get(((geoX / IRegion.REGION_CELLS_X) * GEO_REGIONS_Y) + (geoY / IRegion.REGION_CELLS_Y)); + } + + /** + * Checks if geodata is available at the specified coordinates. + * @param geoX The geodata X coordinate + * @param geoY The geodata Y coordinate + * @return {@code true} if geodata is available, {@code false} otherwise + */ + public boolean hasGeoPos(int geoX, int geoY) + { + return getRegion(geoX, geoY).hasGeo(); + } + + /** + * Checks if movement is possible in the specified direction (NSWE) from the given geodata coordinates and height. + * @param geoX The geodata X coordinate + * @param geoY The geodata Y coordinate + * @param worldZ The world Z coordinate + * @param nswe The direction to check + * @return {@code true} if movement is possible, {@code false} otherwise + */ + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return getRegion(geoX, geoY).checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + /** + * Checks if movement is possible in the specified direction (NSWE) from the given geodata coordinates and height, considering anti-corner-cut logic. + * @param geoX The geodata X coordinate + * @param geoY The geodata Y coordinate + * @param worldZ The world Z coordinate + * @param nswe The direction to check + * @return {@code true} if movement is possible, {@code false} otherwise + */ + public boolean checkNearestNsweAntiCornerCut(int geoX, int geoY, int worldZ, int nswe) + { + boolean canMove = true; + + final IRegion region = getRegion(geoX, geoY); + if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) + { + canMove = region.checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_EAST) && region.checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_NORTH); + } + + if (canMove && ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST)) + { + canMove = region.checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_WEST) && region.checkNearestNswe(geoX - 1, geoY, worldZ, Cell.NSWE_NORTH); + } + + if (canMove && ((nswe & Cell.NSWE_SOUTH_EAST) == Cell.NSWE_SOUTH_EAST)) + { + canMove = region.checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_EAST) && region.checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_SOUTH); + } + + if (canMove && ((nswe & Cell.NSWE_SOUTH_WEST) == Cell.NSWE_SOUTH_WEST)) + { + canMove = region.checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_WEST) && region.checkNearestNswe(geoX - 1, geoY, worldZ, Cell.NSWE_SOUTH); + } + + return canMove && region.checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + /** + * Sets the nearest NSWE data at the specified coordinates and height. + * @param geoX The geodata X coordinate + * @param geoY The geodata Y coordinate + * @param worldZ The world Z coordinate + * @param nswe The direction data to set + */ + public void setNearestNswe(int geoX, int geoY, int worldZ, byte nswe) + { + getRegion(geoX, geoY).setNearestNswe(geoX, geoY, worldZ, nswe); + } + + /** + * Removes the nearest NSWE data at the specified coordinates and height. + * @param geoX The geodata X coordinate + * @param geoY The geodata Y coordinate + * @param worldZ The world Z coordinate + * @param nswe The direction data to remove + */ + public void unsetNearestNswe(int geoX, int geoY, int worldZ, byte nswe) + { + getRegion(geoX, geoY).unsetNearestNswe(geoX, geoY, worldZ, nswe); + } + + /** + * Retrieves the nearest Z coordinate at the specified geodata coordinates and height. + * @param geoX The geodata X coordinate + * @param geoY The geodata Y coordinate + * @param worldZ The world Z coordinate + * @return the nearest Z coordinate + */ + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNearestZ(geoX, geoY, worldZ); + } + + /** + * Retrieves the next lower Z coordinate at the specified geodata coordinates and height. + * @param geoX The geodata X coordinate + * @param geoY The geodata Y coordinate + * @param worldZ The world Z coordinate + * @return the next lower Z coordinate + */ + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNextLowerZ(geoX, geoY, worldZ); + } + + /** + * Retrieves the next higher Z coordinate at the specified geodata coordinates and height. + * @param geoX The geodata X coordinate + * @param geoY The geodata Y coordinate + * @param worldZ The world Z coordinate + * @return the next higher Z coordinate + */ + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return getRegion(geoX, geoY).getNextHigherZ(geoX, geoY, worldZ); + } + + /** + * Converts a world X coordinate to a geodata X coordinate. + * @param worldX The world X coordinate + * @return the corresponding geodata X coordinate + */ + public static int getGeoX(int worldX) + { + return (worldX - WORLD_MIN_X) / COORDINATE_SCALE; + } + + /** + * Converts a world Y coordinate to a geodata Y coordinate. + * @param worldY The world Y coordinate + * @return the corresponding geodata Y coordinate + */ + public static int getGeoY(int worldY) + { + return (worldY - WORLD_MIN_Y) / COORDINATE_SCALE; + } + + /** + * Converts a geodata X coordinate to a world X coordinate. + * @param geoX The geodata X coordinate + * @return the corresponding world X coordinate + */ + public static int getWorldX(int geoX) + { + return (geoX * COORDINATE_SCALE) + WORLD_MIN_X + COORDINATE_OFFSET; + } + + /** + * Converts a geodata Y coordinate to a world Y coordinate. + * @param geoY The geodata Y coordinate + * @return the corresponding world Y coordinate + */ + public static int getWorldY(int geoY) + { + return (geoY * COORDINATE_SCALE) + WORLD_MIN_Y + COORDINATE_OFFSET; + } + + /** + * Gets the height at the specified coordinates. + * @param x The x coordinate + * @param y The y coordinate + * @param z The z coordinate + * @return the height + */ + public int getHeight(int x, int y, int z) + { + return getNearestZ(getGeoX(x), getGeoY(y), z); + } + + /** + * Gets the spawn height at the specified coordinates.
        + * Calculates appropriate spawn position based on terrain height and spawn limits. + * @param x The x coordinate + * @param y The y coordinate + * @param z The z coordinate + * @return the spawn height + */ + public int getSpawnHeight(int x, int y, int z) + { + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + + if (!hasGeoPos(geoX, geoY)) + { + return z; + } + + final int nextLowerZ = getNextLowerZ(geoX, geoY, z + SPAWN_HEIGHT_OFFSET); + return Math.abs(nextLowerZ - z) <= SPAWN_Z_DELTA_LIMIT ? nextLowerZ : z; + } + + /** + * Gets the spawn height at the specified location. + * @param location The location + * @return the spawn height + */ + public int getSpawnHeight(Location location) + { + return getSpawnHeight(location.getX(), location.getY(), location.getZ()); + } + + /** + * Checks if creature can see target.
        + * Doors as target always return true and checks doors between positions. + * @param creature The creature + * @param target The target + * @return {@code true} if the creature can see the target (LOS), {@code false} otherwise + */ + public boolean canSeeTarget(WorldObject creature, WorldObject target) + { + return (target != null) && (target.isDoor() || target.isArtefact() || canSeeTarget(creature.getX(), creature.getY(), creature.getZ(), creature.getInstanceId(), target.getX(), target.getY(), target.getZ(), target.getInstanceId())); + } + + /** + * Checks if creature can see target at the given world position. + * @param creature The creature + * @param worldPosition The world position + * @return {@code true} if the creature can see the target at the given world position, {@code false} otherwise + */ + public boolean canSeeTarget(WorldObject creature, ILocational worldPosition) + { + return canSeeTarget(creature.getX(), creature.getY(), creature.getZ(), worldPosition.getX(), worldPosition.getY(), worldPosition.getZ(), creature.getInstanceId()); + } + + /** + * Checks if position can see target position with instance validation. + * @param x The x coordinate + * @param y The y coordinate + * @param z The z coordinate + * @param instanceId the instance id + * @param targetX The target's x coordinate + * @param targetY The target's y coordinate + * @param targetZ The target's z coordinate + * @param targetInstanceId the target's instance id + * @return true if there is line of sight between positions in same instance, false otherwise + */ + public boolean canSeeTarget(int x, int y, int z, int instanceId, int targetX, int targetY, int targetZ, int targetInstanceId) + { + return (instanceId == targetInstanceId) && canSeeTarget(x, y, z, targetX, targetY, targetZ, instanceId); + } + + /** + * Checks if position can see target position with door and fence validation. + * @param x The x coordinate + * @param y The y coordinate + * @param z The z coordinate + * @param targetX The target's x coordinate + * @param targetY The target's y coordinate + * @param targetZ The target's z coordinate + * @param instanceId the instance id + * @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise + */ + public boolean canSeeTarget(int x, int y, int z, int targetX, int targetY, int targetZ, int instanceId) + { + // Door checks. + if (DoorData.getInstance().checkIfDoorsBetween(x, y, z, targetX, targetY, targetZ, instanceId, true)) + { + return false; + } + + // Fence checks. + if (FenceData.getInstance().checkIfFenceBetween(x, y, z, targetX, targetY, targetZ, instanceId)) + { + return false; + } + + return canSeeTarget(x, y, z, targetX, targetY, targetZ); + } + + /** + * Calculates line-of-sight Z coordinate for movement validation. + * @param previousX The previous X coordinate + * @param previousY The previous Y coordinate + * @param previousGeoZ The previous geo Z coordinate + * @param currentX The current X coordinate + * @param currentY The current Y coordinate + * @param nswe The movement direction + * @return the calculated geo Z coordinate + */ + private int getLosGeoZ(int previousX, int previousY, int previousGeoZ, int currentX, int currentY, int nswe) + { + if ((((nswe & Cell.NSWE_NORTH) != 0) && ((nswe & Cell.NSWE_SOUTH) != 0)) || (((nswe & Cell.NSWE_WEST) != 0) && ((nswe & Cell.NSWE_EAST) != 0))) + { + throw new RuntimeException("Multiple directions specified in NSWE: " + nswe); + } + + return checkNearestNsweAntiCornerCut(previousX, previousY, previousGeoZ, nswe) ? getNearestZ(currentX, currentY, previousGeoZ) : getNextHigherZ(currentX, currentY, previousGeoZ); + } + + /** + * Checks if position can see target position without door and fence validation. + * @param x The x coordinate + * @param y The y coordinate + * @param z The z coordinate + * @param targetX The target's x coordinate + * @param targetY The target's y coordinate + * @param targetZ The target's z coordinate + * @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise + */ + public boolean canSeeTarget(int x, int y, int z, int targetX, int targetY, int targetZ) + { + int geoX = getGeoX(x); + int geoY = getGeoY(y); + int targetGeoX = getGeoX(targetX); + int targetGeoY = getGeoY(targetY); + + int nearestFromZ = getNearestZ(geoX, geoY, z); + int nearestToZ = getNearestZ(targetGeoX, targetGeoY, targetZ); + + // Fastpath for same coordinates. + if ((geoX == targetGeoX) && (geoY == targetGeoY)) + { + return !hasGeoPos(targetGeoX, targetGeoY) || (nearestFromZ == nearestToZ); + } + + int fromX = x; + int fromY = y; + int toX = targetX; + int toY = targetY; + if (nearestToZ > nearestFromZ) + { + int temp = toX; + toX = fromX; + fromX = temp; + + temp = toY; + toY = fromY; + fromY = temp; + + temp = nearestToZ; + nearestToZ = nearestFromZ; + nearestFromZ = temp; + + temp = targetGeoX; + targetGeoX = geoX; + geoX = temp; + + temp = targetGeoY; + targetGeoY = geoY; + geoY = temp; + } + + final GridLineIterator3D pointIterator = new GridLineIterator3D(geoX, geoY, nearestFromZ, targetGeoX, targetGeoY, nearestToZ); + + // First point is guaranteed to be available, skip it, we can always see our own position. + pointIterator.next(); + int previousX = pointIterator.x(); + int previousY = pointIterator.y(); + int previousGeoZ = pointIterator.z(); + int pointIndex = 0; + while (pointIterator.next()) + { + final int currentX = pointIterator.x(); + final int currentY = pointIterator.y(); + + if ((currentX == previousX) && (currentY == previousY)) + { + continue; + } + + final int beeCurrentZ = pointIterator.z(); + int currentGeoZ = previousGeoZ; + + // Check if the position has geodata. + if (hasGeoPos(currentX, currentY)) + { + final int nswe = GeoUtils.computeNswe(previousX, previousY, currentX, currentY); + currentGeoZ = getLosGeoZ(previousX, previousY, previousGeoZ, currentX, currentY, nswe); + final int maxHeight = pointIndex < ELEVATED_SEE_OVER_DISTANCE ? nearestFromZ + MAX_SEE_OVER_HEIGHT : beeCurrentZ + MAX_SEE_OVER_HEIGHT; + boolean canSeeThrough = false; + if (currentGeoZ <= maxHeight) + { + if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) + { + final int northGeoZ = getLosGeoZ(previousX, previousY, previousGeoZ, previousX, previousY - 1, Cell.NSWE_EAST); + final int eastGeoZ = getLosGeoZ(previousX, previousY, previousGeoZ, previousX + 1, previousY, Cell.NSWE_NORTH); + canSeeThrough = (northGeoZ <= maxHeight) && (eastGeoZ <= maxHeight) && (northGeoZ <= getNearestZ(previousX, previousY - 1, beeCurrentZ)) && (eastGeoZ <= getNearestZ(previousX + 1, previousY, beeCurrentZ)); + } + else if ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST) + { + final int northGeoZ = getLosGeoZ(previousX, previousY, previousGeoZ, previousX, previousY - 1, Cell.NSWE_WEST); + final int westGeoZ = getLosGeoZ(previousX, previousY, previousGeoZ, previousX - 1, previousY, Cell.NSWE_NORTH); + canSeeThrough = (northGeoZ <= maxHeight) && (westGeoZ <= maxHeight) && (northGeoZ <= getNearestZ(previousX, previousY - 1, beeCurrentZ)) && (westGeoZ <= getNearestZ(previousX - 1, previousY, beeCurrentZ)); + } + else if ((nswe & Cell.NSWE_SOUTH_EAST) == Cell.NSWE_SOUTH_EAST) + { + final int southGeoZ = getLosGeoZ(previousX, previousY, previousGeoZ, previousX, previousY + 1, Cell.NSWE_EAST); + final int eastGeoZ = getLosGeoZ(previousX, previousY, previousGeoZ, previousX + 1, previousY, Cell.NSWE_SOUTH); + canSeeThrough = (southGeoZ <= maxHeight) && (eastGeoZ <= maxHeight) && (southGeoZ <= getNearestZ(previousX, previousY + 1, beeCurrentZ)) && (eastGeoZ <= getNearestZ(previousX + 1, previousY, beeCurrentZ)); + } + else if ((nswe & Cell.NSWE_SOUTH_WEST) == Cell.NSWE_SOUTH_WEST) + { + final int southGeoZ = getLosGeoZ(previousX, previousY, previousGeoZ, previousX, previousY + 1, Cell.NSWE_WEST); + final int westGeoZ = getLosGeoZ(previousX, previousY, previousGeoZ, previousX - 1, previousY, Cell.NSWE_SOUTH); + canSeeThrough = (southGeoZ <= maxHeight) && (westGeoZ <= maxHeight) && (southGeoZ <= getNearestZ(previousX, previousY + 1, beeCurrentZ)) && (westGeoZ <= getNearestZ(previousX - 1, previousY, beeCurrentZ)); + } + else + { + canSeeThrough = true; + } + } + + if (!canSeeThrough) + { + return false; + } + } + + previousX = currentX; + previousY = currentY; + previousGeoZ = currentGeoZ; + ++pointIndex; + } + + return true; + } + + /** + * Returns {@code true} if any of the four NSWE neighbours of {@code (geoX, geoY)} has a layer within {@code tolerance} game units of {@code worldZ}.
        + * Used to detect single-cell layer gaps where the surface is continuous through a neighbour even though the cell itself lacks the matching layer.
        + * Cells without geodata are not considered matches. + * @param geoX the cell X + * @param geoY the cell Y + * @param worldZ the source Z value to match + * @param tolerance maximum {@code |neighbourLayerZ - worldZ|} for a neighbour to be considered "the same surface" + * @return {@code true} if at least one geo-bearing NSWE neighbour has a near match + */ + private boolean hasNeighbourLayerNear(int geoX, int geoY, int worldZ, int tolerance) + { + // @formatter:off + final int[] dx = { 1, -1, 0, 0 }; + final int[] dy = { 0, 0, 1, -1 }; + // @formatter:on + + for (int i = 0; i < 4; i++) + { + final int nx = geoX + dx[i]; + final int ny = geoY + dy[i]; + if (hasGeoPos(nx, ny) && (Math.abs(getNearestZ(nx, ny, worldZ) - worldZ) <= tolerance)) + { + return true; + } + } + + return false; + } + + /** + * Verifies if there is a path between origin and destination locations.
        + * Returns the destination if there is a path or the closest valid location. + * @param origin The origin + * @param destination The destination + * @return the destination if there is a path or the closest location + */ + public Location getValidLocation(ILocational origin, ILocational destination) + { + return getValidLocation(origin.getX(), origin.getY(), origin.getZ(), destination.getX(), destination.getY(), destination.getZ(), 0); + } + + /** + * Performs movement validation and returns the last valid location before obstacles. + * @param x The x coordinate + * @param y The y coordinate + * @param z The z coordinate + * @param targetX The target's x coordinate + * @param targetY The target's y coordinate + * @param targetZ The target's z coordinate + * @param instanceId the instance id + * @return the last Location where player can walk - just before wall + */ + public Location getValidLocation(int x, int y, int z, int targetX, int targetY, int targetZ, int instanceId) + { + final int geoX = getGeoX(x); + final int geoY = getGeoY(y); + final int nearestFromZ = getNearestZ(geoX, geoY, z); + final int targetGeoX = getGeoX(targetX); + final int targetGeoY = getGeoY(targetY); + final int nearestToZ = getNearestZ(targetGeoX, targetGeoY, targetZ); + + // Door checks. + if (DoorData.getInstance().checkIfDoorsBetween(x, y, nearestFromZ, targetX, targetY, nearestToZ, instanceId, false)) + { + return new Location(x, y, getHeight(x, y, nearestFromZ)); + } + + // Fence checks. + if (FenceData.getInstance().checkIfFenceBetween(x, y, nearestFromZ, targetX, targetY, nearestToZ, instanceId)) + { + return new Location(x, y, getHeight(x, y, nearestFromZ)); + } + + final GridLineIterator2D pointIterator = new GridLineIterator2D(geoX, geoY, targetGeoX, targetGeoY); + + // First point is guaranteed to be available. + pointIterator.next(); + int previousX = pointIterator.x(); + int previousY = pointIterator.y(); + int previousZ = nearestFromZ; + + while (pointIterator.next()) + { + final int currentX = pointIterator.x(); + final int currentY = pointIterator.y(); + final int rawCurrentZ = getNearestZ(currentX, currentY, previousZ); + final int currentZ; + if ((rawCurrentZ - previousZ) > HEIGHT_INCREASE_LIMIT) // Check for sudden height increase. + { + // Single-cell layer-gap fallback: if any NSWE neighbour of this cell has a layer near previousZ, the surface is continuous through this cell even though its own nearest layer is far. + // Step over the gap by treating the current cell as if it had a layer at previousZ. + if (!hasNeighbourLayerNear(currentX, currentY, previousZ, PATH_CONTINUITY_TOLERANCE)) + { + // Genuine wall, return previous location. + return new Location(getWorldX(previousX), getWorldY(previousY), previousZ); + } + + currentZ = previousZ; + } + else + { + currentZ = rawCurrentZ; + } + + if (hasGeoPos(previousX, previousY)) + { + if (isCompletelyBlocked(currentX, currentY, currentZ)) + { + // Can't move, return previous location. + return new Location(getWorldX(previousX), getWorldY(previousY), previousZ); + } + + if (!checkNearestNsweAntiCornerCut(previousX, previousY, previousZ, GeoUtils.computeNswe(previousX, previousY, currentX, currentY))) + { + // Can't move, return previous location. + return new Location(getWorldX(previousX), getWorldY(previousY), previousZ); + } + } + + previousX = currentX; + previousY = currentY; + previousZ = currentZ; + } + + return hasGeoPos(previousX, previousY) && (previousZ != nearestToZ) ? new Location(x, y, nearestFromZ) : new Location(targetX, targetY, nearestToZ); + } + + /** + * Checks if it is possible to move from one location to another. + * @param fromX The X coordinate to start checking from + * @param fromY The Y coordinate to start checking from + * @param fromZ The Z coordinate to start checking from + * @param toX The X coordinate to end checking at + * @param toY The Y coordinate to end checking at + * @param toZ The Z coordinate to end checking at + * @param instanceId the instance id + * @return {@code true} if the creature at start coordinates can move to end coordinates, {@code false} otherwise + */ + public boolean canMoveToTarget(int fromX, int fromY, int fromZ, int toX, int toY, int toZ, int instanceId) + { + final int geoX = getGeoX(fromX); + final int geoY = getGeoY(fromY); + final int nearestFromZ = getNearestZ(geoX, geoY, fromZ); + final int targetGeoX = getGeoX(toX); + final int targetGeoY = getGeoY(toY); + final int nearestToZ = getNearestZ(targetGeoX, targetGeoY, toZ); + + // Door checks. + if (DoorData.getInstance().checkIfDoorsBetween(fromX, fromY, nearestFromZ, toX, toY, nearestToZ, instanceId, false)) + { + return false; + } + + // Fence checks. + if (FenceData.getInstance().checkIfFenceBetween(fromX, fromY, nearestFromZ, toX, toY, nearestToZ, instanceId)) + { + return false; + } + + final GridLineIterator2D pointIterator = new GridLineIterator2D(geoX, geoY, targetGeoX, targetGeoY); + + // First point is guaranteed to be available. + pointIterator.next(); + int previousX = pointIterator.x(); + int previousY = pointIterator.y(); + int previousZ = nearestFromZ; + + while (pointIterator.next()) + { + final int currentX = pointIterator.x(); + final int currentY = pointIterator.y(); + final int rawCurrentZ = getNearestZ(currentX, currentY, previousZ); + final int currentZ; + if ((rawCurrentZ - previousZ) > HEIGHT_INCREASE_LIMIT) // Check for sudden height increase. + { + // Single-cell layer-gap fallback (mirrors getValidLocation): treat as continuous surface if any NSWE neighbour of the current cell has a layer near previousZ. + if (!hasNeighbourLayerNear(currentX, currentY, previousZ, PATH_CONTINUITY_TOLERANCE)) + { + return false; + } + + currentZ = previousZ; + } + else + { + currentZ = rawCurrentZ; + } + + if (hasGeoPos(previousX, previousY)) + { + final int nswe = GeoUtils.computeNswe(previousX, previousY, currentX, currentY); + if (GeoEngineConfig.AVOID_OBSTRUCTED_PATH_NODES && !checkNearestNswe(previousX, previousY, previousZ, nswe)) + { + return false; + } + + if (!checkNearestNsweAntiCornerCut(previousX, previousY, previousZ, nswe)) + { + return false; + } + } + + previousX = currentX; + previousY = currentY; + previousZ = currentZ; + } + + return !hasGeoPos(previousX, previousY) || (previousZ == nearestToZ); + } + + /** + * Checks if it is possible to move from one location to another. + * @param from The {@code ILocational} to start checking from + * @param toX The X coordinate to end checking at + * @param toY The Y coordinate to end checking at + * @param toZ The Z coordinate to end checking at + * @return {@code true} if the creature at start coordinates can move to end coordinates, {@code false} otherwise + */ + public boolean canMoveToTarget(ILocational from, int toX, int toY, int toZ) + { + return canMoveToTarget(from.getX(), from.getY(), from.getZ(), toX, toY, toZ, 0); + } + + /** + * Checks if it is possible to move from one location to another. + * @param from The {@code ILocational} to start checking from + * @param to The {@code ILocational} to end checking at + * @return {@code true} if the creature at start coordinates can move to end coordinates, {@code false} otherwise + */ + public boolean canMoveToTarget(ILocational from, ILocational to) + { + return canMoveToTarget(from, to.getX(), to.getY(), to.getZ()); + } + + /** + * Checks the specified position for available geodata. + * @param x The X coordinate + * @param y The Y coordinate + * @return {@code true} if there is geodata for the given coordinates, {@code false} otherwise + */ + public boolean hasGeo(int x, int y) + { + return hasGeoPos(getGeoX(x), getGeoY(y)); + } + + /** + * Checks if all directions (North, South, East, West) are blocked at the specified geo coordinates. + * @param geoX The geo X coordinate to check + * @param geoY The geo Y coordinate to check + * @param geoZ The geo Z coordinate to check + * @return {@code true} if all directions (North, South, East, West) are blocked, {@code false} otherwise. + */ + public boolean isCompletelyBlocked(int geoX, int geoY, int geoZ) + { + if (GeoEngineConfig.PATHFINDING < 1) + { + return false; + } + + final IRegion region = getRegion(geoX, geoY); + if (region != null) + { + return region.hasGeo() && !region.checkNearestNswe(geoX, geoY, geoZ, Cell.NSWE_NORTH) && !region.checkNearestNswe(geoX, geoY, geoZ, Cell.NSWE_SOUTH) && !region.checkNearestNswe(geoX, geoY, geoZ, Cell.NSWE_EAST) && !region.checkNearestNswe(geoX, geoY, geoZ, Cell.NSWE_WEST); + } + + return true; + } + + public static GeoEngine getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final GeoEngine INSTANCE = new GeoEngine(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java new file mode 100644 index 0000000..186e916 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java @@ -0,0 +1,48 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public class Cell +{ + /** East NSWE flag */ + public static final byte NSWE_EAST = 1 << 0; + /** West NSWE flag */ + public static final byte NSWE_WEST = 1 << 1; + /** South NSWE flag */ + public static final byte NSWE_SOUTH = 1 << 2; + /** North NSWE flag */ + public static final byte NSWE_NORTH = 1 << 3; + + /** North-East NSWE flags */ + public static final byte NSWE_NORTH_EAST = NSWE_NORTH | NSWE_EAST; + /** North-West NSWE flags */ + public static final byte NSWE_NORTH_WEST = NSWE_NORTH | NSWE_WEST; + /** South-East NSWE flags */ + public static final byte NSWE_SOUTH_EAST = NSWE_SOUTH | NSWE_EAST; + /** South-West NSWE flags */ + public static final byte NSWE_SOUTH_WEST = NSWE_SOUTH | NSWE_WEST; + + /** All directions NSWE flags */ + public static final byte NSWE_ALL = NSWE_EAST | NSWE_WEST | NSWE_SOUTH | NSWE_NORTH; + + private Cell() + { + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java new file mode 100644 index 0000000..0d651f7 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java @@ -0,0 +1,48 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho + */ +public interface IBlock +{ + int TYPE_FLAT = 0; + int TYPE_COMPLEX = 1; + int TYPE_MULTILAYER = 2; + + /** Cells in a block on the x axis */ + int BLOCK_CELLS_X = 8; + /** Cells in a block on the y axis */ + int BLOCK_CELLS_Y = 8; + /** Cells in a block */ + int BLOCK_CELLS = BLOCK_CELLS_X * BLOCK_CELLS_Y; + + boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe); + + void setNearestNswe(int geoX, int geoY, int worldZ, byte nswe); + + void unsetNearestNswe(int geoX, int geoY, int worldZ, byte nswe); + + short getNearestNswe(int geoX, int geoY, int worldZ); + + int getNearestZ(int geoX, int geoY, int worldZ); + + int getNextLowerZ(int geoX, int geoY, int worldZ); + + int getNextHigherZ(int geoX, int geoY, int worldZ); +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java new file mode 100644 index 0000000..3b7e4a3 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.geoengine.geodata; + +/** + * @author HorridoJoho, Mobius + */ +public interface IRegion +{ + /** Blocks in a region on the x axis. */ + int REGION_BLOCKS_X = 256; + /** Blocks in a region on the y axis. */ + int REGION_BLOCKS_Y = 256; + /** Blocks in a region. */ + int REGION_BLOCKS = REGION_BLOCKS_X * REGION_BLOCKS_Y; + + /** Cells in a region on the x axis. */ + int REGION_CELLS_X = REGION_BLOCKS_X * IBlock.BLOCK_CELLS_X; + /** Cells in a region on the y axis. */ + int REGION_CELLS_Y = REGION_BLOCKS_Y * IBlock.BLOCK_CELLS_Y; + /** Cells in a region. */ + int REGION_CELLS = REGION_CELLS_X * REGION_CELLS_Y; + + boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe); + + void setNearestNswe(int geoX, int geoY, int worldZ, byte nswe); + + void unsetNearestNswe(int geoX, int geoY, int worldZ, byte nswe); + + int getNearestZ(int geoX, int geoY, int worldZ); + + int getNextLowerZ(int geoX, int geoY, int worldZ); + + int getNextHigherZ(int geoX, int geoY, int worldZ); + + boolean hasGeo(); + + boolean saveToFile(String fileName); +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java new file mode 100644 index 0000000..d8167eb --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/ComplexBlock.java @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.Cell; +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho, Mobius + */ +public class ComplexBlock implements IBlock +{ + private final short[] _data; + + public ComplexBlock(ByteBuffer bb) + { + _data = new short[IBlock.BLOCK_CELLS]; + for (int cellOffset = 0; cellOffset < IBlock.BLOCK_CELLS; cellOffset++) + { + _data[cellOffset] = bb.getShort(); + } + } + + private short getCellData(int geoX, int geoY) + { + return _data[((geoX % IBlock.BLOCK_CELLS_X) * IBlock.BLOCK_CELLS_Y) + (geoY % IBlock.BLOCK_CELLS_Y)]; + } + + private byte getCellNSWE(int geoX, int geoY) + { + return (byte) (getCellData(geoX, geoY) & 0x000F); + } + + private int getCellHeight(int geoX, int geoY) + { + return (short) (getCellData(geoX, geoY) & 0x0FFF0) >> 1; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return (getCellNSWE(geoX, geoY) & nswe) == nswe; + } + + @Override + public void setNearestNswe(int geoX, int geoY, int worldZ, byte nswe) + { + final short currentNswe = getNearestNswe(geoX, geoY, worldZ); + if ((currentNswe & nswe) == 0) + { + final short currentHeight = (short) getCellHeight(geoX, geoY); + final short encodedHeight = (short) (currentHeight << 1); // Shift left by 1 bit. + final short newNswe = (short) (currentNswe | nswe); // Add NSWE. + final short newCombinedData = (short) (encodedHeight | newNswe); // Combine height and NSWE. + _data[((geoX % IBlock.BLOCK_CELLS_X) * IBlock.BLOCK_CELLS_Y) + (geoY % IBlock.BLOCK_CELLS_Y)] = (short) (newCombinedData & 0xffff); + } + } + + @Override + public void unsetNearestNswe(int geoX, int geoY, int worldZ, byte nswe) + { + final short currentNswe = getNearestNswe(geoX, geoY, worldZ); + if ((currentNswe & nswe) != 0) + { + final short currentHeight = (short) getCellHeight(geoX, geoY); + final short encodedHeight = (short) (currentHeight << 1); // Shift left by 1 bit. + final short newNswe = (short) (currentNswe & ~nswe); // Subtract NSWE. + final short newCombinedData = (short) (encodedHeight | newNswe); // Combine height and NSWE. + _data[((geoX % IBlock.BLOCK_CELLS_X) * IBlock.BLOCK_CELLS_Y) + (geoY % IBlock.BLOCK_CELLS_Y)] = (short) (newCombinedData & 0xffff); + } + } + + @Override + public short getNearestNswe(int geoX, int geoY, int worldZ) + { + short nswe = 0; + if (checkNearestNswe(geoX, geoY, worldZ, Cell.NSWE_NORTH)) + { + nswe = (short) (nswe | Cell.NSWE_NORTH); + } + + if (checkNearestNswe(geoX, geoY, worldZ, Cell.NSWE_EAST)) + { + nswe = (short) (nswe | Cell.NSWE_EAST); + } + + if (checkNearestNswe(geoX, geoY, worldZ, Cell.NSWE_SOUTH)) + { + nswe = (short) (nswe | Cell.NSWE_SOUTH); + } + + if (checkNearestNswe(geoX, geoY, worldZ, Cell.NSWE_WEST)) + { + nswe = (short) (nswe | Cell.NSWE_WEST); + } + + return nswe; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getCellHeight(geoX, geoY); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + final int cellHeight = getCellHeight(geoX, geoY); + return cellHeight <= worldZ ? cellHeight : worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + final int cellHeight = getCellHeight(geoX, geoY); + return cellHeight >= worldZ ? cellHeight : worldZ; + } + + public short[] getData() + { + return _data; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java new file mode 100644 index 0000000..6b20dc6 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/FlatBlock.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.Cell; +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho, Mobius + */ +public class FlatBlock implements IBlock +{ + private final short _height; + + public FlatBlock(ByteBuffer bb) + { + _height = bb.getShort(); + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return true; + } + + @Override + public void setNearestNswe(int geoX, int geoY, int worldZ, byte nswe) + { + throw new RuntimeException("Cannot set NSWE on a flat block!"); + } + + @Override + public void unsetNearestNswe(int geoX, int geoY, int worldZ, byte nswe) + { + throw new RuntimeException("Cannot unset NSWE on a flat block!"); + } + + @Override + public short getNearestNswe(int geoX, int geoY, int worldZ) + { + return Cell.NSWE_ALL; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return _height; + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return _height <= worldZ ? _height : worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return _height >= worldZ ? _height : worldZ; + } + + public short getHeight() + { + return _height; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java new file mode 100644 index 0000000..05a19f3 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/blocks/MultilayerBlock.java @@ -0,0 +1,316 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.geoengine.geodata.blocks; + +import java.nio.ByteBuffer; + +import org.l2jmobius.gameserver.geoengine.geodata.Cell; +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; + +/** + * @author HorridoJoho, Mobius + */ +public class MultilayerBlock implements IBlock +{ + private final byte[] _data; + + /** + * Initializes a new instance of this block reading the specified buffer. + * @param bb the buffer + */ + public MultilayerBlock(ByteBuffer bb) + { + final int start = bb.position(); + + for (int blockCellOffset = 0; blockCellOffset < IBlock.BLOCK_CELLS; blockCellOffset++) + { + final byte nLayers = bb.get(); + if ((nLayers <= 0) || (nLayers > 125)) + { + throw new RuntimeException("L2JGeoDriver: Geo file corrupted! Invalid layers count!"); + } + + bb.position(bb.position() + (nLayers * 2)); + } + + _data = new byte[bb.position() - start]; + bb.position(start); + bb.get(_data); + } + + private short getNearestLayer(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + // One layer at least was required on loading so this is set at least once on the loop below. + int nearestDZ = 0; + short nearestData = 0; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerData; + } + + final int layerDZ = Math.abs(layerZ - worldZ); + if ((offset == (startOffset + 1)) || (layerDZ < nearestDZ)) + { + nearestDZ = layerDZ; + nearestData = layerData; + } + } + + return nearestData; + } + + private int getCellDataOffset(int geoX, int geoY) + { + final int cellLocalOffset = ((geoX % IBlock.BLOCK_CELLS_X) * IBlock.BLOCK_CELLS_Y) + (geoY % IBlock.BLOCK_CELLS_Y); + int cellDataOffset = 0; + + // Move index to cell, we need to parse on each request, OR we parse on creation and save indexes. + for (int i = 0; i < cellLocalOffset; i++) + { + cellDataOffset += 1 + (_data[cellDataOffset] * 2); + } + + // Now the index points to the cell we need. + + return cellDataOffset; + } + + private short extractLayerData(int dataOffset) + { + return (short) ((_data[dataOffset] & 0xff) | (_data[dataOffset + 1] << 8)); + } + + private int getNearestNSWE(int geoX, int geoY, int worldZ) + { + return extractLayerNswe(getNearestLayer(geoX, geoY, worldZ)); + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return (getNearestNSWE(geoX, geoY, worldZ) & nswe) == nswe; + } + + @Override + public void setNearestNswe(int geoX, int geoY, int worldZ, byte nswe) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + int nearestDZ = 0; + int nearestLayerZ = 0; + int nearestOffset = 0; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + nearestLayerZ = layerZ; + nearestOffset = offset; + break; + } + + final int layerDZ = Math.abs(layerZ - worldZ); + if ((offset == (startOffset + 1)) || (layerDZ < nearestDZ)) + { + nearestDZ = layerDZ; + nearestLayerZ = layerZ; + nearestOffset = offset; + } + } + + final short currentNswe = getNearestNswe(geoX, geoY, worldZ); + if ((currentNswe & nswe) == 0) + { + final short encodedHeight = (short) (nearestLayerZ << 1); // Shift left by 1 bit. + final short newNswe = (short) (currentNswe | nswe); // Combine NSWE. + final short newCombinedData = (short) (encodedHeight | newNswe); // Combine height and NSWE. + _data[nearestOffset] = (byte) (newCombinedData & 0xff); // Update the first byte at offset. + _data[nearestOffset + 1] = (byte) ((newCombinedData >> 8) & 0xff); // Update the second byte at offset + 1. + } + } + + @Override + public void unsetNearestNswe(int geoX, int geoY, int worldZ, byte nswe) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + int nearestDZ = 0; + int nearestLayerZ = 0; + int nearestOffset = 0; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + nearestLayerZ = layerZ; + nearestOffset = offset; + break; + } + + final int layerDZ = Math.abs(layerZ - worldZ); + if ((offset == (startOffset + 1)) || (layerDZ < nearestDZ)) + { + nearestDZ = layerDZ; + nearestLayerZ = layerZ; + nearestOffset = offset; + } + } + + final short currentNswe = getNearestNswe(geoX, geoY, worldZ); + if ((currentNswe & nswe) != 0) + { + final short encodedHeight = (short) (nearestLayerZ << 1); // Shift left by 1 bit. + final short newNswe = (short) (currentNswe & ~nswe); // Subtract NSWE. + final short newCombinedData = (short) (encodedHeight | newNswe); // Combine height and NSWE. + _data[nearestOffset] = (byte) (newCombinedData & 0xff); // Update the first byte at offset. + _data[nearestOffset + 1] = (byte) ((newCombinedData >> 8) & 0xff); // Update the second byte at offset + 1. + } + } + + @Override + public short getNearestNswe(int geoX, int geoY, int worldZ) + { + short nswe = 0; + if (checkNearestNswe(geoX, geoY, worldZ, Cell.NSWE_NORTH)) + { + nswe = (short) (nswe | Cell.NSWE_NORTH); + } + + if (checkNearestNswe(geoX, geoY, worldZ, Cell.NSWE_EAST)) + { + nswe = (short) (nswe | Cell.NSWE_EAST); + } + + if (checkNearestNswe(geoX, geoY, worldZ, Cell.NSWE_SOUTH)) + { + nswe = (short) (nswe | Cell.NSWE_SOUTH); + } + + if (checkNearestNswe(geoX, geoY, worldZ, Cell.NSWE_WEST)) + { + nswe = (short) (nswe | Cell.NSWE_WEST); + } + + return nswe; + } + + private int extractLayerNswe(short layer) + { + return (byte) (layer & 0x000f); + } + + private int extractLayerHeight(short layer) + { + return (short) (layer & 0x0fff0) >> 1; + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + // Get the height of the nearest terrain layer. + final int nearestZ = extractLayerHeight(getNearestLayer(geoX, geoY, worldZ)); + + // If the nearest layer is more than 1000 units above current position (likely a different floor/level). + if ((nearestZ - worldZ) > 1000) + { + // Find the next lower valid height instead to prevent unreachable positions. + return getNextLowerZ(geoX, geoY, worldZ); + } + + return nearestZ; + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + int lowerZ = Integer.MIN_VALUE; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerZ; + } + + if ((layerZ < worldZ) && (layerZ > lowerZ)) + { + lowerZ = layerZ; + } + } + + return lowerZ == Integer.MIN_VALUE ? worldZ : lowerZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + final int startOffset = getCellDataOffset(geoX, geoY); + final byte nLayers = _data[startOffset]; + final int endOffset = startOffset + 1 + (nLayers * 2); + + int higherZ = Integer.MAX_VALUE; + for (int offset = startOffset + 1; offset < endOffset; offset += 2) + { + final short layerData = extractLayerData(offset); + + final int layerZ = extractLayerHeight(layerData); + if (layerZ == worldZ) + { + // Exact z. + return layerZ; + } + + if ((layerZ > worldZ) && (layerZ < higherZ)) + { + higherZ = layerZ; + } + } + + return higherZ == Integer.MAX_VALUE ? worldZ : higherZ; + } + + public byte[] getData() + { + return _data; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java new file mode 100644 index 0000000..b9f1e0f --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/regions/NullRegion.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.geoengine.geodata.regions; + +import org.l2jmobius.gameserver.geoengine.geodata.IRegion; + +/** + * @author HorridoJoho, Mobius + */ +public class NullRegion implements IRegion +{ + public static final NullRegion INSTANCE = new NullRegion(); + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return true; + } + + @Override + public void setNearestNswe(int geoX, int geoY, int worldZ, byte nswe) + { + } + + @Override + public void unsetNearestNswe(int geoX, int geoY, int worldZ, byte nswe) + { + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return worldZ; + } + + @Override + public boolean hasGeo() + { + return false; + } + + @Override + public boolean saveToFile(String fileName) + { + return false; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java new file mode 100644 index 0000000..fa519f3 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/geodata/regions/Region.java @@ -0,0 +1,225 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.geoengine.geodata.regions; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.file.Files; +import java.nio.file.Path; + +import org.l2jmobius.gameserver.config.GeoEngineConfig; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; +import org.l2jmobius.gameserver.geoengine.geodata.IBlock; +import org.l2jmobius.gameserver.geoengine.geodata.IRegion; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.ComplexBlock; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.FlatBlock; +import org.l2jmobius.gameserver.geoengine.geodata.blocks.MultilayerBlock; + +/** + * @author HorridoJoho, Mobius + */ +public class Region implements IRegion +{ + private final IBlock[] _blocks = new IBlock[IRegion.REGION_BLOCKS]; + + public Region(ByteBuffer bb) + { + load(bb); + } + + public void load(ByteBuffer bb) + { + for (int blockOffset = 0; blockOffset < IRegion.REGION_BLOCKS; blockOffset++) + { + final int blockType = bb.get(); + switch (blockType) + { + case IBlock.TYPE_FLAT: + { + _blocks[blockOffset] = new FlatBlock(bb); + break; + } + case IBlock.TYPE_COMPLEX: + { + _blocks[blockOffset] = new ComplexBlock(bb); + break; + } + case IBlock.TYPE_MULTILAYER: + { + _blocks[blockOffset] = new MultilayerBlock(bb); + break; + } + default: + { + throw new RuntimeException("Invalid block type " + blockType + "!"); + } + } + } + } + + private IBlock getBlock(int geoX, int geoY) + { + return _blocks[(((geoX / IBlock.BLOCK_CELLS_X) % IRegion.REGION_BLOCKS_X) * IRegion.REGION_BLOCKS_Y) + ((geoY / IBlock.BLOCK_CELLS_Y) % IRegion.REGION_BLOCKS_Y)]; + } + + @Override + public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) + { + return getBlock(geoX, geoY).checkNearestNswe(geoX, geoY, worldZ, nswe); + } + + @Override + public void setNearestNswe(int geoX, int geoY, int worldZ, byte nswe) + { + final IBlock block = getBlock(geoX, geoY); + + // Flat block cells are enabled by default on all directions. + if (block instanceof FlatBlock) + { + // convertFlatToComplex(block, geoX, geoY); + return; + } + + getBlock(geoX, geoY).setNearestNswe(geoX, geoY, worldZ, nswe); + } + + @Override + public void unsetNearestNswe(int geoX, int geoY, int worldZ, byte nswe) + { + final IBlock block = getBlock(geoX, geoY); + + // Flat blocks are by default enabled on all locations. + if (block instanceof FlatBlock) + { + convertFlatToComplex(block, geoX, geoY); + } + + getBlock(geoX, geoY).unsetNearestNswe(geoX, geoY, worldZ, nswe); + } + + private void convertFlatToComplex(IBlock block, int geoX, int geoY) + { + final short currentHeight = ((FlatBlock) block).getHeight(); + final short encodedHeight = (short) ((currentHeight << 1) & 0xffff); + final short combinedData = (short) (encodedHeight | Cell.NSWE_ALL); + final ByteBuffer buffer = ByteBuffer.allocate(IBlock.BLOCK_CELLS * 2); + for (int i = 0; i < IBlock.BLOCK_CELLS; i++) + { + buffer.putShort(combinedData); + } + + buffer.rewind(); + _blocks[(((geoX / IBlock.BLOCK_CELLS_X) % IRegion.REGION_BLOCKS_X) * IRegion.REGION_BLOCKS_Y) + ((geoY / IBlock.BLOCK_CELLS_Y) % IRegion.REGION_BLOCKS_Y)] = new ComplexBlock(buffer); + } + + @Override + public int getNearestZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNearestZ(geoX, geoY, worldZ); + } + + @Override + public int getNextLowerZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNextLowerZ(geoX, geoY, worldZ); + } + + @Override + public int getNextHigherZ(int geoX, int geoY, int worldZ) + { + return getBlock(geoX, geoY).getNextHigherZ(geoX, geoY, worldZ); + } + + @Override + public boolean hasGeo() + { + return true; + } + + /** + * Saves this region to a file. + * @param fileName the target file name. + * @return true if the file was saved successfully, false otherwise. + */ + @Override + public boolean saveToFile(String fileName) + { + final Path filePath = new File(GeoEngineConfig.GEOEDIT_PATH + File.separator + fileName).toPath(); + if (Files.exists(filePath)) + { + try + { + Files.delete(filePath); + } + catch (IOException e) + { + return false; + } + } + + try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(filePath.toFile()))) + { + for (IBlock block : _blocks) + { + if (block instanceof FlatBlock) + { + final ByteBuffer buffer = ByteBuffer.allocate(3); + buffer.put((byte) IBlock.TYPE_FLAT); + buffer.putShort(Short.reverseBytes(((FlatBlock) block).getHeight())); + bos.write(buffer.array()); + } + else if (block instanceof ComplexBlock) + { + final short[] data = ((ComplexBlock) block).getData(); + final ByteBuffer buffer = ByteBuffer.allocate(1 + (data.length * 2)); + buffer.put((byte) IBlock.TYPE_COMPLEX); + for (short info : data) + { + buffer.putShort(Short.reverseBytes(info)); + } + + bos.write(buffer.array()); + } + else if (block instanceof MultilayerBlock) + { + final byte[] data = ((MultilayerBlock) block).getData(); + final ByteBuffer buffer = ByteBuffer.allocate(1 + (data.length)); + buffer.put((byte) IBlock.TYPE_MULTILAYER); + for (byte info : data) + { + buffer.put(info); + } + + bos.write(buffer.array()); + } + } + } + catch (IOException e) + { + return false; + } + + return true; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/GeoLocation.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/GeoLocation.java new file mode 100644 index 0000000..1f9d8aa --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/GeoLocation.java @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.geoengine.pathfinding; + +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; + +/** + * @author Mobius + */ +public class GeoLocation +{ + private int _x; + private int _y; + private int _nswe; + private int _geoHeight; + + public GeoLocation(int x, int y, int z) + { + set(x, y, z); + } + + public void set(int x, int y, int z) + { + _x = x; + _y = y; + _nswe = 0; // Reset the bitmask. + + // Set the NSWE bitmask based on movement possibilities. + final GeoEngine geoEngine = GeoEngine.getInstance(); + if (geoEngine.checkNearestNswe(x, y, z, Cell.NSWE_NORTH)) + { + _nswe |= Cell.NSWE_NORTH; + } + + if (geoEngine.checkNearestNswe(x, y, z, Cell.NSWE_EAST)) + { + _nswe |= Cell.NSWE_EAST; + } + + if (geoEngine.checkNearestNswe(x, y, z, Cell.NSWE_SOUTH)) + { + _nswe |= Cell.NSWE_SOUTH; + } + + if (geoEngine.checkNearestNswe(x, y, z, Cell.NSWE_WEST)) + { + _nswe |= Cell.NSWE_WEST; + } + + _geoHeight = geoEngine.getNearestZ(x, y, z); + } + + public boolean canGoNorth() + { + return (_nswe & Cell.NSWE_NORTH) != 0; + } + + public boolean canGoEast() + { + return (_nswe & Cell.NSWE_EAST) != 0; + } + + public boolean canGoSouth() + { + return (_nswe & Cell.NSWE_SOUTH) != 0; + } + + public boolean canGoWest() + { + return (_nswe & Cell.NSWE_WEST) != 0; + } + + public boolean canGoNone() + { + return _nswe == 0; + } + + public boolean canGoAll() + { + return _nswe == Cell.NSWE_ALL; + } + + public int getX() + { + return GeoEngine.getWorldX(_x); + } + + public int getY() + { + return GeoEngine.getWorldY(_y); + } + + public int getZ() + { + return _geoHeight; + } + + public void setZ(short z) + { + } + + public int getNodeX() + { + return _x; + } + + public int getNodeY() + { + return _y; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = (prime * result) + _x; + result = (prime * result) + _y; + + // Combine the geo height and the NSWE bitmask into the hash. + result = (prime * result) + (((_geoHeight & 0xFFFF) << 1) | _nswe); + + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + + if ((obj == null) || (getClass() != obj.getClass())) + { + return false; + } + + // Compare _x, _y, _geoHeight, and the bitmask _nswe directly. + final GeoLocation other = (GeoLocation) obj; + return (_x == other._x) && (_y == other._y) && (_geoHeight == other._geoHeight) && (_nswe == other._nswe); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java new file mode 100644 index 0000000..4b713b1 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java @@ -0,0 +1,564 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.geoengine.pathfinding; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.l2jmobius.gameserver.config.GeoEngineConfig; +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.geoengine.geodata.Cell; + +/** + * Data-oriented A* pathfinder.
        + * Cell state is held in parallel primitive arrays indexed by {@code cellIdx = (aY * mapSize) + aX}.
        + * The open list is a primitive indexed binary min-heap with O(log N) decrease-key; the closed-set and open-set are bit flags packed into {@link #_flags}.
        + * Free runs in O(touched), not O(mapSize^2). Buffers are owned by a single thread via ThreadLocal in {@link PathFinding} - no internal locking. + * @author Mobius + */ +public class NodeBuffer +{ + private static final int MAX_ITERATIONS = 7000; + private static final int COST_SCALE = 1000; + private static final int Z_TOLERANCE = 64; + private static final int Z_STEP_LIMIT = 16; + + private static final byte FLAG_TOUCHED = 0x1; + private static final byte FLAG_OPEN = 0x2; + private static final byte FLAG_CLOSED = 0x4; + + private final int _mapSize; + private final int _capacity; + + // Scaled weight constants captured at construction. + private final int _wLow; + private final int _wMedium; + private final int _wHigh; + private final int _wDiagonal; + private final int _hStraight; // octile straight step cost + private final int _hDiagonalExcess; // octile diagonal step extra cost (diag - straight) + private final boolean _diagonalStrategy; + + // Cell state (indexed by cellIdx). + private final int[] _parent; + private final int[] _gCost; + private final int[] _hCost; + private final int[] _fCost; + private final byte[] _flags; + private final byte[] _nswe; + private final short[] _z; + + // Touched-cell ledger so free() runs in O(touched). + private final int[] _touched; + private int _touchedCount; + + // Primitive indexed binary min-heap. + private final int[] _heap; + private final int[] _heapPos; + private int _heapSize; + + private int _baseX; + private int _baseY; + private int _targetX; + private int _targetY; + private int _targetZ; + + public NodeBuffer(int size) + { + _mapSize = size; + _capacity = size * size; + _parent = new int[_capacity]; + _gCost = new int[_capacity]; + _hCost = new int[_capacity]; + _fCost = new int[_capacity]; + _flags = new byte[_capacity]; + _nswe = new byte[_capacity]; + _z = new short[_capacity]; + _touched = new int[_capacity]; + _heap = new int[_capacity]; + _heapPos = new int[_capacity]; + + _wLow = (int) (GeoEngineConfig.LOW_WEIGHT * COST_SCALE); + _wMedium = (int) (GeoEngineConfig.MEDIUM_WEIGHT * COST_SCALE); + _wHigh = (int) (GeoEngineConfig.HIGH_WEIGHT * COST_SCALE); + _wDiagonal = (int) (GeoEngineConfig.DIAGONAL_WEIGHT * COST_SCALE); + _hStraight = _wLow; + _hDiagonalExcess = _wDiagonal - _wLow; + _diagonalStrategy = GeoEngineConfig.ADVANCED_DIAGONAL_STRATEGY; + } + + public int getMapSize() + { + return _mapSize; + } + + /** + * Runs A* from start to target in geo-cell space.
        + * Returns the path as a list of {@link GeoLocation} waypoints (in forward order, start exclusive, target inclusive), collapsing colinear runs into endpoints. + * @param x the start geo X + * @param y the start geo Y + * @param z the start world Z + * @param tx the target geo X + * @param ty the target geo Y + * @param tz the target world Z + * @return the waypoint list, or {@code null} when no path exists within the iteration budget + */ + public List findPath(int x, int y, int z, int tx, int ty, int tz) + { + _baseX = x + ((tx - x - _mapSize) / 2); + _baseY = y + ((ty - y - _mapSize) / 2); + _targetX = tx; + _targetY = ty; + _targetZ = tz; + + final int startIdx = cellIdx(x, y); + if (startIdx < 0) + { + return null; + } + + touch(startIdx, x, y, z); + _gCost[startIdx] = 0; + _fCost[startIdx] = _hCost[startIdx]; + heapPush(startIdx); + + for (int count = 0; count < MAX_ITERATIONS; count++) + { + if (_heapSize == 0) + { + return null; + } + + final int currIdx = heapPoll(); + _flags[currIdx] &= ~FLAG_OPEN; + _flags[currIdx] |= FLAG_CLOSED; + + final int currX = idxToX(currIdx); + final int currY = idxToY(currIdx); + final int currZ = _z[currIdx]; + + if ((currX == _targetX) && (currY == _targetY) && (Math.abs(currZ - _targetZ) < Z_TOLERANCE)) + { + return constructPath(currIdx); + } + + expandNeighbors(currIdx, currX, currY, currZ); + } + + return null; + } + + /** + * Resets touched cells and the heap so the buffer can be reused. Runs in O(touched) rather than O(mapSize^2). + */ + public void free() + { + for (int i = 0; i < _touchedCount; i++) + { + _flags[_touched[i]] = 0; + } + + _touchedCount = 0; + _heapSize = 0; + } + + private int cellIdx(int gx, int gy) + { + final int aX = gx - _baseX; + if ((aX < 0) || (aX >= _mapSize)) + { + return -1; + } + + final int aY = gy - _baseY; + if ((aY < 0) || (aY >= _mapSize)) + { + return -1; + } + + return (aY * _mapSize) + aX; + } + + private int idxToX(int idx) + { + return _baseX + (idx % _mapSize); + } + + private int idxToY(int idx) + { + return _baseY + (idx / _mapSize); + } + + /** + * Lazily initialises a cell on first contact and records it in the touched ledger.
        + * The NSWE bitmask and nearest Z are queried from the geo engine exactly once per cell per pathfind. + * @param idx the cell index + * @param gx the geo X coordinate of the cell + * @param gy the geo Y coordinate of the cell + * @param refZ the reference world Z used to resolve the nearest geo layer + */ + private void touch(int idx, int gx, int gy, int refZ) + { + if ((_flags[idx] & FLAG_TOUCHED) != 0) + { + return; + } + + final GeoEngine geo = GeoEngine.getInstance(); + byte nswe = 0; + if (geo.checkNearestNswe(gx, gy, refZ, Cell.NSWE_NORTH)) + { + nswe |= Cell.NSWE_NORTH; + } + + if (geo.checkNearestNswe(gx, gy, refZ, Cell.NSWE_EAST)) + { + nswe |= Cell.NSWE_EAST; + } + + if (geo.checkNearestNswe(gx, gy, refZ, Cell.NSWE_SOUTH)) + { + nswe |= Cell.NSWE_SOUTH; + } + + if (geo.checkNearestNswe(gx, gy, refZ, Cell.NSWE_WEST)) + { + nswe |= Cell.NSWE_WEST; + } + + _nswe[idx] = nswe; + _z[idx] = (short) geo.getNearestZ(gx, gy, refZ); + _flags[idx] = FLAG_TOUCHED; + _parent[idx] = -1; + _gCost[idx] = Integer.MAX_VALUE; + _hCost[idx] = computeH(gx, gy); + _fCost[idx] = Integer.MAX_VALUE; + _heapPos[idx] = -1; + + _touched[_touchedCount++] = idx; + } + + /** + * Octile heuristic in pre-scaled integer arithmetic - admissible for the 8-connected grid using {@code LOW_WEIGHT} as the per-step cost and {@code DIAGONAL_WEIGHT} as the diagonal cost.
        + * Z is folded into the neighbour weighting (see {@link #considerNeighbor}) rather than the heuristic to keep h consistent. + * @param gx the geo X coordinate of the cell being scored + * @param gy the geo Y coordinate of the cell being scored + * @return the scaled heuristic cost to the target + */ + private int computeH(int gx, int gy) + { + final int dx = Math.abs(gx - _targetX); + final int dy = Math.abs(gy - _targetY); + final int maxD = Math.max(dx, dy); + final int minD = Math.min(dx, dy); + return (maxD * _hStraight) + (minD * _hDiagonalExcess); + } + + /** + * Discovers and relaxes all walkable neighbours of the current cell - four orthogonal neighbours always, plus the four diagonals when {@code ADVANCED_DIAGONAL_STRATEGY} is enabled and both flanking cells permit the diagonal. + * @param currIdx the index of the current cell + * @param x the geo X coordinate of the current cell + * @param y the geo Y coordinate of the current cell + * @param z the world Z of the current cell, used as reference for new touches + */ + private void expandNeighbors(int currIdx, int x, int y, int z) + { + final byte nswe = _nswe[currIdx]; + if (nswe == 0) + { + return; + } + + int idxE = -1; + int idxS = -1; + int idxW = -1; + int idxN = -1; + + if ((nswe & Cell.NSWE_EAST) != 0) + { + idxE = considerNeighbor(currIdx, x + 1, y, z, false); + } + + if ((nswe & Cell.NSWE_SOUTH) != 0) + { + idxS = considerNeighbor(currIdx, x, y + 1, z, false); + } + + if ((nswe & Cell.NSWE_WEST) != 0) + { + idxW = considerNeighbor(currIdx, x - 1, y, z, false); + } + + if ((nswe & Cell.NSWE_NORTH) != 0) + { + idxN = considerNeighbor(currIdx, x, y - 1, z, false); + } + + if (!_diagonalStrategy) + { + return; + } + + if ((idxE >= 0) && (idxS >= 0) && ((_nswe[idxE] & Cell.NSWE_SOUTH) != 0) && ((_nswe[idxS] & Cell.NSWE_EAST) != 0)) + { + considerNeighbor(currIdx, x + 1, y + 1, z, true); + } + + if ((idxS >= 0) && (idxW >= 0) && ((_nswe[idxW] & Cell.NSWE_SOUTH) != 0) && ((_nswe[idxS] & Cell.NSWE_WEST) != 0)) + { + considerNeighbor(currIdx, x - 1, y + 1, z, true); + } + + if ((idxN >= 0) && (idxE >= 0) && ((_nswe[idxE] & Cell.NSWE_NORTH) != 0) && ((_nswe[idxN] & Cell.NSWE_EAST) != 0)) + { + considerNeighbor(currIdx, x + 1, y - 1, z, true); + } + + if ((idxN >= 0) && (idxW >= 0) && ((_nswe[idxW] & Cell.NSWE_NORTH) != 0) && ((_nswe[idxN] & Cell.NSWE_WEST) != 0)) + { + considerNeighbor(currIdx, x - 1, y - 1, z, true); + } + } + + /** + * Relaxes a neighbouring cell against the current node - picks a movement weight from terrain (high for blocked/cliff, medium for adjacent-to-blocked, low or diagonal otherwise), then pushes or sifts up in the open heap when a better path is found. + * @param parentIdx the index of the parent cell + * @param nx the geo X coordinate of the neighbour + * @param ny the geo Y coordinate of the neighbour + * @param parentZ the world Z of the parent cell + * @param diagonal whether this neighbour is reached via a diagonal step + * @return the cell index of the neighbour, or {@code -1} if it falls outside the buffer + */ + private int considerNeighbor(int parentIdx, int nx, int ny, int parentZ, boolean diagonal) + { + final int idx = cellIdx(nx, ny); + if (idx < 0) + { + return -1; + } + + touch(idx, nx, ny, parentZ); + + if ((_flags[idx] & FLAG_CLOSED) != 0) + { + return idx; + } + + final int neighborZ = _z[idx]; + final int stepZ = Math.abs(neighborZ - parentZ); + + final int weight; + if (((_nswe[idx] & Cell.NSWE_ALL) != Cell.NSWE_ALL) || (stepZ > Z_STEP_LIMIT)) + { + weight = _wHigh; + } + else if (isHighWeight(nx + 1, ny, neighborZ) || isHighWeight(nx - 1, ny, neighborZ) || isHighWeight(nx, ny + 1, neighborZ) || isHighWeight(nx, ny - 1, neighborZ)) + { + weight = _wMedium; + } + else + { + weight = diagonal ? _wDiagonal : _wLow; + } + + final int newGCost = _gCost[parentIdx] + weight; + final boolean inOpen = (_flags[idx] & FLAG_OPEN) != 0; + if (inOpen && (newGCost >= _gCost[idx])) + { + return idx; + } + + _parent[idx] = parentIdx; + _gCost[idx] = newGCost; + _fCost[idx] = newGCost + _hCost[idx]; + if (inOpen) + { + heapSiftUp(_heapPos[idx]); + } + else + { + heapPush(idx); + } + + return idx; + } + + private boolean isHighWeight(int gx, int gy, int refZ) + { + final int idx = cellIdx(gx, gy); + if (idx < 0) + { + return true; + } + + touch(idx, gx, gy, refZ); + return ((_nswe[idx] & Cell.NSWE_ALL) != Cell.NSWE_ALL) || (Math.abs(_z[idx] - refZ) > Z_STEP_LIMIT); + } + + private void heapPush(int idx) + { + final int pos = _heapSize++; + _heap[pos] = idx; + _heapPos[idx] = pos; + _flags[idx] |= FLAG_OPEN; + heapSiftUp(pos); + } + + private int heapPoll() + { + final int root = _heap[0]; + _heapPos[root] = -1; + final int lastPos = --_heapSize; + if (lastPos > 0) + { + final int moved = _heap[lastPos]; + _heap[0] = moved; + _heapPos[moved] = 0; + heapSiftDown(0); + } + + return root; + } + + private void heapSiftUp(int startPos) + { + int pos = startPos; + final int idx = _heap[pos]; + final int fCost = _fCost[idx]; + final int hCost = _hCost[idx]; + while (pos > 0) + { + final int parentPos = (pos - 1) >>> 1; + final int parentIdx = _heap[parentPos]; + final int parentF = _fCost[parentIdx]; + if ((fCost > parentF) || ((fCost == parentF) && (hCost >= _hCost[parentIdx]))) + { + break; + } + + _heap[pos] = parentIdx; + _heapPos[parentIdx] = pos; + pos = parentPos; + } + + _heap[pos] = idx; + _heapPos[idx] = pos; + } + + private void heapSiftDown(int startPos) + { + int pos = startPos; + final int idx = _heap[pos]; + final int fCost = _fCost[idx]; + final int hCost = _hCost[idx]; + final int half = _heapSize >>> 1; + while (pos < half) + { + int childPos = (pos << 1) + 1; + int childIdx = _heap[childPos]; + int childF = _fCost[childIdx]; + final int rightPos = childPos + 1; + if (rightPos < _heapSize) + { + final int rightIdx = _heap[rightPos]; + final int rightF = _fCost[rightIdx]; + if ((rightF < childF) || ((rightF == childF) && (_hCost[rightIdx] < _hCost[childIdx]))) + { + childPos = rightPos; + childIdx = rightIdx; + childF = rightF; + } + } + + if ((fCost < childF) || ((fCost == childF) && (hCost <= _hCost[childIdx]))) + { + break; + } + + _heap[pos] = childIdx; + _heapPos[childIdx] = pos; + pos = childPos; + } + + _heap[pos] = idx; + _heapPos[idx] = pos; + } + + /** + * Walks the parent chain from the result cell backward, emitting a waypoint only when the movement direction changes.
        + * Build is forward-then-reverse instead of {@code ArrayList.addFirst}, which is O(N) per insert. + * @param endIdx the cell index of the target node + * @return the path as a list of {@link GeoLocation} waypoints in forward order + */ + private List constructPath(int endIdx) + { + final List reversed = new ArrayList<>(); + int prevDirX = Integer.MIN_VALUE; + int prevDirY = Integer.MIN_VALUE; + int curr = endIdx; + + while (_parent[curr] != -1) + { + final int parent = _parent[curr]; + final int grand = _parent[parent]; + final int currX = idxToX(curr); + final int currY = idxToY(curr); + final int parentX = idxToX(parent); + final int parentY = idxToY(parent); + + int dirX; + int dirY; + if (!_diagonalStrategy && (grand != -1)) + { + final int tmpX = currX - idxToX(grand); + final int tmpY = currY - idxToY(grand); + if (Math.abs(tmpX) == Math.abs(tmpY)) + { + dirX = tmpX; + dirY = tmpY; + } + else + { + dirX = currX - parentX; + dirY = currY - parentY; + } + } + else + { + dirX = currX - parentX; + dirY = currY - parentY; + } + + if ((dirX != prevDirX) || (dirY != prevDirY)) + { + prevDirX = dirX; + prevDirY = dirY; + reversed.add(new GeoLocation(currX, currY, _z[curr])); + } + + curr = parent; + } + + Collections.reverse(reversed); + return reversed; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java new file mode 100644 index 0000000..c5de95c --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/pathfinding/PathFinding.java @@ -0,0 +1,305 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.geoengine.pathfinding; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.gameserver.config.GeoEngineConfig; +import org.l2jmobius.gameserver.entity.World; +import org.l2jmobius.gameserver.geoengine.GeoEngine; + +/** + * Entry point for pathfinding.
        + * Buffers are owned per-thread via {@link ThreadLocal} - no shared pool, no locks.
        + * Each thread lazily allocates one {@link NodeBuffer} per configured size class on first use; subsequent calls of compatible size reuse the same instance via {@link NodeBuffer#free()}. + * @author Mobius + */ +public class PathFinding +{ + private static final Logger LOGGER = Logger.getLogger(PathFinding.class.getName()); + + private final int[] _sizeClasses; + private final ThreadLocal _tlBuffers; + + protected PathFinding() + { + try + { + final String[] array = GeoEngineConfig.PATHFIND_BUFFERS.split(";"); + final int[] parsed = new int[array.length]; + for (int i = 0; i < array.length; i++) + { + final String[] args = array[i].split("x"); + if (args.length < 1) + { + throw new Exception("Invalid buffer definition: " + array[i]); + } + + parsed[i] = Integer.parseInt(args[0]); + } + + Arrays.sort(parsed); + _sizeClasses = parsed; + _tlBuffers = ThreadLocal.withInitial(() -> new NodeBuffer[_sizeClasses.length]); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "CellPathFinding: Problem during buffer init: " + e.getMessage(), e); + throw new Error("CellPathFinding: load aborted"); + } + } + + /** + * Converts a geodata position to a pathnode position. + * @param geoPos the geodata position + * @return the corresponding pathnode position + */ + public short getNodePos(int geoPos) + { + return (short) (geoPos >> 3); + } + + /** + * Converts a node position to a pathnode block position. + * @param nodePos the node position + * @return the pathnode block position (range: 0 to 255) + */ + public short getNodeBlock(int nodePos) + { + return (short) (nodePos % 256); + } + + /** + * Retrieves the X region of a node position. + * @param nodePos the node position + * @return the X region + */ + public byte getRegionX(int nodePos) + { + return (byte) ((nodePos >> 8) + World.TILE_X_MIN); + } + + /** + * Retrieves the Y region of a node position. + * @param nodePos the node position + * @return the Y region + */ + public byte getRegionY(int nodePos) + { + return (byte) ((nodePos >> 8) + World.TILE_Y_MIN); + } + + /** + * Calculates the region offset based on region X and Y. + * @param rx the X region + * @param ry the Y region + * @return the region offset + */ + public short getRegionOffset(byte rx, byte ry) + { + return (short) ((rx << 5) + ry); + } + + /** + * Converts a pathnode X coordinate to a world X coordinate. + * @param nodeX the pathnode X coordinate + * @return the corresponding world X coordinate + */ + public int calculateWorldX(short nodeX) + { + return World.WORLD_X_MIN + (nodeX * 128) + 48; + } + + /** + * Converts a pathnode Y coordinate to a world Y coordinate. + * @param nodeY the pathnode Y coordinate + * @return the corresponding world Y coordinate + */ + public int calculateWorldY(short nodeY) + { + return World.WORLD_Y_MIN + (nodeY * 128) + 48; + } + + /** + * Finds a path between two world positions considering geodata and an instance. + * @param x the starting X coordinate + * @param y the starting Y coordinate + * @param z the starting Z coordinate + * @param tx the target X coordinate + * @param ty the target Y coordinate + * @param tz the target Z coordinate + * @param instanceId the instance ID to consider for pathfinding + * @param playable whether the pathfinding is for a playable character + * @return a list of pathnodes forming the path, or {@code null} if no path is found + */ + public List findPath(int x, int y, int z, int tx, int ty, int tz, int instanceId, boolean playable) + { + final GeoEngine geoEngine = GeoEngine.getInstance(); + if (!geoEngine.hasGeo(x, y)) + { + return null; + } + + if (!geoEngine.hasGeo(tx, ty)) + { + return null; + } + + final int gx = GeoEngine.getGeoX(x); + final int gy = GeoEngine.getGeoY(y); + final int gtx = GeoEngine.getGeoX(tx); + final int gty = GeoEngine.getGeoY(ty); + final NodeBuffer buffer = alloc(64 + (2 * Math.max(Math.abs(gx - gtx), Math.abs(gy - gty)))); + if (buffer == null) + { + return null; + } + + final int gz = geoEngine.getHeight(x, y, z); + final int gtz = geoEngine.getHeight(tx, ty, tz); + List path; + try + { + path = buffer.findPath(gx, gy, gz, gtx, gty, gtz); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "CellPathFinding: Problem finding path: " + e.getMessage(), e); + return null; + } + finally + { + buffer.free(); + } + + if (path == null) + { + return null; + } + + if ((path.size() < 3) || (GeoEngineConfig.MAX_POSTFILTER_PASSES <= 0)) + { + return path; + } + + return applyPostFiltering(path, x, y, z, instanceId, playable); + } + + /** + * Apply post-filtering to remove unnecessary waypoints. Enhanced version with better logic and configurable iterations. + * @param initialPath the initial path to optimize + * @param startX starting world X coordinate + * @param startY starting world Y coordinate + * @param startZ starting world Z coordinate + * @param instanceId the instance ID to consider for pathfinding + * @param playable whether the pathfinding is for a playable character + * @return optimized path + */ + private List applyPostFiltering(List initialPath, int startX, int startY, int startZ, int instanceId, boolean playable) + { + final GeoEngine geoEngine = GeoEngine.getInstance(); + List path = initialPath; + + int pass = 0; + boolean changed; + do + { + pass++; + changed = false; + int currentX = startX; + int currentY = startY; + int currentZ = startZ; + + final List optimizedPath = new ArrayList<>(); + for (int i = 0; i < (path.size() - 1); i++) + { + final GeoLocation current = path.get(i); + final GeoLocation next = path.get(i + 1); + + // Check if we can move directly to the next waypoint. + if (geoEngine.canMoveToTarget(currentX, currentY, currentZ, next.getX(), next.getY(), next.getZ(), instanceId)) + { + // Skip current waypoint. + changed = true; + } + else + { + // Keep current waypoint. + optimizedPath.add(current); + currentX = current.getX(); + currentY = current.getY(); + currentZ = current.getZ(); + } + } + + // Always add the final destination. + if (!path.isEmpty()) + { + optimizedPath.add(path.get(path.size() - 1)); + } + + path = optimizedPath; + } + while (playable && changed && (path.size() > 2) && (pass < GeoEngineConfig.MAX_POSTFILTER_PASSES)); + + return path; + } + + /** + * Returns a per-thread buffer whose map size is at least {@code size}. Lazily allocates one buffer per size class per thread; never blocks. + * @param size the minimum map size required + * @return a usable buffer, or {@code null} when {@code size} exceeds the largest configured class + */ + private NodeBuffer alloc(int size) + { + final NodeBuffer[] perThread = _tlBuffers.get(); + for (int i = 0; i < _sizeClasses.length; i++) + { + if (_sizeClasses[i] >= size) + { + NodeBuffer buffer = perThread[i]; + if (buffer == null) + { + buffer = new NodeBuffer(_sizeClasses[i]); + perThread[i] = buffer; + } + + return buffer; + } + } + + return null; + } + + public static PathFinding getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final PathFinding INSTANCE = new PathFinding(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/util/GridLineIterator2D.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/util/GridLineIterator2D.java new file mode 100644 index 0000000..e9dcc7d --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/util/GridLineIterator2D.java @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.geoengine.util; + +/** + * An iterator that steps through points on a straight line using Bresenham's line-drawing algorithm. + *

        + * Bresenham's algorithm calculates which grid points should be part of a straight line between two points. + *

        + * @author Mobius + */ +public class GridLineIterator2D +{ + // Current position of the iterator. + private int _currentX; // Current X position of the iterator. + private int _currentY; // Current Y position of the iterator. + + // Target endpoint of the line. + private final int _targetX; // Target X endpoint of the line. + private final int _targetY; // Target Y endpoint of the line. + + // Step directions for movement along the X and Y axes. + private final int _stepX; // Step direction for X (-1, 0 or 1). + private final int _stepY; // Step direction for Y (-1, 0 or 1). + + // Absolute distances along X and Y axes. + private final int _deltaX; // Absolute difference in X coordinates. + private final int _deltaY; // Absolute difference in Y coordinates. + + // Indicates whether the line is steep (Y changes faster than X). + private final boolean _isSteep; + + // Tracks the accumulated error term for adjusting the minor axis. + private int _accumulatedError; + + // Indicates whether the iteration has started. + private boolean _hasStarted; + + /** + * Initializes the iterator to step through points along a line from (startX, startY) to (endX, endY). + * @param startX the X-coordinate of the start point + * @param startY the Y-coordinate of the start point + * @param endX the X-coordinate of the end point + * @param endY the Y-coordinate of the end point + */ + public GridLineIterator2D(int startX, int startY, int endX, int endY) + { + // Initialize current position. + _currentX = startX; + _currentY = startY; + + // Initialize target position. + _targetX = endX; + _targetY = endY; + + // Calculate absolute differences in X and Y coordinates. + _deltaX = Math.abs(endX - startX); + _deltaY = Math.abs(endY - startY); + + // Determine step directions for X and Y. + _stepX = Integer.compare(endX, startX); // -1, 0 or 1. + _stepY = Integer.compare(endY, startY); // -1, 0 or 1. + + // Determine if the line is steep (Y changes more than X). + _isSteep = _deltaY > _deltaX; + + // Initialize the accumulated error term. + // Start with half the dominant axis to balance initial adjustment. + _accumulatedError = (_isSteep ? _deltaY : _deltaX) / 2; + + // Set the initial iteration state. + _hasStarted = false; + } + + /** + * Advances the iterator to the next point on the line. + *

        + * This method implements Bresenham's line-drawing algorithm,
        + * using an accumulated error term to determine when to step along the minor axis. + *

        + * @return {@code true} if the iterator successfully moved to the next point or
        + * {@code false} if the end of the line has been reached. + */ + public boolean next() + { + if (!_hasStarted) + { + _hasStarted = true; // Start the iteration on the first call. + return true; + } + + // If the current position equals the target, stop iteration. + if ((_currentX == _targetX) && (_currentY == _targetY)) + { + return false; + } + + // Move along the line. + if (_isSteep) + { + // For steep lines, Y is the dominant axis. + _currentY += _stepY; + _accumulatedError -= _deltaX; + if (_accumulatedError < 0) + { + // Step along X when the accumulated error exceeds the threshold. + _currentX += _stepX; + _accumulatedError += _deltaY; + } + } + else + { + // For shallow lines, X is the dominant axis. + _currentX += _stepX; + _accumulatedError -= _deltaY; + if (_accumulatedError < 0) + { + // Step along Y when the accumulated error exceeds the threshold. + _currentY += _stepY; + _accumulatedError += _deltaX; + } + } + + return true; // Continue iteration. + } + + /** + * Returns the current X-coordinate of the iterator's position. + * @return the current X-coordinate + */ + public int x() + { + return _currentX; + } + + /** + * Returns the current Y-coordinate of the iterator's position. + * @return the current Y-coordinate + */ + public int y() + { + return _currentY; + } + + @Override + public String toString() + { + return "[" + _currentX + ", " + _currentY + "]"; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/util/GridLineIterator3D.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/util/GridLineIterator3D.java new file mode 100644 index 0000000..7be4b9e --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/geoengine/util/GridLineIterator3D.java @@ -0,0 +1,233 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.geoengine.util; + +/** + * An iterator that steps through points on a straight line using Bresenham's line-drawing algorithm. + *

        + * Bresenham's algorithm calculates which grid points should be part of a straight line between two points. + *

        + * @author Mobius + */ +public class GridLineIterator3D +{ + // Current position of the iterator. + private int _currentX; // Current X position of the iterator. + private int _currentY; // Current Y position of the iterator. + private int _currentZ; // Current Z position of the iterator. + + // Target endpoint of the line. + private final int _targetX; // Target X endpoint of the line. + private final int _targetY; // Target Y endpoint of the line. + private final int _targetZ; // Target Z endpoint of the line. + + // Absolute distances along X, Y, and Z axes. + private final int _deltaX; // Absolute difference in X coordinates. + private final int _deltaY; // Absolute difference in Y coordinates. + private final int _deltaZ; // Absolute difference in Z coordinates. + + // Step directions for movement along the X, Y, and Z axes. + private final int _stepX; // Step direction for X (-1 or 1). + private final int _stepY; // Step direction for Y (-1 or 1). + private final int _stepZ; // Step direction for Z (-1 or 1). + + // Tracks the accumulated error terms for adjusting the minor axes. + private int _accumulatedErrorXY; // Accumulated error for the Y axis relative to X. + private int _accumulatedErrorXZ; // Accumulated error for the Z axis relative to X. + + // Indicates whether the iteration has started. + private boolean _hasStarted; + + /** + * Initializes the iterator to step through points along a 3D line from (startX, startY, startZ) to (endX, endY, endZ). + * @param startX the X-coordinate of the start point + * @param startY the Y-coordinate of the start point + * @param startZ the Z-coordinate of the start point + * @param endX the X-coordinate of the end point + * @param endY the Y-coordinate of the end point + * @param endZ the Z-coordinate of the end point + */ + public GridLineIterator3D(int startX, int startY, int startZ, int endX, int endY, int endZ) + { + // Initialize current position to the starting point. + _currentX = startX; + _currentY = startY; + _currentZ = startZ; + + // Initialize target position. + _targetX = endX; + _targetY = endY; + _targetZ = endZ; + + // Calculate absolute differences in X, Y, and Z coordinates. + _deltaX = Math.abs(endX - startX); + _deltaY = Math.abs(endY - startY); + _deltaZ = Math.abs(endZ - startZ); + + // Determine step directions for X, Y, and Z based on the sign of the difference. + _stepX = startX < endX ? 1 : -1; + _stepY = startY < endY ? 1 : -1; + _stepZ = startZ < endZ ? 1 : -1; + + // Initialize accumulated error terms based on the dominant axis. + // The dominant axis is the one with the largest delta, as it determines the primary iteration direction. + if ((_deltaX >= _deltaY) && (_deltaX >= _deltaZ)) + { + _accumulatedErrorXY = _accumulatedErrorXZ = _deltaX / 2; + } + else if ((_deltaY >= _deltaX) && (_deltaY >= _deltaZ)) + { + _accumulatedErrorXY = _accumulatedErrorXZ = _deltaY / 2; + } + else + { + _accumulatedErrorXY = _accumulatedErrorXZ = _deltaZ / 2; + } + + // Set the initial iteration state to indicate the first point has not yet been processed. + _hasStarted = false; + } + + /** + * Advances the iterator to the next point on the line. + *

        + * This method implements Bresenham's line-drawing algorithm,
        + * which uses accumulated error terms to decide when to step along the minor axes. + *

        + * @return {@code true} if the iterator successfully moved to the next point;
        + * {@code false} if the end of the line has been reached. + */ + public boolean next() + { + if (!_hasStarted) + { + // On the first call, mark the iteration as started but do not move. + _hasStarted = true; + return true; + } + + // Stop the iteration if the current position equals the target. + if ((_currentX == _targetX) && (_currentY == _targetY) && (_currentZ == _targetZ)) + { + return false; + } + + // Move along the dominant axis and adjust minor axes as needed. + if ((_deltaX >= _deltaY) && (_deltaX >= _deltaZ)) + { + // Dominant axis X. + _currentX += _stepX; + + // Adjust Y based on accumulated error. + _accumulatedErrorXY += _deltaY; + if (_accumulatedErrorXY >= _deltaX) + { + _currentY += _stepY; + _accumulatedErrorXY -= _deltaX; + } + + // Adjust Z based on accumulated error. + _accumulatedErrorXZ += _deltaZ; + if (_accumulatedErrorXZ >= _deltaX) + { + _currentZ += _stepZ; + _accumulatedErrorXZ -= _deltaX; + } + } + else if ((_deltaY >= _deltaX) && (_deltaY >= _deltaZ)) + { + // Dominant axis Y. + _currentY += _stepY; + + // Adjust X based on accumulated error. + _accumulatedErrorXY += _deltaX; + if (_accumulatedErrorXY >= _deltaY) + { + _currentX += _stepX; + _accumulatedErrorXY -= _deltaY; + } + + // Adjust Z based on accumulated error. + _accumulatedErrorXZ += _deltaZ; + if (_accumulatedErrorXZ >= _deltaY) + { + _currentZ += _stepZ; + _accumulatedErrorXZ -= _deltaY; + } + } + else + { + // Dominant axis Z. + _currentZ += _stepZ; + + // Adjust X based on accumulated error. + _accumulatedErrorXY += _deltaX; + if (_accumulatedErrorXY >= _deltaZ) + { + _currentX += _stepX; + _accumulatedErrorXY -= _deltaZ; + } + + // Adjust Y based on accumulated error. + _accumulatedErrorXZ += _deltaY; + if (_accumulatedErrorXZ >= _deltaZ) + { + _currentY += _stepY; + _accumulatedErrorXZ -= _deltaZ; + } + } + + return true; // Continue iteration. + } + + /** + * Returns the current X-coordinate of the iterator's position. + * @return the current X-coordinate + */ + public int x() + { + return _currentX; + } + + /** + * Returns the current Y-coordinate of the iterator's position. + * @return the current Y-coordinate + */ + public int y() + { + return _currentY; + } + + /** + * Returns the current Z-coordinate of the iterator's position. + * @return the current Z-coordinate + */ + public int z() + { + return _currentZ; + } + + @Override + public String toString() + { + return "[" + _currentX + ", " + _currentY + ", " + _currentZ + "]"; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/handler/ActionClickHandler.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/handler/ActionClickHandler.java new file mode 100644 index 0000000..14fd9b6 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/handler/ActionClickHandler.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.handler; + +import java.util.EnumMap; +import java.util.Map; + +import org.l2jmobius.gameserver.entity.actor.enums.creature.InstanceType; + +public class ActionClickHandler implements IHandler +{ + private final Map _actions; + + protected ActionClickHandler() + { + _actions = new EnumMap<>(InstanceType.class); + } + + @Override + public void registerHandler(IActionClickHandler handler) + { + _actions.put(handler.getInstanceType(), handler); + } + + @Override + public synchronized void removeHandler(IActionClickHandler handler) + { + _actions.remove(handler.getInstanceType()); + } + + @Override + public IActionClickHandler getHandler(InstanceType iType) + { + IActionClickHandler result = null; + for (InstanceType t = iType; t != null; t = t.getParent()) + { + result = _actions.get(t); + if (result != null) + { + break; + } + } + + return result; + } + + @Override + public int size() + { + return _actions.size(); + } + + public static ActionClickHandler getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final ActionClickHandler INSTANCE = new ActionClickHandler(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/handler/ActionShiftHandler.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/handler/ActionShiftHandler.java new file mode 100644 index 0000000..7371ab1 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/handler/ActionShiftHandler.java @@ -0,0 +1,79 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.handler; + +import java.util.EnumMap; +import java.util.Map; + +import org.l2jmobius.gameserver.entity.actor.enums.creature.InstanceType; + +/** + * @author UnAfraid + */ +public class ActionShiftHandler implements IHandler +{ + private final Map _actionsShift; + + protected ActionShiftHandler() + { + _actionsShift = new EnumMap<>(InstanceType.class); + } + + @Override + public void registerHandler(IActionShiftHandler handler) + { + _actionsShift.put(handler.getInstanceType(), handler); + } + + @Override + public synchronized void removeHandler(IActionShiftHandler handler) + { + _actionsShift.remove(handler.getInstanceType()); + } + + @Override + public IActionShiftHandler getHandler(InstanceType iType) + { + IActionShiftHandler result = null; + for (InstanceType t = iType; t != null; t = t.getParent()) + { + result = _actionsShift.get(t); + if (result != null) + { + break; + } + } + + return result; + } + + @Override + public int size() + { + return _actionsShift.size(); + } + + public static ActionShiftHandler getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final ActionShiftHandler INSTANCE = new ActionShiftHandler(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/handler/AdminCommandHandler.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/handler/AdminCommandHandler.java new file mode 100644 index 0000000..42f1093 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/handler/AdminCommandHandler.java @@ -0,0 +1,163 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.handler; + +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.commons.threads.ThreadPool; +import org.l2jmobius.commons.time.TimeUtil; +import org.l2jmobius.gameserver.config.GeneralConfig; +import org.l2jmobius.gameserver.data.xml.AdminData; +import org.l2jmobius.gameserver.entity.WorldObject; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.enums.player.PlayerAction; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.serverpackets.ConfirmDlg; +import org.l2jmobius.gameserver.util.GMAudit; + +public class AdminCommandHandler implements IHandler +{ + private static final Logger LOGGER = Logger.getLogger(AdminCommandHandler.class.getName()); + + private final Map _datatable; + + protected AdminCommandHandler() + { + _datatable = new HashMap<>(); + } + + @Override + public void registerHandler(IAdminCommandHandler handler) + { + for (String id : handler.getCommandList()) + { + _datatable.put(id, handler); + } + } + + @Override + public synchronized void removeHandler(IAdminCommandHandler handler) + { + for (String id : handler.getCommandList()) + { + _datatable.remove(id); + } + } + + /** + * WARNING: Please use {@link #onCommand(Player, String, boolean)} instead. + */ + @Override + public IAdminCommandHandler getHandler(String adminCommand) + { + String command = adminCommand; + if (adminCommand.contains(" ")) + { + command = adminCommand.substring(0, adminCommand.indexOf(' ')); + } + + return _datatable.get(command); + } + + public void onCommand(Player player, String fullCommand, boolean useConfirm) + { + if (!player.isGM()) + { + return; + } + + final String command = fullCommand.split(" ")[0]; + final String commandNoPrefix = command.substring(6); + final IAdminCommandHandler handler = getHandler(command); + if (handler == null) + { + player.sendMessage("The command '" + commandNoPrefix + "' does not exist!"); + LOGGER.warning("No handler registered for admin command '" + command + "'"); + return; + } + + if (!AdminData.getInstance().hasAccess(command, player.getAccessLevel())) + { + player.sendMessage("You don't have the access rights to use this command!"); + LOGGER.warning(player + " tried to use admin command '" + command + "', without proper access level!"); + return; + } + + if (useConfirm && AdminData.getInstance().requireConfirm(command)) + { + player.setAdminConfirmCmd(fullCommand); + final ConfirmDlg dlg = new ConfirmDlg(SystemMessageId.S1_3); + dlg.getSystemMessage().addString("Are you sure you want execute command '" + commandNoPrefix + "' ?"); + player.addAction(PlayerAction.ADMIN_COMMAND); + player.sendPacket(dlg); + } + else + { + // Admin Commands must run through a long running task, otherwise a command that takes too much time will freeze the server, this way you'll feel only a minor spike. + ThreadPool.execute(() -> + { + final long begin = System.currentTimeMillis(); + try + { + if (GeneralConfig.GMAUDIT) + { + final WorldObject target = player.getTarget(); + GMAudit.logAction(player.getName() + " [" + player.getObjectId() + "]", fullCommand, (target != null ? target.getName() : "no-target")); + } + + handler.onCommand(fullCommand, player); + } + catch (RuntimeException e) + { + player.sendMessage("Exception during execution of '" + fullCommand + "': " + e.toString()); + LOGGER.log(Level.WARNING, "Exception during execution of " + fullCommand, e); + } + finally + { + final long runtime = System.currentTimeMillis() - begin; + if (runtime > 5000) + { + player.sendMessage("The execution of '" + fullCommand + "' took " + TimeUtil.formatDuration(runtime) + "."); + } + } + }); + } + } + + @Override + public int size() + { + return _datatable.size(); + } + + public static AdminCommandHandler getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final AdminCommandHandler INSTANCE = new AdminCommandHandler(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/handler/BypassHandler.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/handler/BypassHandler.java new file mode 100644 index 0000000..5e57b54 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/handler/BypassHandler.java @@ -0,0 +1,79 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.handler; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author nBd + */ +public class BypassHandler implements IHandler +{ + private final Map _datatable; + + protected BypassHandler() + { + _datatable = new HashMap<>(); + } + + @Override + public void registerHandler(IBypassHandler handler) + { + for (String element : handler.getCommandList()) + { + _datatable.put(element.toLowerCase(), handler); + } + } + + @Override + public synchronized void removeHandler(IBypassHandler handler) + { + for (String element : handler.getCommandList()) + { + _datatable.remove(element.toLowerCase()); + } + } + + @Override + public IBypassHandler getHandler(String commandValue) + { + String command = commandValue; + if (command.contains(" ")) + { + command = command.substring(0, command.indexOf(' ')); + } + + return _datatable.get(command.toLowerCase()); + } + + @Override + public int size() + { + return _datatable.size(); + } + + public static BypassHandler getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final BypassHandler INSTANCE = new BypassHandler(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/handler/ChatHandler.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/handler/ChatHandler.java new file mode 100644 index 0000000..b96a113 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/handler/ChatHandler.java @@ -0,0 +1,91 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.handler; + +import java.util.EnumMap; +import java.util.Map; + +import org.l2jmobius.gameserver.network.enums.ChatType; + +/** + * This class handles all chat handlers + * @author durgus + */ +public class ChatHandler implements IHandler +{ + private final Map _datatable = new EnumMap<>(ChatType.class); + + /** + * Singleton constructor + */ + protected ChatHandler() + { + } + + /** + * Register a new chat handler + * @param handler + */ + @Override + public void registerHandler(IChatHandler handler) + { + for (ChatType type : handler.getChatTypeList()) + { + _datatable.put(type, handler); + } + } + + @Override + public synchronized void removeHandler(IChatHandler handler) + { + for (ChatType type : handler.getChatTypeList()) + { + _datatable.remove(type); + } + } + + /** + * Get the chat handler for the given chat type + * @param chatType + * @return + */ + @Override + public IChatHandler getHandler(ChatType chatType) + { + return _datatable.get(chatType); + } + + /** + * Returns the size + * @return + */ + @Override + public int size() + { + return _datatable.size(); + } + + public static ChatHandler getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final ChatHandler INSTANCE = new ChatHandler(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/handler/CommunityBoardHandler.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/handler/CommunityBoardHandler.java new file mode 100644 index 0000000..2eae662 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/handler/CommunityBoardHandler.java @@ -0,0 +1,250 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.handler; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Logger; + +import org.l2jmobius.gameserver.config.GeneralConfig; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.util.HtmlUtil; + +/** + * Community Board handler. + * @author Zoey76 + */ +public class CommunityBoardHandler implements IHandler +{ + private static final Logger LOG = Logger.getLogger(CommunityBoardHandler.class.getName()); + /** The registered handlers. */ + private final Map _datatable = new HashMap<>(); + /** The bypasses used by the players. */ + private final Map _bypasses = new ConcurrentHashMap<>(); + + protected CommunityBoardHandler() + { + // Prevent external initialization. + } + + @Override + public void registerHandler(IParseBoardHandler handler) + { + for (String cmd : handler.getCommandList()) + { + _datatable.put(cmd.toLowerCase(), handler); + } + } + + @Override + public synchronized void removeHandler(IParseBoardHandler handler) + { + for (String cmd : handler.getCommandList()) + { + _datatable.remove(cmd.toLowerCase()); + } + } + + @Override + public IParseBoardHandler getHandler(String cmd) + { + for (IParseBoardHandler cb : _datatable.values()) + { + for (String command : cb.getCommandList()) + { + if (cmd.toLowerCase().startsWith(command.toLowerCase())) + { + return cb; + } + } + } + + return null; + } + + @Override + public int size() + { + return _datatable.size(); + } + + /** + * Verifies if the string is a registered community board command. + * @param cmd the command to verify + * @return {@code true} if the command has been registered, {@code false} otherwise + */ + public boolean isCommunityBoardCommand(String cmd) + { + return getHandler(cmd) != null; + } + + /** + * Parses a community board command. + * @param command the command + * @param player the player + */ + public void handleParseCommand(String command, Player player) + { + if (player == null) + { + return; + } + + if (!GeneralConfig.ENABLE_COMMUNITY_BOARD) + { + player.sendPacket(SystemMessageId.THE_COMMUNITY_SERVER_IS_CURRENTLY_OFFLINE); + return; + } + + final IParseBoardHandler cb = getHandler(command); + if (cb == null) + { + LOG.warning(CommunityBoardHandler.class.getSimpleName() + ": Couldn't find parse handler for command " + command + "!"); + return; + } + + cb.onCommand(command, player); + } + + /** + * Writes a command into the client. + * @param player the player + * @param url the command URL + * @param arg1 the first argument + * @param arg2 the second argument + * @param arg3 the third argument + * @param arg4 the fourth argument + * @param arg5 the fifth argument + */ + public void handleWriteCommand(Player player, String url, String arg1, String arg2, String arg3, String arg4, String arg5) + { + if (player == null) + { + return; + } + + if (!GeneralConfig.ENABLE_COMMUNITY_BOARD) + { + player.sendPacket(SystemMessageId.THE_COMMUNITY_SERVER_IS_CURRENTLY_OFFLINE); + return; + } + + String cmd = ""; + switch (url) + { + case "Topic": + { + cmd = "_bbstop"; + break; + } + case "Post": + { + cmd = "_bbspos"; // TODO: Implement. + break; + } + case "Region": + { + cmd = "_bbsloc"; + break; + } + case "Notice": + { + cmd = "_bbsclan"; + break; + } + case "Mail": + { + cmd = "_bbsmail"; + break; + } + case "Search": + { + cmd = "_bbsclan"; + break; + } + case "CMail": + { + cmd = "_bbsclan"; + break; + } + default: + { + separateAndSend("

        The command: " + url + " is not implemented yet.


        ", player); + return; + } + } + + final IParseBoardHandler cb = getHandler(cmd); + if (cb == null) + { + LOG.warning(CommunityBoardHandler.class.getSimpleName() + ": Couldn't find write handler for command " + cmd + "!"); + return; + } + + if (!(cb instanceof IWriteBoardHandler)) + { + LOG.warning(CommunityBoardHandler.class.getSimpleName() + ": " + cb.getClass().getSimpleName() + " doesn't implement write!"); + return; + } + + ((IWriteBoardHandler) cb).writeCommunityBoardCommand(player, arg1, arg2, arg3, arg4, arg5); + } + + /** + * Sets the last bypass used by the player. + * @param player the player + * @param title the title + * @param bypass the bypass + */ + public void addBypass(Player player, String title, String bypass) + { + _bypasses.put(player.getObjectId(), title + "&" + bypass); + } + + /** + * Removes the last bypass used by the player. + * @param player the player + * @return the last bypass used + */ + public String removeBypass(Player player) + { + return _bypasses.remove(player.getObjectId()); + } + + /** + * Separates and send an HTML into multiple packets, to display into the community board.
        + * The limit is 16383 characters. + * @param html the HTML to send + * @param player the player + */ + public static void separateAndSend(String html, Player player) + { + HtmlUtil.sendCBHtml(player, html); + } + + public static CommunityBoardHandler getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final CommunityBoardHandler INSTANCE = new CommunityBoardHandler(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/handler/EffectHandler.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/handler/EffectHandler.java new file mode 100644 index 0000000..267a39a --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/handler/EffectHandler.java @@ -0,0 +1,82 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.handler; + +import java.util.HashMap; +import java.util.Map; + +import org.l2jmobius.gameserver.mechanics.effects.AbstractEffect; +import org.l2jmobius.gameserver.scripting.ScriptEngine; + +/** + * @author BiggBoss + */ +public class EffectHandler implements IHandler, String> +{ + private final Map> _handlers; + + protected EffectHandler() + { + _handlers = new HashMap<>(); + } + + @Override + public void registerHandler(Class handler) + { + _handlers.put(handler.getSimpleName(), handler); + } + + @Override + public synchronized void removeHandler(Class handler) + { + _handlers.remove(handler.getSimpleName()); + } + + @Override + public Class getHandler(String name) + { + return _handlers.get(name); + } + + @Override + public int size() + { + return _handlers.size(); + } + + public void executeScript() + { + try + { + ScriptEngine.getInstance().executeScript(ScriptEngine.EFFECT_MASTER_HANDLER_FILE); + } + catch (Exception e) + { + throw new Error("Problems while running EffectMasterHandler", e); + } + } + + private static class SingletonHolder + { + protected static final EffectHandler INSTANCE = new EffectHandler(); + } + + public static EffectHandler getInstance() + { + return SingletonHolder.INSTANCE; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/handler/IActionClickHandler.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/handler/IActionClickHandler.java new file mode 100644 index 0000000..0ef563b --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/handler/IActionClickHandler.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.handler; + +import java.util.logging.Logger; + +import org.l2jmobius.gameserver.entity.WorldObject; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.enums.creature.InstanceType; + +public interface IActionClickHandler +{ + Logger LOGGER = Logger.getLogger(IActionClickHandler.class.getName()); + + boolean onAction(Player player, WorldObject target, boolean interact); + + InstanceType getInstanceType(); +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/handler/IActionShiftHandler.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/handler/IActionShiftHandler.java new file mode 100644 index 0000000..6768f17 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/handler/IActionShiftHandler.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.handler; + +import java.util.logging.Logger; + +import org.l2jmobius.gameserver.entity.WorldObject; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.enums.creature.InstanceType; + +public interface IActionShiftHandler +{ + Logger LOGGER = Logger.getLogger(IActionShiftHandler.class.getName()); + + boolean onAction(Player player, WorldObject target, boolean interact); + + InstanceType getInstanceType(); +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/handler/IAdminCommandHandler.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/handler/IAdminCommandHandler.java new file mode 100644 index 0000000..88e4a18 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/handler/IAdminCommandHandler.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.handler; + +import org.l2jmobius.gameserver.entity.actor.Player; + +public interface IAdminCommandHandler +{ + /** + * This is the worker method that is called when someone uses a command. + * @param player + * @param command + * @return command success + */ + boolean onCommand(String command, Player player); + + /** + * This method is called at initialization to register all commands automatically. + * @return all known commands + */ + String[] getCommandList(); +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/handler/IBypassHandler.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/handler/IBypassHandler.java new file mode 100644 index 0000000..d77e7d3 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/handler/IBypassHandler.java @@ -0,0 +1,45 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.handler; + +import java.util.logging.Logger; + +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.Player; + +/** + * @author nBd + */ +public interface IBypassHandler +{ + Logger LOGGER = Logger.getLogger(IBypassHandler.class.getName()); + + /** + * This is the worker method that is called when someone uses a command. + * @param command + * @param player + * @param bypassOrigin + * @return success + */ + boolean onCommand(String command, Player player, Creature bypassOrigin); + + /** + * This method is called at initialization to register all commands automatically. + * @return all known commands + */ + String[] getCommandList(); +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/handler/IChatHandler.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/handler/IChatHandler.java new file mode 100644 index 0000000..022bb76 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/handler/IChatHandler.java @@ -0,0 +1,42 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.handler; + +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.network.enums.ChatType; + +/** + * Interface for chat handlers + * @author durgus + */ +public interface IChatHandler +{ + /** + * Handles a specific type of chat messages + * @param type + * @param player + * @param target + * @param text + */ + void onChat(ChatType type, Player player, String target, String text); + + /** + * Returns a list of all chat types registered to this handler + * @return + */ + ChatType[] getChatTypeList(); +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/handler/IHandler.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/handler/IHandler.java new file mode 100644 index 0000000..3b35656 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/handler/IHandler.java @@ -0,0 +1,33 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.handler; + +/** + * @author UnAfraid + * @param + * @param + */ +public interface IHandler +{ + void registerHandler(K handler); + + void removeHandler(K handler); + + K getHandler(V val); + + int size(); +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/handler/IItemHandler.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/handler/IItemHandler.java new file mode 100644 index 0000000..4deb9ed --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/handler/IItemHandler.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.handler; + +import java.util.logging.Logger; + +import org.l2jmobius.gameserver.entity.actor.Playable; +import org.l2jmobius.gameserver.entity.item.instance.Item; + +/** + * Mother class of all Item Handlers. + */ +public interface IItemHandler +{ + Logger LOGGER = Logger.getLogger(IItemHandler.class.getName()); + + /** + * Launch task associated to the item. + * @param playable the non-NPC character using the item + * @param item Item designating the item to use + * @param forceUse ctrl hold on item use + * @return {@code true} if the item all conditions are met and the item is used, {@code false} otherwise. + */ + boolean onItemUse(Playable playable, Item item, boolean forceUse); +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/handler/IParseBoardHandler.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/handler/IParseBoardHandler.java new file mode 100644 index 0000000..58e7017 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/handler/IParseBoardHandler.java @@ -0,0 +1,44 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.handler; + +import java.util.logging.Logger; + +import org.l2jmobius.gameserver.entity.actor.Player; + +/** + * Community Board interface. + * @author Zoey76 + */ +public interface IParseBoardHandler +{ + Logger LOG = Logger.getLogger(IParseBoardHandler.class.getName()); + + /** + * Parses a community board command. + * @param command the command + * @param player the player + * @return + */ + boolean onCommand(String command, Player player); + + /** + * Gets the community board commands. + * @return the community board commands + */ + String[] getCommandList(); +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/handler/IPunishmentHandler.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/handler/IPunishmentHandler.java new file mode 100644 index 0000000..d30642d --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/handler/IPunishmentHandler.java @@ -0,0 +1,36 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.handler; + +import java.util.logging.Logger; + +import org.l2jmobius.gameserver.mechanics.punishment.PunishmentTask; +import org.l2jmobius.gameserver.mechanics.punishment.PunishmentType; + +/** + * @author UnAfraid + */ +public interface IPunishmentHandler +{ + Logger LOGGER = Logger.getLogger(IPunishmentHandler.class.getName()); + + void onStart(PunishmentTask task); + + void onEnd(PunishmentTask task); + + PunishmentType getType(); +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/handler/ITargetTypeHandler.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/handler/ITargetTypeHandler.java new file mode 100644 index 0000000..adcdb79 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/handler/ITargetTypeHandler.java @@ -0,0 +1,34 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.handler; + +import java.util.List; + +import org.l2jmobius.gameserver.entity.WorldObject; +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.mechanics.skill.Skill; +import org.l2jmobius.gameserver.mechanics.skill.targets.TargetType; + +/** + * @author UnAfraid + */ +public interface ITargetTypeHandler +{ + List getTargetList(Skill skill, Creature creature, boolean onlyFirst, Creature target); + + Enum getTargetType(); +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/handler/IUserCommandHandler.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/handler/IUserCommandHandler.java new file mode 100644 index 0000000..c271d3e --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/handler/IUserCommandHandler.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.handler; + +import java.util.logging.Logger; + +import org.l2jmobius.gameserver.entity.actor.Player; + +public interface IUserCommandHandler +{ + Logger LOGGER = Logger.getLogger(IUserCommandHandler.class.getName()); + + /** + * This is the worker method that is called when someone uses a command. + * @param id + * @param player + * @return command success + */ + boolean onCommand(int id, Player player); + + /** + * This method is called at initialization to register all the command ids automatically. + * @return all known commands ids + */ + int[] getCommandList(); +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/handler/IVoicedCommandHandler.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/handler/IVoicedCommandHandler.java new file mode 100644 index 0000000..49f94b7 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/handler/IVoicedCommandHandler.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.handler; + +import java.util.logging.Logger; + +import org.l2jmobius.gameserver.entity.actor.Player; + +public interface IVoicedCommandHandler +{ + Logger LOGGER = Logger.getLogger(IVoicedCommandHandler.class.getName()); + + /** + * This is the worker method that is called when someone uses a command. + * @param player + * @param command + * @param params + * @return command success + */ + boolean onCommand(String command, Player player, String params); + + /** + * This method is called at initialization to register all commands automatically. + * @return all known commands + */ + String[] getCommandList(); +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/handler/IWriteBoardHandler.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/handler/IWriteBoardHandler.java new file mode 100644 index 0000000..5f41503 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/handler/IWriteBoardHandler.java @@ -0,0 +1,38 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.handler; + +import org.l2jmobius.gameserver.entity.actor.Player; + +/** + * Community Board interface. + * @author Zoey76 + */ +public interface IWriteBoardHandler extends IParseBoardHandler +{ + /** + * Writes a community board command into the client. + * @param player the player + * @param arg1 the first argument + * @param arg2 the second argument + * @param arg3 the third argument + * @param arg4 the fourth argument + * @param arg5 the fifth argument + * @return + */ + boolean writeCommunityBoardCommand(Player player, String arg1, String arg2, String arg3, String arg4, String arg5); +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/handler/ItemHandler.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/handler/ItemHandler.java new file mode 100644 index 0000000..1e06201 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/handler/ItemHandler.java @@ -0,0 +1,97 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.handler; + +import java.util.HashMap; +import java.util.Map; + +import org.l2jmobius.gameserver.entity.item.EtcItem; + +/** + * This class manages handlers of items + * @author UnAfraid + */ +public class ItemHandler implements IHandler +{ + private final Map _datatable; + + /** + * Constructor of ItemHandler + */ + protected ItemHandler() + { + _datatable = new HashMap<>(); + } + + /** + * Adds handler of item type in datatable.
        + * Concept :

        + * This handler is put in datatable Map <String ; IItemHandler > for each ID corresponding to an item type (existing in classes of package itemhandlers) sets as key of the Map. + * @param handler (IItemHandler) + */ + @Override + public void registerHandler(IItemHandler handler) + { + _datatable.put(handler.getClass().getSimpleName(), handler); + } + + @Override + public synchronized void removeHandler(IItemHandler handler) + { + _datatable.remove(handler.getClass().getSimpleName()); + } + + /** + * Returns the handler of the item + * @param item + * @return IItemHandler + */ + @Override + public IItemHandler getHandler(EtcItem item) + { + if ((item == null) || (item.getHandlerName() == null)) + { + return null; + } + + return _datatable.get(item.getHandlerName()); + } + + /** + * Returns the number of elements contained in datatable + * @return int : Size of the datatable + */ + @Override + public int size() + { + return _datatable.size(); + } + + /** + * Create ItemHandler if doesn't exist and returns ItemHandler + * @return ItemHandler + */ + public static ItemHandler getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final ItemHandler INSTANCE = new ItemHandler(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/handler/PunishmentHandler.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/handler/PunishmentHandler.java new file mode 100644 index 0000000..a891ddd --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/handler/PunishmentHandler.java @@ -0,0 +1,69 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.handler; + +import java.util.EnumMap; +import java.util.Map; + +import org.l2jmobius.gameserver.mechanics.punishment.PunishmentType; + +/** + * This class manages handlers of punishments. + * @author UnAfraid + */ +public class PunishmentHandler implements IHandler +{ + private final Map _handlers = new EnumMap<>(PunishmentType.class); + + protected PunishmentHandler() + { + } + + @Override + public void registerHandler(IPunishmentHandler handler) + { + _handlers.put(handler.getType(), handler); + } + + @Override + public synchronized void removeHandler(IPunishmentHandler handler) + { + _handlers.remove(handler.getType()); + } + + @Override + public IPunishmentHandler getHandler(PunishmentType val) + { + return _handlers.get(val); + } + + @Override + public int size() + { + return _handlers.size(); + } + + public static PunishmentHandler getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final PunishmentHandler INSTANCE = new PunishmentHandler(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/handler/TargetHandler.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/handler/TargetHandler.java new file mode 100644 index 0000000..4050a39 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/handler/TargetHandler.java @@ -0,0 +1,69 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.handler; + +import java.util.HashMap; +import java.util.Map; + +import org.l2jmobius.gameserver.mechanics.skill.targets.TargetType; + +/** + * @author UnAfraid + */ +public class TargetHandler implements IHandler> +{ + private final Map, ITargetTypeHandler> _datatable; + + protected TargetHandler() + { + _datatable = new HashMap<>(); + } + + @Override + public void registerHandler(ITargetTypeHandler handler) + { + _datatable.put(handler.getTargetType(), handler); + } + + @Override + public synchronized void removeHandler(ITargetTypeHandler handler) + { + _datatable.remove(handler.getTargetType()); + } + + @Override + public ITargetTypeHandler getHandler(Enum targetType) + { + return _datatable.get(targetType); + } + + @Override + public int size() + { + return _datatable.size(); + } + + public static TargetHandler getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final TargetHandler INSTANCE = new TargetHandler(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/handler/UserCommandHandler.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/handler/UserCommandHandler.java new file mode 100644 index 0000000..e4e565d --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/handler/UserCommandHandler.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.handler; + +import java.util.HashMap; +import java.util.Map; + +public class UserCommandHandler implements IHandler +{ + private final Map _datatable; + + protected UserCommandHandler() + { + _datatable = new HashMap<>(); + } + + @Override + public void registerHandler(IUserCommandHandler handler) + { + for (int id : handler.getCommandList()) + { + _datatable.put(id, handler); + } + } + + @Override + public synchronized void removeHandler(IUserCommandHandler handler) + { + for (int id : handler.getCommandList()) + { + _datatable.remove(id); + } + } + + @Override + public IUserCommandHandler getHandler(Integer userCommand) + { + return _datatable.get(userCommand); + } + + @Override + public int size() + { + return _datatable.size(); + } + + public static UserCommandHandler getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final UserCommandHandler INSTANCE = new UserCommandHandler(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/handler/VoicedCommandHandler.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/handler/VoicedCommandHandler.java new file mode 100644 index 0000000..6f620a6 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/handler/VoicedCommandHandler.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.handler; + +import java.util.HashMap; +import java.util.Map; + +public class VoicedCommandHandler implements IHandler +{ + private final Map _datatable; + + protected VoicedCommandHandler() + { + _datatable = new HashMap<>(); + } + + @Override + public void registerHandler(IVoicedCommandHandler handler) + { + for (String id : handler.getCommandList()) + { + _datatable.put(id, handler); + } + } + + @Override + public synchronized void removeHandler(IVoicedCommandHandler handler) + { + for (String id : handler.getCommandList()) + { + _datatable.remove(id); + } + } + + @Override + public IVoicedCommandHandler getHandler(String voicedCommand) + { + return _datatable.get(voicedCommand.contains(" ") ? voicedCommand.substring(0, voicedCommand.indexOf(' ')) : voicedCommand); + } + + @Override + public int size() + { + return _datatable.size(); + } + + public static VoicedCommandHandler getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final VoicedCommandHandler INSTANCE = new VoicedCommandHandler(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/interfaces/ILocational.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/interfaces/ILocational.java new file mode 100644 index 0000000..2caab1e --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/interfaces/ILocational.java @@ -0,0 +1,114 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.interfaces; + +import org.l2jmobius.gameserver.entity.actor.enums.creature.Position; +import org.l2jmobius.gameserver.util.LocationUtil; + +/** + * Object world location storage interface. + * @author xban1x + */ +public interface ILocational +{ + /** + * Gets the X coordinate of this object. + * @return the X coordinate + */ + int getX(); + + /** + * Gets the Y coordinate of this object. + * @return the current Y coordinate + */ + int getY(); + + /** + * Gets the Z coordinate of this object. + * @return the current Z coordinate + */ + int getZ(); + + /** + * Gets the heading of this object. + * @return the current heading + */ + int getHeading(); + + /** + * Gets the instance zone ID of this object. + * @return the ID of the instance zone this object is currently in (0 - not in any instance) + */ + int getInstanceId(); + + /** + * Gets this object's location. + * @return a {@link ILocational} object containing the current position of this object + */ + ILocational getLocation(); + + /** + * @param to + * @return the heading to the target specified + */ + default int calculateHeadingTo(ILocational to) + { + return LocationUtil.calculateHeadingFrom(getX(), getY(), to.getX(), to.getY()); + } + + /** + * @param target + * @return {@code true} if this location is in front of the target location based on the game's concept of position. + */ + default boolean isInFrontOf(ILocational target) + { + if (target == null) + { + return false; + } + + return Position.FRONT == Position.getPosition(this, target); + } + + /** + * @param target + * @return {@code true} if this location is in one of the sides of the target location based on the game's concept of position. + */ + default boolean isOnSideOf(ILocational target) + { + if (target == null) + { + return false; + } + + return Position.SIDE == Position.getPosition(this, target); + } + + /** + * @param target + * @return {@code true} if this location is behind the target location based on the game's concept of position. + */ + default boolean isBehind(ILocational target) + { + if (target == null) + { + return false; + } + + return Position.BACK == Position.getPosition(this, target); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/interfaces/IPositionable.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/interfaces/IPositionable.java new file mode 100644 index 0000000..39a3ec1 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/interfaces/IPositionable.java @@ -0,0 +1,58 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.interfaces; + +import org.l2jmobius.gameserver.entity.Location; + +/** + * Object world location storage and update interface. + * @author Zoey76 + */ +public interface IPositionable extends ILocational +{ + /** + * Sets all three coordinates of this object. + * @param x the new X coordinate + * @param y the new Y coordinate + * @param z the new Z coordinate + */ + void setXYZ(int x, int y, int z); + + /** + * Sets all three coordinates of this object. + * @param loc the object whose coordinates to use + */ + void setXYZ(ILocational loc); + + /** + * Sets the heading of this object. + * @param heading the new heading + */ + void setHeading(int heading); + + /** + * Changes the instance zone ID of this object. + * @param instanceId the ID of the instance zone to put this object in (0 - not in any instance) + */ + void setInstanceId(int instanceId); + + /** + * Changes the location of this object. + * @param loc the new location + */ + void setLocation(Location loc); +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/AntiFeedManager.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/AntiFeedManager.java new file mode 100644 index 0000000..5ce7271 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/AntiFeedManager.java @@ -0,0 +1,327 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.managers; + +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.logging.Logger; + +import org.l2jmobius.gameserver.config.PvpConfig; +import org.l2jmobius.gameserver.config.custom.DualboxCheckConfig; +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.mechanics.olympiad.Olympiad; +import org.l2jmobius.gameserver.network.GameClient; + +public class AntiFeedManager +{ + protected static final Logger LOGGER_ACCOUNTING = Logger.getLogger("accounting"); + + public static final int GAME_ID = 0; + public static final int OLYMPIAD_ID = 1; + public static final int TVT_ID = 2; + public static final int L2EVENT_ID = 3; + public static final int OFFLINE_PLAY = 4; + + private final Map _lastDeathTimes = new ConcurrentHashMap<>(); + private final Map> _eventIPs = new ConcurrentHashMap<>(); + + protected AntiFeedManager() + { + } + + /** + * Set time of the last player's death to current + * @param objectId Player's objectId + */ + public void setLastDeathTime(int objectId) + { + _lastDeathTimes.put(objectId, System.currentTimeMillis()); + } + + /** + * Check if current kill should be counted as non-feeded. + * @param attacker Attacker character + * @param target Target character + * @return True if kill is non-feeded. + */ + public boolean check(Creature attacker, Creature target) + { + if (!PvpConfig.ANTIFEED_ENABLE) + { + return true; + } + + if (target == null) + { + return false; + } + + final Player targetPlayer = target.asPlayer(); + if (targetPlayer == null) + { + return false; + } + + // Players in offline mode should't be valid targets. + if (targetPlayer.getClient().isDetached()) + { + return false; + } + + if ((PvpConfig.ANTIFEED_INTERVAL > 0) && _lastDeathTimes.containsKey(targetPlayer.getObjectId()) && ((System.currentTimeMillis() - _lastDeathTimes.get(targetPlayer.getObjectId())) < PvpConfig.ANTIFEED_INTERVAL)) + { + return false; + } + + if (PvpConfig.ANTIFEED_DUALBOX && (attacker != null)) + { + final Player attackerPlayer = attacker.asPlayer(); + if (attackerPlayer == null) + { + return false; + } + + final GameClient targetClient = targetPlayer.getClient(); + final GameClient attackerClient = attackerPlayer.getClient(); + if ((targetClient == null) || (attackerClient == null) || targetClient.isDetached() || attackerClient.isDetached()) + { + // Unable to check ip address. + return !PvpConfig.ANTIFEED_DISCONNECTED_AS_DUALBOX; + } + + return !targetClient.getIp().equals(attackerClient.getIp()); + } + + return true; + } + + /** + * Clears all timestamps + */ + public void clear() + { + _lastDeathTimes.clear(); + } + + /** + * Register new event for dualbox check. Should be called only once. + * @param eventId + */ + public void registerEvent(int eventId) + { + _eventIPs.putIfAbsent(eventId, new ConcurrentHashMap<>()); + } + + /** + * @param eventId + * @param player + * @param max + * @return If number of all simultaneous connections from player's IP address lower than max then increment connection count and return true.
        + * False if number of all simultaneous connections from player's IP address higher than max. + */ + public boolean tryAddPlayer(int eventId, Player player, int max) + { + return tryAddClient(eventId, player.getClient(), max); + } + + /** + * @param eventId + * @param client + * @param max + * @return If number of all simultaneous connections from player's IP address lower than max then increment connection count and return true.
        + * False if number of all simultaneous connections from player's IP address higher than max. + */ + public boolean tryAddClient(int eventId, GameClient client, int max) + { + if (client == null) + { + return false; // Unable to determine IP address. + } + + final Map event = _eventIPs.get(eventId); + if (event == null) + { + return false; // no such event registered + } + + final Integer addrHash = client.getIp().hashCode(); + final AtomicInteger connectionCount = event.computeIfAbsent(addrHash, _ -> new AtomicInteger()); + if ((connectionCount.get() + 1) <= (max + DualboxCheckConfig.DUALBOX_CHECK_WHITELIST.getOrDefault(addrHash, 0))) + { + connectionCount.incrementAndGet(); + return true; + } + + return false; + } + + /** + * Decreasing number of active connection from player's IP address + * @param eventId + * @param player + * @return true if success and false if any problem detected. + */ + public boolean removePlayer(int eventId, Player player) + { + return removeClient(eventId, player.getClient()); + } + + /** + * Decreasing number of active connection from player's IP address + * @param eventId + * @param client + * @return true if success and false if any problem detected. + */ + public boolean removeClient(int eventId, GameClient client) + { + if (client == null) + { + return false; // Unable to determine IP address. + } + + final Map event = _eventIPs.get(eventId); + if (event == null) + { + return false; // no such event registered + } + + final Integer addrHash = client.getIp().hashCode(); + return event.computeIfPresent(addrHash, (_, v) -> + { + if ((v == null) || (v.decrementAndGet() == 0)) + { + return null; + } + + return v; + }) != null; + } + + /** + * Remove player connection IP address from all registered events lists. + * @param client + */ + public void onDisconnect(GameClient client) + { + if (client == null) + { + return; + } + + final Player player = client.getPlayer(); + if (player == null) + { + return; + } + + if (player.isInOfflineMode()) + { + return; + } + + final String clientIp = client.getIp(); + if (clientIp == null) + { + return; + } + + for (Entry> entry : _eventIPs.entrySet()) + { + final int eventId = entry.getKey(); + if (eventId == OLYMPIAD_ID) + { + final AtomicInteger count = entry.getValue().get(clientIp.hashCode()); + if ((count != null) && (Olympiad.getInstance().isRegistered(player) || (player.getOlympiadGameId() != -1))) + { + count.decrementAndGet(); + } + } + else + { + removeClient(eventId, client); + } + } + } + + /** + * Clear all entries for this eventId. + * @param eventId + */ + public void clear(int eventId) + { + final Map event = _eventIPs.get(eventId); + if (event != null) + { + event.clear(); + } + } + + /** + * @param player + * @param max + * @return maximum number of allowed connections (whitelist + max) + */ + public int getLimit(Player player, int max) + { + return getLimit(player.getClient(), max); + } + + /** + * @param client + * @param max + * @return maximum number of allowed connections (whitelist + max) + */ + public int getLimit(GameClient client, int max) + { + if (client == null) + { + return max; + } + + final Integer addrHash = client.getIp().hashCode(); + int limit = max; + final Integer whiteListLimit = DualboxCheckConfig.DUALBOX_CHECK_WHITELIST.get(addrHash); + if (whiteListLimit != null) + { + if (whiteListLimit < 1) // DualboxCheckWhitelist takes 0 or negative value for unlimited number of connections. + { + return Integer.MAX_VALUE; + } + + limit += whiteListLimit; + } + + return limit; + } + + public static AntiFeedManager getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final AntiFeedManager INSTANCE = new AntiFeedManager(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/BoatManager.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/BoatManager.java new file mode 100644 index 0000000..ef33b1b --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/BoatManager.java @@ -0,0 +1,213 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.managers; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.l2jmobius.gameserver.config.GeneralConfig; +import org.l2jmobius.gameserver.entity.World; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.holders.creature.VehiclePathPoint; +import org.l2jmobius.gameserver.entity.actor.instance.Boat; +import org.l2jmobius.gameserver.entity.actor.templates.CreatureTemplate; +import org.l2jmobius.gameserver.network.serverpackets.ServerPacket; +import org.l2jmobius.gameserver.util.StatSet; + +public class BoatManager +{ + private final Map _boats = new ConcurrentHashMap<>(); + private final boolean[] _docksBusy = new boolean[3]; + + public static final int TALKING_ISLAND = 1; + public static final int GLUDIN_HARBOR = 2; + public static final int RUNE_HARBOR = 3; + + public static BoatManager getInstance() + { + return SingletonHolder.INSTANCE; + } + + protected BoatManager() + { + for (int i = 0; i < _docksBusy.length; i++) + { + _docksBusy[i] = false; + } + } + + public Boat getNewBoat(int boatId, int x, int y, int z, int heading) + { + if (!GeneralConfig.ALLOW_BOAT) + { + return null; + } + + final StatSet npcDat = new StatSet(); + npcDat.set("npcId", boatId); + npcDat.set("level", 0); + npcDat.set("jClass", "boat"); + npcDat.set("baseShldDef", 0); + npcDat.set("baseShldRate", 0); + npcDat.set("baseAccCombat", 38); + npcDat.set("baseEvasRate", 38); + npcDat.set("baseCritRate", 38); + + // npcDat.set("name", ""); + npcDat.set("collisionRadius", 0); + npcDat.set("collisionHeight", 0); + npcDat.set("sex", "male"); + npcDat.set("type", ""); + npcDat.set("baseAtkRange", 0); + npcDat.set("baseMpMax", 0); + npcDat.set("baseCpMax", 0); + npcDat.set("rewardExp", 0); + npcDat.set("rewardSp", 0); + npcDat.set("basePAtk", 0); + npcDat.set("baseMAtk", 0); + npcDat.set("basePAtkSpd", 0); + npcDat.set("aggroRange", 0); + npcDat.set("baseMAtkSpd", 0); + npcDat.set("rhand", 0); + npcDat.set("lhand", 0); + npcDat.set("armor", 0); + npcDat.set("baseWalkSpd", 0); + npcDat.set("baseRunSpd", 0); + npcDat.set("baseHpMax", 50000); + npcDat.set("baseHpReg", 3.e-3f); + npcDat.set("baseMpReg", 3.e-3f); + npcDat.set("basePDef", 100); + npcDat.set("baseMDef", 100); + + final Boat boat = new Boat(new CreatureTemplate(npcDat)); + boat.setHeading(heading); + boat.setXYZInvisible(x, y, z); + boat.spawnMe(); + + _boats.put(boat.getObjectId(), boat); + return boat; + } + + /** + * @param boatId + * @return + */ + public Boat getBoat(int boatId) + { + return _boats.get(boatId); + } + + /** + * Lock/unlock dock so only one ship can be docked + * @param h Dock Id + * @param value True if dock is locked + */ + public void dockShip(int h, boolean value) + { + try + { + _docksBusy[h] = value; + } + catch (ArrayIndexOutOfBoundsException e) + { + // Ignore. + } + } + + /** + * Check if dock is busy + * @param h Dock Id + * @return Trye if dock is locked + */ + public boolean dockBusy(int h) + { + try + { + return _docksBusy[h]; + } + catch (ArrayIndexOutOfBoundsException e) + { + return false; + } + } + + /** + * Broadcast one packet in both path points + * @param point1 + * @param point2 + * @param packet + */ + public void broadcastPacket(VehiclePathPoint point1, VehiclePathPoint point2, ServerPacket packet) + { + broadcastPacketsToPlayers(point1, point2, packet); + } + + /** + * Broadcast several packets in both path points + * @param point1 + * @param point2 + * @param packets + */ + public void broadcastPackets(VehiclePathPoint point1, VehiclePathPoint point2, ServerPacket... packets) + { + broadcastPacketsToPlayers(point1, point2, packets); + } + + private void broadcastPacketsToPlayers(VehiclePathPoint point1, VehiclePathPoint point2, ServerPacket... packets) + { + final double radSq = (double) GeneralConfig.BOAT_BROADCAST_RADIUS * GeneralConfig.BOAT_BROADCAST_RADIUS; + for (Player player : World.getPlayers()) + { + final int px = player.getX(); + final int py = player.getY(); + + final double dx1 = px - point1.getX(); + final double dy1 = py - point1.getY(); + final double dx1Sq = dx1 * dx1; + final double dy1Sq = dy1 * dy1; + if ((dx1Sq + dy1Sq) < radSq) + { + for (ServerPacket p : packets) + { + player.sendPacket(p); + } + continue; + } + + final double dx2 = px - point2.getX(); + final double dy2 = py - point2.getY(); + final double dx2Sq = dx2 * dx2; + final double dy2Sq = dy2 * dy2; + if ((dx2Sq + dy2Sq) < radSq) + { + for (ServerPacket p : packets) + { + player.sendPacket(p); + } + } + } + } + + private static class SingletonHolder + { + protected static final BoatManager INSTANCE = new BoatManager(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/CHSiegeManager.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/CHSiegeManager.java new file mode 100644 index 0000000..a01d6b1 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/CHSiegeManager.java @@ -0,0 +1,211 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.managers; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.Statement; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.logging.Logger; + +import org.l2jmobius.commons.database.DatabaseFactory; +import org.l2jmobius.gameserver.config.ConquerableHallSiegeConfig; +import org.l2jmobius.gameserver.data.sql.ClanHallTable; +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.clan.Clan; +import org.l2jmobius.gameserver.entity.zone.type.ClanHallZone; +import org.l2jmobius.gameserver.mechanics.siege.clanhalls.ClanHallSiegeEngine; +import org.l2jmobius.gameserver.mechanics.siege.clanhalls.SiegableHall; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; +import org.l2jmobius.gameserver.util.StatSet; + +/** + * @author BiggBoss + */ +public class CHSiegeManager +{ + private static final Logger LOGGER = Logger.getLogger(CHSiegeManager.class.getName()); + private static final String SQL_LOAD_HALLS = "SELECT * FROM siegable_clanhall"; + + private final Map _siegableHalls = new HashMap<>(); + + protected CHSiegeManager() + { + loadClanHalls(); + } + + private void loadClanHalls() + { + try (Connection con = DatabaseFactory.getConnection(); + Statement s = con.createStatement(); + ResultSet rs = s.executeQuery(SQL_LOAD_HALLS)) + { + _siegableHalls.clear(); + + while (rs.next()) + { + final int id = rs.getInt("clanHallId"); + final StatSet set = new StatSet(); + set.set("id", id); + set.set("name", rs.getString("name")); + set.set("ownerId", rs.getInt("ownerId")); + set.set("desc", rs.getString("desc")); + set.set("location", rs.getString("location")); + set.set("nextSiege", rs.getLong("nextSiege")); + set.set("siegeLength", rs.getLong("siegeLength")); + set.set("scheduleConfig", rs.getString("schedule_config")); + final SiegableHall hall = new SiegableHall(set); + _siegableHalls.put(id, hall); + ClanHallTable.addClanHall(hall); + } + + LOGGER.info(getClass().getSimpleName() + ": Loaded " + _siegableHalls.size() + " conquerable clan halls."); + } + catch (Exception e) + { + LOGGER.warning("CHSiegeManager: Could not load siegable clan halls!:" + e.getMessage()); + } + } + + public Map getConquerableHalls() + { + return _siegableHalls; + } + + public SiegableHall getSiegableHall(int clanHall) + { + return _siegableHalls.get(clanHall); + } + + public SiegableHall getNearbyClanHall(Creature creature) + { + return getNearbyClanHall(creature.getX(), creature.getY(), 10000); + } + + public SiegableHall getNearbyClanHall(int x, int y, int maxDist) + { + ClanHallZone zone = null; + for (Entry ch : _siegableHalls.entrySet()) + { + zone = ch.getValue().getZone(); + if ((zone != null) && (zone.getDistanceToZone(x, y) < maxDist)) + { + return ch.getValue(); + } + } + + return null; + } + + public ClanHallSiegeEngine getSiege(Creature creature) + { + final SiegableHall hall = getNearbyClanHall(creature); + return hall == null ? null : hall.getSiege(); + } + + public void registerClan(Clan clan, SiegableHall hall, Player player) + { + if (clan.getLevel() < ConquerableHallSiegeConfig.CHS_CLAN_MINLEVEL) + { + player.sendMessage("Only clans of level " + ConquerableHallSiegeConfig.CHS_CLAN_MINLEVEL + " or higher may register for a castle siege"); + } + else if (hall.isWaitingBattle()) + { + final SystemMessage sm = new SystemMessage(SystemMessageId.THE_DEADLINE_TO_REGISTER_FOR_THE_SIEGE_OF_S1_HAS_PASSED); + sm.addString(hall.getName()); + player.sendPacket(sm); + } + else if (hall.isInSiege()) + { + player.sendPacket(SystemMessageId.THIS_IS_NOT_THE_TIME_FOR_SIEGE_REGISTRATION_AND_SO_REGISTRATION_AND_CANCELLATION_CANNOT_BE_DONE); + } + else if (hall.getOwnerId() == clan.getId()) + { + player.sendPacket(SystemMessageId.THE_CLAN_THAT_OWNS_THE_CASTLE_IS_AUTOMATICALLY_REGISTERED_ON_THE_DEFENDING_SIDE); + } + else if ((clan.getCastleId() != 0) || (clan.getHideoutId() != 0)) + { + player.sendPacket(SystemMessageId.A_CLAN_THAT_OWNS_A_CASTLE_CANNOT_PARTICIPATE_IN_ANOTHER_SIEGE); + } + else if (hall.getSiege().checkIsAttacker(clan)) + { + player.sendPacket(SystemMessageId.YOU_HAVE_ALREADY_REQUESTED_A_SIEGE_BATTLE); + } + else if (isClanParticipating(clan)) + { + player.sendPacket(SystemMessageId.YOUR_APPLICATION_HAS_BEEN_DENIED_BECAUSE_YOU_HAVE_ALREADY_SUBMITTED_A_REQUEST_FOR_ANOTHER_SIEGE_BATTLE); + } + else if (hall.getSiege().getAttackers().size() >= ConquerableHallSiegeConfig.CHS_MAX_ATTACKERS) + { + player.sendPacket(SystemMessageId.NO_MORE_REGISTRATIONS_MAY_BE_ACCEPTED_FOR_THE_ATTACKER_SIDE); + } + else + { + hall.addAttacker(clan); + } + } + + public void unRegisterClan(Clan clan, SiegableHall hall) + { + if (!hall.isRegistering()) + { + return; + } + + hall.removeAttacker(clan); + } + + public boolean isClanParticipating(Clan clan) + { + for (SiegableHall hall : _siegableHalls.values()) + { + if ((hall.getSiege() != null) && hall.getSiege().checkIsAttacker(clan)) + { + return true; + } + } + + return false; + } + + public void onServerShutDown() + { + for (SiegableHall hall : _siegableHalls.values()) + { + if (hall.getSiege() == null) + { + continue; + } + + hall.getSiege().saveAttackers(); + } + } + + public static CHSiegeManager getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final CHSiegeManager INSTANCE = new CHSiegeManager(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/CaptchaManager.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/CaptchaManager.java new file mode 100644 index 0000000..7dd5a14 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/CaptchaManager.java @@ -0,0 +1,515 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.managers; + +import java.awt.image.BufferedImage; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.logging.Logger; + +import org.l2jmobius.commons.threads.ThreadPool; +import org.l2jmobius.commons.util.Rnd; +import org.l2jmobius.commons.util.StringUtil; +import org.l2jmobius.gameserver.config.ServerConfig; +import org.l2jmobius.gameserver.config.custom.AutoPlayConfig; +import org.l2jmobius.gameserver.config.custom.CaptchaConfig; +import org.l2jmobius.gameserver.data.xml.AdminData; +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.enums.player.TeleportWhereType; +import org.l2jmobius.gameserver.entity.actor.instance.Monster; +import org.l2jmobius.gameserver.mechanics.captcha.Captcha; +import org.l2jmobius.gameserver.mechanics.captcha.ImageData; +import org.l2jmobius.gameserver.mechanics.punishment.PunishmentAffect; +import org.l2jmobius.gameserver.mechanics.punishment.PunishmentTask; +import org.l2jmobius.gameserver.mechanics.punishment.PunishmentType; +import org.l2jmobius.gameserver.mechanics.script.QuestSound; +import org.l2jmobius.gameserver.network.Disconnection; +import org.l2jmobius.gameserver.network.PacketLogger; +import org.l2jmobius.gameserver.network.enums.ChatType; +import org.l2jmobius.gameserver.network.serverpackets.CreatureSay; +import org.l2jmobius.gameserver.network.serverpackets.LeaveWorld; +import org.l2jmobius.gameserver.network.serverpackets.NpcHtmlMessage; +import org.l2jmobius.gameserver.network.serverpackets.PledgeCrest; +import org.l2jmobius.gameserver.util.DDSConverter; +import org.l2jmobius.gameserver.util.MathUtil; + +/** + * @author Skache + */ +public class CaptchaManager +{ + private static final Logger LOGGER = Logger.getLogger(CaptchaManager.class.getName()); + + private static final Map JAIL_COUNT = new ConcurrentHashMap<>(); + private static final Map RETRIES = new ConcurrentHashMap<>(); + private static final Map LAST_JAIL_TIME = new ConcurrentHashMap<>(); + private static final int MAX_JAIL_TIME = 24 * 60; // Maximum (double) jail time in minutes (24 hours). + + protected static Map IMAGES; + protected static Map MONSTER_COUNTER = new ConcurrentHashMap<>(); + protected static Map LAST_KILL_TIME = new ConcurrentHashMap<>(); + protected static Map> BEGIN_VALIDATION = new ConcurrentHashMap<>(); + protected static Map VALIDATION = new ConcurrentHashMap<>(); + protected static int WINDOW_DELAY = 3; // Delay used to generate new window if previous have been closed. + private static int USED_ID = 0; + + public class PlayerData + { + public PlayerData() + { + firstWindow = true; + } + + public boolean firstWindow; + public BufferedImage image; + public String captchaText = ""; + public int captchaId = 0; + } + + protected CaptchaManager() + { + if (CaptchaConfig.ENABLE_CAPTCHA) + { + IMAGES = Captcha.getInstance().createImageList(); + LOGGER.info(getClass().getSimpleName() + ": Enabled."); + } + } + + public void updateCounter(Creature player, Creature monster) + { + if (!CaptchaConfig.ENABLE_CAPTCHA) + { + return; + } + + if (!(player instanceof Player) || !(monster instanceof Monster)) + { + return; + } + + // Check if auto-play is enabled and player is auto-playing. + final Player killer = player.asPlayer(); + if (AutoPlayConfig.ENABLE_AUTO_PLAY && killer.isAutoPlaying()) + { + return; // Don't count kills when auto-play is enabled. + } + + if (VALIDATION.get(killer.getObjectId()) != null) + { + return; + } + + if (CaptchaConfig.KILL_COUNTER_RESET) + { + final long currentTime = System.currentTimeMillis(); + final long previousKillTime = LAST_KILL_TIME.getOrDefault(killer.getObjectId(), currentTime); + if ((currentTime - previousKillTime) > CaptchaConfig.KILL_COUNTER_RESET_TIME) + { + MONSTER_COUNTER.put(killer.getObjectId(), 0); + } + + LAST_KILL_TIME.put(killer.getObjectId(), currentTime); + } + + int count = 1; + if (MONSTER_COUNTER.get(killer.getObjectId()) != null) + { + count = MONSTER_COUNTER.get(killer.getObjectId()) + 1; + } + + final int next = Rnd.get(CaptchaConfig.KILL_COUNTER_RANDOMIZATION); + if ((CaptchaConfig.KILL_COUNTER + next) < count) + { + validationTasks(killer); + MONSTER_COUNTER.remove(killer.getObjectId()); + } + else + { + MONSTER_COUNTER.put(killer.getObjectId(), count); + } + } + + public void preValidationWindow(Player player) + { + RETRIES.put(player.getObjectId(), 0); + final NpcHtmlMessage html = new NpcHtmlMessage(1); + final StringBuilder sb = new StringBuilder(); + StringUtil.append(sb, ""); + StringUtil.append(sb, "Bot Protection"); + StringUtil.append(sb, "


        "); + StringUtil.append(sb, "

        If such window appears it means server suspect,that you may using cheating software."); + StringUtil.append(sb, "

        If given answer results are incorrect or no action is madeserver is going to punish character instantly."); + StringUtil.append(sb, "

        "); + StringUtil.append(sb, ""); + html.setHtml(sb.toString()); + player.sendPacket(html); + } + + private static void validationWindow(Player player) + { + final PlayerData data = VALIDATION.get(player.getObjectId()); + final NpcHtmlMessage html = new NpcHtmlMessage(1); + final StringBuilder sb = new StringBuilder(); + StringUtil.append(sb, ""); + StringUtil.append(sb, "Bot Protection"); + StringUtil.append(sb, "


        "); + StringUtil.append(sb, "

        in order to prove you are a human beingyou've to enter the code from picture:"); + + // Generated main pattern. + StringUtil.append(sb, "

        "); + StringUtil.append(sb, "
        "); + StringUtil.append(sb, "

        "); + StringUtil.append(sb, "
        "); + StringUtil.append(sb, ""); + + html.setHtml(sb.toString()); + player.sendPacket(html); + QuestSound soundToPlay = QuestSound.SKILLSOUND_HORROR_1; + player.sendPacket(soundToPlay.getPacket()); + } + + public void punishmentWindow(Player player) + { + final NpcHtmlMessage html = new NpcHtmlMessage(1); + final StringBuilder sb = new StringBuilder(); + StringUtil.append(sb, ""); + StringUtil.append(sb, "Bot Protection"); + StringUtil.append(sb, "


        "); + StringUtil.append(sb, "

        If such window appears, it means character haven'tpassed through prevention system."); + StringUtil.append(sb, "

        In such case character get moved to nearest town."); + StringUtil.append(sb, "
        "); + StringUtil.append(sb, ""); + html.setHtml(sb.toString()); + player.sendPacket(html); + } + + public void validationTasks(Player player) + { + final PlayerData data = new PlayerData(); + Captcha.getInstance().generateCaptcha(data, player); + data.image = IMAGES.get(USED_ID).image; + data.captchaId = IMAGES.get(USED_ID).captchaID; + data.captchaText = IMAGES.get(USED_ID).captchaText; + final PledgeCrest packet = new PledgeCrest(data.captchaId, DDSConverter.convertToDDS(data.image).array()); + player.sendPacket(packet); + USED_ID++; + + if (CaptchaManager.USED_ID == 998) + { + CaptchaManager.USED_ID = 0; + ThreadPool.schedule(() -> IMAGES = Captcha.getInstance().createImageList(), 100); + } + + VALIDATION.put(player.getObjectId(), data); + + final Future newTask = ThreadPool.schedule(new ReportCheckTask(player), CaptchaConfig.VALIDATION_TIME * 1000); + ThreadPool.schedule(new countdown(player, CaptchaConfig.VALIDATION_TIME), 0); + BEGIN_VALIDATION.put(player.getObjectId(), newTask); + } + + protected void banPunishment(Player player) + { + VALIDATION.remove(player.getObjectId()); + BEGIN_VALIDATION.get(player.getObjectId()).cancel(true); + BEGIN_VALIDATION.remove(player.getObjectId()); + + // 0 = move character to the closest village. + // 1 = kick characters from the server. + // 2 = put character to jail. + // 3 = ban character from the server. + switch (CaptchaConfig.PUNISHMENT) + { + case 0: + { + player.stopMove(null); + player.teleToLocation(TeleportWhereType.TOWN); + punishmentWindow(player); + QuestSound soundForTeleport = QuestSound.ITEMSOUND_QUEST_GIVEUP; + player.sendPacket(soundForTeleport.getPacket()); + AdminData.getInstance().broadcastToGMs(new CreatureSay(null, ChatType.ALLIANCE, "Punishment", player.getName() + " has been teleported.")); + PacketLogger.warning(player + "[TELEPORT] Wrong Captcha maybe boting?"); + break; + } + case 1: + { + if (player.isOnline()) + { + Disconnection.of(player).storeAndDeleteWith(LeaveWorld.STATIC_PACKET); + AdminData.getInstance().broadcastToGMs(new CreatureSay(null, ChatType.ALLIANCE, "Punishment", player.getName() + " has been kicked.")); + PacketLogger.warning(player + "[KICK] Wrong Captcha maybe boting?"); + return; + } + break; + } + case 2: + { + applyJailPunishment(player); + QuestSound soundForJail = QuestSound.ETCSOUND_ELROKI_SONG_FULL; + player.sendPacket(soundForJail.getPacket()); + AdminData.getInstance().broadcastToGMs(new CreatureSay(null, ChatType.ALLIANCE, "Punishment", player.getName() + " has been jailed.")); + PacketLogger.warning(player + "[JAIL] Wrong Captcha maybe boting?"); + break; + } + case 3: + { + changeAccessLevel(player); + AdminData.getInstance().broadcastToGMs(new CreatureSay(null, ChatType.ALLIANCE, "Punishment", player.getName() + " has been banned.")); + PacketLogger.warning(player + "[BAN] Wrong Captcha maybe boting?"); + break; + } + } + + player.sendMessage("Unfortunately, code doesn't match."); + } + + private int getPlayerJailCount(Player player) + { + return JAIL_COUNT.getOrDefault(player.getObjectId(), 0); + } + + private void incrementPlayerJailCount(Player player) + { + JAIL_COUNT.put(player.getObjectId(), getPlayerJailCount(player) + 1); + } + + private void applyJailPunishment(Player player) + { + if (player.isOnline()) + { + final long currentTime = System.currentTimeMillis(); + int jailTime; + + if (CaptchaConfig.DOUBLE_JAIL_TIME) + { + final long lastJailTime = LAST_JAIL_TIME.getOrDefault(player.getObjectId(), 0L); + final int previousJailCount = getPlayerJailCount(player); + + // Use multiplier if enabled. + if ((currentTime - lastJailTime) >= TimeUnit.HOURS.toMillis(24)) + { + jailTime = CaptchaConfig.JAIL_TIME; // Initial jail time from CaptchaConfig. + } + else + { + // Double the previous jail time, capped at maximum jail time. + jailTime = Math.min((int) MathUtil.pow(2, previousJailCount) * CaptchaConfig.JAIL_TIME, MAX_JAIL_TIME); + } + + // Increment the jail count for the player. + incrementPlayerJailCount(player); + + // Send a generic message to the player about the increased punishment next time with a delay. + ThreadPool.schedule(() -> player.sendMessage("Warning: Your next jail time will be increased."), 2000); + } + else + { + // If multiplier is disabled, use the initial jail time from CaptchaConfig. + jailTime = CaptchaConfig.JAIL_TIME; + } + + // Calculate punishment time. + final long punishmentTime; + if (jailTime == 0) + { + punishmentTime = 0; + } + else + { + punishmentTime = currentTime + TimeUnit.MINUTES.toMillis(jailTime); + } + + // Create a PunishmentTask instance. + final PunishmentTask punishmentTask = new PunishmentTask(player.getObjectId(), PunishmentAffect.CHARACTER, PunishmentType.JAIL, punishmentTime, "Bot Protection", "System"); + PunishmentManager.getInstance().startPunishment(punishmentTask); + + // Update the last jail time for the player if applicable. + if (jailTime != 0) + { + LAST_JAIL_TIME.put(player.getObjectId(), currentTime); + if (CaptchaConfig.DOUBLE_JAIL_TIME) + { + LAST_JAIL_TIME.put(player.getObjectId(), currentTime); + } + } + + // Message to inform the player about the punishment. + // player.sendMessage("You've been jailed for not completing bot protection"); + } + } + + private static void changeAccessLevel(Player player) + { + if (player.isOnline()) + { + final PunishmentTask punishmentTask = new PunishmentTask(player.getObjectId(), PunishmentAffect.CHARACTER, PunishmentType.BAN, 0, "Bot Protection", "System"); + PunishmentManager.getInstance().startPunishment(punishmentTask); + } + } + + public void analyseBypass(String command, Player player) + { + if (!VALIDATION.containsKey(player.getObjectId())) + { + return; + } + + final String params = command.substring(command.indexOf("_") + 1); + if (params.startsWith("continue")) + { + validationWindow(player); + VALIDATION.get(player.getObjectId()).firstWindow = false; + return; + } + + final PlayerData data = VALIDATION.get(player.getObjectId()); + if (!params.trim().equalsIgnoreCase(data.captchaText)) + { + int retries = RETRIES.getOrDefault(player.getObjectId(), 0) + 1; + RETRIES.put(player.getObjectId(), retries); + + if (retries >= CaptchaConfig.CAPTCHA_ATTEMPTS) + { + banPunishment(player); + } + else + { + player.sendMessage("Incorrect code. You have " + (CaptchaConfig.CAPTCHA_ATTEMPTS - retries) + " more attempt(s) left."); + validationWindow(player); // Provide another chance. + + // Play sound when doesn't match. + player.sendPacket(QuestSound.ITEMSOUND_BROKEN_KEY.getPacket()); + } + } + else + { + player.sendMessage("Congratulations, code matches!"); + VALIDATION.remove(player.getObjectId()); + BEGIN_VALIDATION.get(player.getObjectId()).cancel(true); + BEGIN_VALIDATION.remove(player.getObjectId()); + + // Play sound when code matches. + player.sendPacket(QuestSound.ITEMSOUND_SOW_SUCCESS.getPacket()); + } + } + + protected class countdown implements Runnable + { + private final Player _player; + private final int _time; + + public countdown(Player player, int time) + { + _time = time; + _player = player; + } + + @Override + public void run() + { + if (_player.isOnline()) + { + if (VALIDATION.containsKey(_player.getObjectId()) && VALIDATION.get(_player.getObjectId()).firstWindow) + { + if ((_time % WINDOW_DELAY) == 0) + { + preValidationWindow(_player); + } + } + + switch (_time) + { + case 300: + case 240: + case 180: + case 120: + case 60: + { + _player.sendMessage((_time / 60) + " minute(s) to enter the code."); + break; + } + case 30: + case 10: + case 5: + case 4: + case 3: + case 2: + case 1: + { + _player.sendMessage(_time + " second(s) to enter the code!"); + break; + } + } + + if ((_time > 1) && VALIDATION.containsKey(_player.getObjectId())) + { + ThreadPool.schedule(new countdown(_player, _time - 1), 1000); + } + } + } + } + + public void captchaSuccessfull(Player player) + { + if (VALIDATION.get(player.getObjectId()) != null) + { + VALIDATION.remove(player.getObjectId()); + } + } + + public boolean isAlredyInReportMode(Player player) + { + return VALIDATION.get(player.getObjectId()) != null; + } + + private class ReportCheckTask implements Runnable + { + private final Player _player; + + public ReportCheckTask(Player player) + { + _player = player; + } + + @Override + public void run() + { + if (VALIDATION.get(_player.getObjectId()) != null) + { + banPunishment(_player); + } + } + } + + public static CaptchaManager getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final CaptchaManager INSTANCE = new CaptchaManager(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/CastleManager.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/CastleManager.java new file mode 100644 index 0000000..76ed398 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/CastleManager.java @@ -0,0 +1,374 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.managers; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.Statement; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.commons.database.DatabaseFactory; +import org.l2jmobius.gameserver.entity.WorldObject; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.clan.Clan; +import org.l2jmobius.gameserver.entity.clan.ClanMember; +import org.l2jmobius.gameserver.entity.item.enums.ItemProcessType; +import org.l2jmobius.gameserver.entity.item.instance.Item; +import org.l2jmobius.gameserver.mechanics.sevensigns.SevenSigns; +import org.l2jmobius.gameserver.mechanics.siege.Castle; + +public class CastleManager +{ + private static final Logger LOGGER = Logger.getLogger(CastleManager.class.getName()); + + private static final List _castles = new CopyOnWriteArrayList<>(); + + private static final Map _castleSiegeDate = new ConcurrentHashMap<>(); + + private static final int[] _castleCirclets = + { + 0, + 6838, + 6835, + 6839, + 6837, + 6840, + 6834, + 6836, + 8182, + 8183 + }; + + public int findNearestCastleIndex(WorldObject obj) + { + return findNearestCastleIndex(obj, Long.MAX_VALUE); + } + + public int findNearestCastleIndex(WorldObject obj, long maxDistanceValue) + { + int index = getCastleIndex(obj); + if (index < 0) + { + Castle castle; + double distance; + long maxDistance = maxDistanceValue; + for (int i = 0; i < _castles.size(); i++) + { + castle = _castles.get(i); + if (castle == null) + { + continue; + } + + distance = castle.getDistance(obj); + if (maxDistance > distance) + { + maxDistance = (long) distance; + index = i; + } + } + } + + return index; + } + + public Castle getCastleById(int castleId) + { + for (Castle temp : _castles) + { + if (temp.getResidenceId() == castleId) + { + return temp; + } + } + + return null; + } + + public Castle getCastleByOwner(Clan clan) + { + if (clan == null) + { + return null; + } + + for (Castle temp : _castles) + { + if (temp.getOwnerId() == clan.getId()) + { + return temp; + } + } + + return null; + } + + public Castle getCastle(String name) + { + for (Castle temp : _castles) + { + if (temp.getName().equalsIgnoreCase(name.trim())) + { + return temp; + } + } + + return null; + } + + public Castle getCastle(int x, int y, int z) + { + for (Castle temp : _castles) + { + if (temp.checkIfInZone(x, y, z)) + { + return temp; + } + } + + return null; + } + + public Castle getCastle(WorldObject activeObject) + { + return getCastle(activeObject.getX(), activeObject.getY(), activeObject.getZ()); + } + + public int getCastleIndex(int castleId) + { + Castle castle; + for (int i = 0; i < _castles.size(); i++) + { + castle = _castles.get(i); + if ((castle != null) && (castle.getResidenceId() == castleId)) + { + return i; + } + } + + return -1; + } + + public int getCastleIndex(WorldObject activeObject) + { + return getCastleIndex(activeObject.getX(), activeObject.getY(), activeObject.getZ()); + } + + public int getCastleIndex(int x, int y, int z) + { + Castle castle; + for (int i = 0; i < _castles.size(); i++) + { + castle = _castles.get(i); + if ((castle != null) && castle.checkIfInZone(x, y, z)) + { + return i; + } + } + + return -1; + } + + public List getCastles() + { + return _castles; + } + + public boolean hasOwnedCastle() + { + boolean hasOwnedCastle = false; + for (Castle castle : _castles) + { + if (castle.getOwnerId() > 0) + { + hasOwnedCastle = true; + break; + } + } + + return hasOwnedCastle; + } + + public void validateTaxes(int sealStrifeOwner) + { + int maxTax; + switch (sealStrifeOwner) + { + case SevenSigns.CABAL_DUSK: + { + maxTax = 5; + break; + } + case SevenSigns.CABAL_DAWN: + { + maxTax = 25; + break; + } + default: // no owner + { + maxTax = 15; + break; + } + } + + for (Castle castle : _castles) + { + if (castle.getTaxPercent() > maxTax) + { + castle.setTaxPercent(maxTax); + } + } + } + + public int getCirclet() + { + return getCircletByCastleId(1); + } + + public int getCircletByCastleId(int castleId) + { + if ((castleId > 0) && (castleId < 10)) + { + return _castleCirclets[castleId]; + } + + return 0; + } + + // Remove this castle's circlets from the clan. + public void removeCirclet(Clan clan, int castleId) + { + for (ClanMember member : clan.getMembers()) + { + removeCirclet(member, castleId); + } + } + + public void removeCirclet(ClanMember member, int castleId) + { + if (member == null) + { + return; + } + + final Player player = member.getPlayer(); + final int circletId = getCircletByCastleId(castleId); + if (circletId != 0) + { + // Online-player circlet removal. + if (player != null) + { + try + { + final Item circlet = player.getInventory().getItemByItemId(circletId); + if (circlet != null) + { + if (circlet.isEquipped()) + { + player.getInventory().unEquipItemInSlot(circlet.getLocationSlot()); + } + + player.destroyItemByItemId(ItemProcessType.DESTROY, circletId, 1, player, true); + } + return; + } + catch (NullPointerException e) + { + // Continue removing offline. + } + } + // Else offline-player circlet removal. + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement("DELETE FROM items WHERE owner_id = ? and item_id = ?")) + { + ps.setInt(1, member.getObjectId()); + ps.setInt(2, circletId); + ps.execute(); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Failed to remove castle circlets offline for player " + member.getName() + ": " + e.getMessage(), e); + } + } + } + + public void loadInstances() + { + try (Connection con = DatabaseFactory.getConnection(); + Statement s = con.createStatement(); + ResultSet rs = s.executeQuery("SELECT id FROM castle ORDER BY id")) + { + while (rs.next()) + { + _castles.add(new Castle(rs.getInt("id"))); + } + + LOGGER.info(getClass().getSimpleName() + ": Loaded " + _castles.size() + " castles"); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Exception: loadCastleData(): " + e.getMessage(), e); + } + } + + public void activateInstances() + { + for (Castle castle : _castles) + { + castle.activateInstance(); + } + } + + public void registerSiegeDate(int castleId, long siegeDate) + { + _castleSiegeDate.put(castleId, siegeDate); + } + + public int getSiegeDates(long siegeDate) + { + int count = 0; + for (long date : _castleSiegeDate.values()) + { + if (Math.abs(date - siegeDate) < 1000) + { + count++; + } + } + + return count; + } + + public static CastleManager getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final CastleManager INSTANCE = new CastleManager(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/CastleManorManager.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/CastleManorManager.java new file mode 100644 index 0000000..156e3e9 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/CastleManorManager.java @@ -0,0 +1,936 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.managers; + +import java.io.File; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.time.Instant; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.w3c.dom.Document; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; + +import org.l2jmobius.commons.database.DatabaseFactory; +import org.l2jmobius.commons.threads.ThreadPool; +import org.l2jmobius.commons.util.IXmlReader; +import org.l2jmobius.commons.util.Rnd; +import org.l2jmobius.gameserver.config.GeneralConfig; +import org.l2jmobius.gameserver.entity.clan.Clan; +import org.l2jmobius.gameserver.entity.clan.ClanMember; +import org.l2jmobius.gameserver.entity.item.enums.ItemProcessType; +import org.l2jmobius.gameserver.entity.itemcontainer.ItemContainer; +import org.l2jmobius.gameserver.mechanics.siege.Castle; +import org.l2jmobius.gameserver.mechanics.siege.manor.CropProcure; +import org.l2jmobius.gameserver.mechanics.siege.manor.ManorMode; +import org.l2jmobius.gameserver.mechanics.siege.manor.Seed; +import org.l2jmobius.gameserver.mechanics.siege.manor.SeedProduction; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.util.StatSet; + +/** + * Castle manor system. + * @author malyelfik, Stayway + */ +public class CastleManorManager implements IXmlReader +{ + private static final Logger LOGGER = Logger.getLogger(CastleManorManager.class.getName()); + + private static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("dd/MM HH:mm:ss"); + + // SQL queries + private static final String INSERT_PRODUCT = "INSERT INTO castle_manor_production VALUES (?, ?, ?, ?, ?, ?)"; + private static final String INSERT_CROP = "INSERT INTO castle_manor_procure VALUES (?, ?, ?, ?, ?, ?, ?)"; + + // Current manor status + private ManorMode _mode = ManorMode.APPROVED; + + // Temporary date + private Calendar _nextModeChange = null; + + // Seeds holder + private static final Map _seeds = new HashMap<>(); + + // Manor period settings + private final Map> _procure = new HashMap<>(); + private final Map> _procureNext = new HashMap<>(); + private final Map> _production = new HashMap<>(); + private final Map> _productionNext = new HashMap<>(); + + public CastleManorManager() + { + if (GeneralConfig.ALLOW_MANOR) + { + // Load seed data from XML and castle manor data from the database. + load(); + loadDb(); + + // Set the initial manor mode based on the current time. + final Calendar currentTime = Calendar.getInstance(); + final int hour = currentTime.get(Calendar.HOUR_OF_DAY); + final int min = currentTime.get(Calendar.MINUTE); + final int maintenanceMin = GeneralConfig.ALT_MANOR_REFRESH_MIN + GeneralConfig.ALT_MANOR_MAINTENANCE_MIN; + if (((hour >= GeneralConfig.ALT_MANOR_REFRESH_TIME) && (min >= maintenanceMin)) || (hour < GeneralConfig.ALT_MANOR_APPROVE_TIME) || ((hour == GeneralConfig.ALT_MANOR_APPROVE_TIME) && (min <= GeneralConfig.ALT_MANOR_APPROVE_MIN))) + { + _mode = ManorMode.MODIFIABLE; + } + else if ((hour == GeneralConfig.ALT_MANOR_REFRESH_TIME) && (min >= GeneralConfig.ALT_MANOR_REFRESH_MIN) && (min < maintenanceMin)) + { + _mode = ManorMode.MAINTENANCE; + } + else + { + _mode = ManorMode.APPROVED; + } + + // Schedule the mode change task. + scheduleModeChange(); + + // Schedule auto-save if not saving all actions. + if (!GeneralConfig.ALT_MANOR_SAVE_ALL_ACTIONS) + { + ThreadPool.scheduleAtFixedRate(this::storeMe, GeneralConfig.ALT_MANOR_SAVE_PERIOD_RATE * 60 * 60 * 1000, GeneralConfig.ALT_MANOR_SAVE_PERIOD_RATE * 60 * 60 * 1000); + } + } + else + { + _mode = ManorMode.DISABLED; + _nextModeChange = null; + LOGGER.info(getClass().getSimpleName() + ": Manor system is deactivated."); + } + } + + @Override + public void load() + { + parseDatapackFile("data/Seeds.xml"); + LOGGER.info(getClass().getSimpleName() + ": Loaded " + _seeds.size() + " seeds."); + } + + @Override + public void parseDocument(Document document, File file) + { + StatSet set; + NamedNodeMap attrs; + Node att; + for (Node n = document.getFirstChild(); n != null; n = n.getNextSibling()) + { + if ("list".equalsIgnoreCase(n.getNodeName())) + { + for (Node d = n.getFirstChild(); d != null; d = d.getNextSibling()) + { + if ("castle".equalsIgnoreCase(d.getNodeName())) + { + final int castleId = parseInteger(d.getAttributes(), "id"); + for (Node c = d.getFirstChild(); c != null; c = c.getNextSibling()) + { + if ("crop".equalsIgnoreCase(c.getNodeName())) + { + set = new StatSet(); + set.set("castleId", castleId); + attrs = c.getAttributes(); + for (int i = 0; i < attrs.getLength(); i++) + { + att = attrs.item(i); + set.set(att.getNodeName(), att.getNodeValue()); + } + + _seeds.put(set.getInt("seedId"), new Seed(set)); + } + } + } + } + } + } + } + + private void loadDb() + { + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement stProduction = con.prepareStatement("SELECT * FROM castle_manor_production WHERE castle_id=?"); + PreparedStatement stProcure = con.prepareStatement("SELECT * FROM castle_manor_procure WHERE castle_id=?")) + { + for (Castle castle : CastleManager.getInstance().getCastles()) + { + final int castleId = castle.getResidenceId(); + + // Clear parameters for reuse. + stProduction.clearParameters(); + stProcure.clearParameters(); + + // Load seed production data for the castle. + final List currentProduction = new ArrayList<>(); + final List nextProduction = new ArrayList<>(); + stProduction.setInt(1, castleId); + + try (ResultSet rs = stProduction.executeQuery()) + { + while (rs.next()) + { + final int seedId = rs.getInt("seed_id"); + if (_seeds.containsKey(seedId)) + { + final SeedProduction seedProduction = new SeedProduction(seedId, rs.getInt("amount"), rs.getInt("price"), rs.getInt("start_amount")); + + // Separate current and next period productions. + if (rs.getBoolean("next_period")) + { + nextProduction.add(seedProduction); + } + else + { + currentProduction.add(seedProduction); + } + } + else + { + LOGGER.warning(getClass().getSimpleName() + ": Unknown seed ID: " + seedId); + } + } + } + + _production.put(castleId, currentProduction); + _productionNext.put(castleId, nextProduction); + + // Load crop procure data for the castle. + final List currentProcure = new ArrayList<>(); + final List nextProcure = new ArrayList<>(); + stProcure.setInt(1, castleId); + + try (ResultSet rs = stProcure.executeQuery()) + { + final Set knownCropIds = getCropIds(); + while (rs.next()) + { + final int cropId = rs.getInt("crop_id"); + if (knownCropIds.contains(cropId)) + { + final CropProcure cropProcure = new CropProcure(cropId, rs.getInt("amount"), rs.getInt("reward_type"), rs.getInt("start_amount"), rs.getInt("price")); + + // Separate current and next period procurements. + if (rs.getBoolean("next_period")) + { + nextProcure.add(cropProcure); + } + else + { + currentProcure.add(cropProcure); + } + } + else + { + LOGGER.warning(getClass().getSimpleName() + ": Unknown crop ID: " + cropId); + } + } + } + + _procure.put(castleId, currentProcure); + _procureNext.put(castleId, nextProcure); + } + + LOGGER.info(getClass().getSimpleName() + ": Manor data loaded."); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Unable to load manor data!", e); + } + } + + private void scheduleModeChange() + { + // Initialize next mode change time to the current date with seconds reset to zero. + _nextModeChange = Calendar.getInstance(); + _nextModeChange.set(Calendar.SECOND, 0); + + // Determine mode-specific time for the next change. + switch (_mode) + { + case MODIFIABLE: + { + _nextModeChange.set(Calendar.HOUR_OF_DAY, GeneralConfig.ALT_MANOR_APPROVE_TIME); + _nextModeChange.set(Calendar.MINUTE, GeneralConfig.ALT_MANOR_APPROVE_MIN); + + // If the time has already passed today, schedule for tomorrow. + if (_nextModeChange.before(Calendar.getInstance())) + { + _nextModeChange.add(Calendar.DATE, 1); + } + break; + } + case MAINTENANCE: + { + _nextModeChange.set(Calendar.HOUR_OF_DAY, GeneralConfig.ALT_MANOR_REFRESH_TIME); + _nextModeChange.set(Calendar.MINUTE, GeneralConfig.ALT_MANOR_REFRESH_MIN + GeneralConfig.ALT_MANOR_MAINTENANCE_MIN); + + // If the time has already passed today, schedule for tomorrow. + if (_nextModeChange.before(Calendar.getInstance())) + { + _nextModeChange.add(Calendar.DATE, 1); + } + break; + } + case APPROVED: + { + _nextModeChange.set(Calendar.HOUR_OF_DAY, GeneralConfig.ALT_MANOR_REFRESH_TIME); + _nextModeChange.set(Calendar.MINUTE, GeneralConfig.ALT_MANOR_REFRESH_MIN); + + // If the time has already passed today, schedule for tomorrow. + if (_nextModeChange.before(Calendar.getInstance())) + { + _nextModeChange.add(Calendar.DATE, 1); + } + break; + } + case DISABLED: + { + // If mode is DISABLED, don't schedule. + return; + } + default: + { + // For any unexpected mode, don't schedule. + return; + } + } + + // Schedule mode change. + long delay = _nextModeChange.getTimeInMillis() - System.currentTimeMillis(); + + // Ensure delay is not negative (minimum 1 second). + if (delay < 1000) + { + delay = 1000; // Schedule for at least 1 second from now. + _nextModeChange.setTimeInMillis(System.currentTimeMillis() + delay); + } + + ThreadPool.schedule(this::changeMode, delay); + } + + public void changeMode() + { + switch (_mode) + { + case APPROVED: + { + // Transition to maintenance mode. + _mode = ManorMode.MAINTENANCE; + + // Update manor period for each castle. + for (Castle castle : CastleManager.getInstance().getCastles()) + { + final Clan owner = castle.getOwner(); + if (owner == null) + { + continue; + } + + final int castleId = castle.getResidenceId(); + final ItemContainer cwh = owner.getWarehouse(); + + // Process crop procurement and treasury updates. + for (CropProcure crop : _procure.get(castleId)) + { + if (crop.getStartAmount() > 0) + { + // Adding bought crops to clan warehouse. + if (crop.getStartAmount() != crop.getAmount()) + { + int harvestedAmount = (int) ((crop.getStartAmount() - crop.getAmount()) * 0.9); + if ((harvestedAmount < 1) && (Rnd.get(99) < 90)) + { + harvestedAmount = 1; + } + + // Add harvested crops to the clan warehouse. + if (harvestedAmount > 0) + { + cwh.addItem(ItemProcessType.REWARD, getSeedByCrop(crop.getId()).getMatureId(), harvestedAmount, null, null); + } + } + + // Add unspent amount to castle treasury. + if (crop.getAmount() > 0) + { + castle.addToTreasuryNoTax(crop.getAmount() * crop.getPrice()); + } + } + } + + // Update current production and procurements to the next period's data. + _production.put(castleId, _productionNext.get(castleId)); + _procure.put(castleId, _procureNext.get(castleId)); + + // Prepare data for the upcoming period, depending on treasury funds. + if (castle.getTreasury() < getManorCost(castleId, false)) + { + _productionNext.put(castleId, Collections.emptyList()); + _procureNext.put(castleId, Collections.emptyList()); + } + else + { + // Reset production and procurement amounts for the next period. + final List productionList = new ArrayList<>(_productionNext.get(castleId)); + for (SeedProduction seed : productionList) + { + seed.setAmount(seed.getStartAmount()); + } + + _productionNext.put(castleId, productionList); + + final List procureList = new ArrayList<>(_procureNext.get(castleId)); + for (CropProcure crop : procureList) + { + crop.setAmount(crop.getStartAmount()); + } + + _procureNext.put(castleId, procureList); + } + } + + // Save the updated manor data. + storeMe(); + break; + } + case MAINTENANCE: + { + // Notify clan leaders of the manor mode change. + for (Castle castle : CastleManager.getInstance().getCastles()) + { + final Clan owner = castle.getOwner(); + if (owner != null) + { + final ClanMember clanLeader = owner.getLeader(); + if ((clanLeader != null) && clanLeader.isOnline()) + { + clanLeader.getPlayer().sendPacket(SystemMessageId.THE_MANOR_INFORMATION_HAS_BEEN_UPDATED); + } + } + } + + // Transition to modifiable mode. + _mode = ManorMode.MODIFIABLE; + break; + } + case MODIFIABLE: + { + // Transition to approved mode. + _mode = ManorMode.APPROVED; + + // Validate each castle's funds and warehouse capacity. + for (Castle castle : CastleManager.getInstance().getCastles()) + { + final Clan owner = castle.getOwner(); + if (owner == null) + { + continue; + } + + final int castleId = castle.getResidenceId(); + final ItemContainer cwh = owner.getWarehouse(); + + // Calculate the necessary slots in the warehouse for new crops. + int requiredSlots = 0; + for (CropProcure crop : _procureNext.get(castleId)) + { + if ((crop.getStartAmount() > 0) && (cwh.getAllItemsByItemId(getSeedByCrop(crop.getId()).getMatureId()) == null)) + { + requiredSlots++; + } + } + + final long manorCost = getManorCost(castleId, true); + + // Check if there's enough capacity and funds. + if (!cwh.validateCapacity(requiredSlots) && (castle.getTreasury() < manorCost)) + { + // Clear next period data if insufficient resources. + _productionNext.get(castleId).clear(); + _procureNext.get(castleId).clear(); + + // Notify clan leader. + final ClanMember clanLeader = owner.getLeader(); + if ((clanLeader != null) && clanLeader.isOnline()) + { + clanLeader.getPlayer().sendPacket(SystemMessageId.THE_AMOUNT_IS_NOT_SUFFICIENT_AND_SO_THE_MANOR_IS_NOT_IN_OPERATION); + } + } + else // Deduct manor cost from treasury. + { + castle.addToTreasuryNoTax(-manorCost); + } + } + + // Store changes if configured to save all actions. + if (GeneralConfig.ALT_MANOR_SAVE_ALL_ACTIONS) + { + storeMe(); + } + break; + } + case DISABLED: + { + // Do nothing if disabled. + return; + } + } + + // Schedule the next mode change. + scheduleModeChange(); + } + + public void setNextSeedProduction(List list, int castleId) + { + _productionNext.put(castleId, list); + + // Save actions to the database if configured to do so. + if (GeneralConfig.ALT_MANOR_SAVE_ALL_ACTIONS) + { + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement deleteStmt = con.prepareStatement("DELETE FROM castle_manor_production WHERE castle_id = ? AND next_period = 1"); + PreparedStatement insertStmt = con.prepareStatement(INSERT_PRODUCT)) + { + // Delete existing production data for the next period. + deleteStmt.setInt(1, castleId); + deleteStmt.executeUpdate(); + + // Insert new production data if list is not empty. + for (SeedProduction sp : list) + { + insertStmt.setInt(1, castleId); + insertStmt.setInt(2, sp.getId()); + insertStmt.setLong(3, sp.getAmount()); + insertStmt.setLong(4, sp.getStartAmount()); + insertStmt.setLong(5, sp.getPrice()); + insertStmt.setBoolean(6, true); + insertStmt.addBatch(); + } + + // Execute batch insert for new production data. + if (!list.isEmpty()) + { + insertStmt.executeBatch(); + } + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, getClass().getSimpleName() + ": Unable to store manor data!", e); + } + } + } + + public void setNextCropProcure(List list, int castleId) + { + _procureNext.put(castleId, list); + + // Save actions to the database if configured to do so. + if (GeneralConfig.ALT_MANOR_SAVE_ALL_ACTIONS) + { + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement deleteStmt = con.prepareStatement("DELETE FROM castle_manor_procure WHERE castle_id = ? AND next_period = 1"); + PreparedStatement insertStmt = con.prepareStatement(INSERT_CROP)) + { + // Delete existing procure data for the next period. + deleteStmt.setInt(1, castleId); + deleteStmt.executeUpdate(); + + // Insert new procure data if list is not empty. + for (CropProcure cp : list) + { + insertStmt.setInt(1, castleId); + insertStmt.setInt(2, cp.getId()); + insertStmt.setLong(3, cp.getAmount()); + insertStmt.setLong(4, cp.getStartAmount()); + insertStmt.setLong(5, cp.getPrice()); + insertStmt.setInt(6, cp.getReward()); + insertStmt.setBoolean(7, true); + insertStmt.addBatch(); + } + + // Execute batch insert for new procure data. + if (!list.isEmpty()) + { + insertStmt.executeBatch(); + } + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, getClass().getSimpleName() + ": Unable to store manor data!", e); + } + } + } + + public void updateCurrentProduction(int castleId, Collection items) + { + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement("UPDATE castle_manor_production SET amount = ? WHERE castle_id = ? AND seed_id = ? AND next_period = 0")) + { + for (SeedProduction sp : items) + { + ps.setLong(1, sp.getAmount()); + ps.setInt(2, castleId); + ps.setInt(3, sp.getId()); + ps.addBatch(); + } + + ps.executeBatch(); + } + catch (Exception e) + { + LOGGER.log(Level.INFO, getClass().getSimpleName() + ": Unable to update current production data!", e); + } + } + + public void updateCurrentProcure(int castleId, Collection items) + { + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement("UPDATE castle_manor_procure SET amount = ? WHERE castle_id = ? AND crop_id = ? AND next_period = 0")) + { + for (CropProcure cp : items) + { + ps.setLong(1, cp.getAmount()); + ps.setInt(2, castleId); + ps.setInt(3, cp.getId()); + ps.addBatch(); + } + + ps.executeBatch(); + } + catch (Exception e) + { + LOGGER.log(Level.INFO, getClass().getSimpleName() + ": Unable to update current procure data!", e); + } + } + + public List getSeedProduction(int castleId, boolean nextPeriod) + { + return nextPeriod ? _productionNext.get(castleId) : _production.get(castleId); + } + + public SeedProduction getSeedProduct(int castleId, int seedId, boolean nextPeriod) + { + for (SeedProduction sp : getSeedProduction(castleId, nextPeriod)) + { + if (sp.getId() == seedId) + { + return sp; + } + } + + return null; + } + + public List getCropProcure(int castleId, boolean nextPeriod) + { + return nextPeriod ? _procureNext.get(castleId) : _procure.get(castleId); + } + + public CropProcure getCropProcure(int castleId, int cropId, boolean nextPeriod) + { + for (CropProcure cp : getCropProcure(castleId, nextPeriod)) + { + if (cp.getId() == cropId) + { + return cp; + } + } + + return null; + } + + public long getManorCost(int castleId, boolean nextPeriod) + { + final List procureList = getCropProcure(castleId, nextPeriod); + final List productionList = getSeedProduction(castleId, nextPeriod); + long totalCost = 0; + + for (SeedProduction seed : productionList) + { + Seed s = getSeed(seed.getId()); + totalCost += (s != null) ? (s.getSeedReferencePrice() * seed.getStartAmount()) : 1; + } + + for (CropProcure crop : procureList) + { + totalCost += crop.getPrice() * crop.getStartAmount(); + } + + return totalCost; + } + + public boolean storeMe() + { + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement deleteProductionStmt = con.prepareStatement("DELETE FROM castle_manor_production"); + PreparedStatement insertProductionStmt = con.prepareStatement(INSERT_PRODUCT); + PreparedStatement deleteProcureStmt = con.prepareStatement("DELETE FROM castle_manor_procure"); + PreparedStatement insertProcureStmt = con.prepareStatement(INSERT_CROP)) + { + // Delete old production data. + deleteProductionStmt.executeUpdate(); + + // Insert current production data. + for (Entry> entry : _production.entrySet()) + { + for (SeedProduction sp : entry.getValue()) + { + insertProductionStmt.setInt(1, entry.getKey()); + insertProductionStmt.setInt(2, sp.getId()); + insertProductionStmt.setLong(3, sp.getAmount()); + insertProductionStmt.setLong(4, sp.getStartAmount()); + insertProductionStmt.setLong(5, sp.getPrice()); + insertProductionStmt.setBoolean(6, false); + insertProductionStmt.addBatch(); + } + } + + // Insert next period production data. + for (Entry> entry : _productionNext.entrySet()) + { + for (SeedProduction sp : entry.getValue()) + { + insertProductionStmt.setInt(1, entry.getKey()); + insertProductionStmt.setInt(2, sp.getId()); + insertProductionStmt.setLong(3, sp.getAmount()); + insertProductionStmt.setLong(4, sp.getStartAmount()); + insertProductionStmt.setLong(5, sp.getPrice()); + insertProductionStmt.setBoolean(6, true); + insertProductionStmt.addBatch(); + } + } + + // Execute batch insert for production data. + insertProductionStmt.executeBatch(); + + // Delete old procure data. + deleteProcureStmt.executeUpdate(); + + // Insert current procure data. + for (Entry> entry : _procure.entrySet()) + { + for (CropProcure cp : entry.getValue()) + { + insertProcureStmt.setInt(1, entry.getKey()); + insertProcureStmt.setInt(2, cp.getId()); + insertProcureStmt.setLong(3, cp.getAmount()); + insertProcureStmt.setLong(4, cp.getStartAmount()); + insertProcureStmt.setLong(5, cp.getPrice()); + insertProcureStmt.setInt(6, cp.getReward()); + insertProcureStmt.setBoolean(7, false); + insertProcureStmt.addBatch(); + } + } + + // Insert next period procure data. + for (Entry> entry : _procureNext.entrySet()) + { + for (CropProcure cp : entry.getValue()) + { + insertProcureStmt.setInt(1, entry.getKey()); + insertProcureStmt.setInt(2, cp.getId()); + insertProcureStmt.setLong(3, cp.getAmount()); + insertProcureStmt.setLong(4, cp.getStartAmount()); + insertProcureStmt.setLong(5, cp.getPrice()); + insertProcureStmt.setInt(6, cp.getReward()); + insertProcureStmt.setBoolean(7, true); + insertProcureStmt.addBatch(); + } + } + + // Execute batch insert for procure data. + insertProcureStmt.executeBatch(); + + return true; + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, getClass().getSimpleName() + ": Unable to store manor data!", e); + return false; + } + } + + public void resetManorData(int castleId) + { + if (!GeneralConfig.ALLOW_MANOR) + { + return; + } + + // Clear existing production and procurement data for the specified castle. + _procure.get(castleId).clear(); + _procureNext.get(castleId).clear(); + _production.get(castleId).clear(); + _productionNext.get(castleId).clear(); + + // Save changes to the database if configured to do so. + if (GeneralConfig.ALT_MANOR_SAVE_ALL_ACTIONS) + { + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement deleteProductionStmt = con.prepareStatement("DELETE FROM castle_manor_production WHERE castle_id = ?"); + PreparedStatement deleteProcureStmt = con.prepareStatement("DELETE FROM castle_manor_procure WHERE castle_id = ?")) + { + // Delete seed production data. + deleteProductionStmt.setInt(1, castleId); + deleteProductionStmt.executeUpdate(); + + // Delete procurement data. + deleteProcureStmt.setInt(1, castleId); + deleteProcureStmt.executeUpdate(); + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, getClass().getSimpleName() + ": Unable to store manor data!", e); + } + } + } + + public boolean isUnderMaintenance() + { + return _mode == ManorMode.MAINTENANCE; + } + + public boolean isManorApproved() + { + return _mode == ManorMode.APPROVED; + } + + public boolean isModifiablePeriod() + { + return _mode == ManorMode.MODIFIABLE; + } + + public String getCurrentModeName() + { + return _mode.toString(); + } + + public String getNextModeChange() + { + if (_nextModeChange == null) + { + return "Disabled"; + } + + return DATE_FORMAT.format(Instant.ofEpochMilli(_nextModeChange.getTimeInMillis()).atZone(ZoneId.systemDefault())); + } + + public List getCrops() + { + final List seeds = new ArrayList<>(); + final List cropIds = new ArrayList<>(); + for (Seed seed : _seeds.values()) + { + if (!cropIds.contains(seed.getCropId())) + { + seeds.add(seed); + cropIds.add(seed.getCropId()); + } + } + + cropIds.clear(); + return seeds; + } + + public Set getSeedsForCastle(int castleId) + { + Set result = new HashSet<>(); + for (Seed seed : _seeds.values()) + { + if (seed.getCastleId() == castleId) + { + result.add(seed); + } + } + + return result; + } + + public Set getSeedIds() + { + return _seeds.keySet(); + } + + public Set getCropIds() + { + final Set result = new HashSet<>(); + for (Seed seed : _seeds.values()) + { + result.add(seed.getCropId()); + } + + return result; + } + + public Seed getSeed(int seedId) + { + return _seeds.get(seedId); + } + + public Seed getSeedByCrop(int cropId, int castleId) + { + for (Seed seed : getSeedsForCastle(castleId)) + { + if (seed.getCropId() == cropId) + { + return seed; + } + } + + return null; + } + + public Seed getSeedByCrop(int cropId) + { + for (Seed seed : _seeds.values()) + { + if (seed.getCropId() == cropId) + { + return seed; + } + } + + return null; + } + + public static CastleManorManager getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final CastleManorManager INSTANCE = new CastleManorManager(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/ClanHallAuctionManager.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/ClanHallAuctionManager.java new file mode 100644 index 0000000..dda8cb8 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/ClanHallAuctionManager.java @@ -0,0 +1,193 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.managers; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.commons.database.DatabaseFactory; +import org.l2jmobius.gameserver.entity.residences.ClanHallAuction; + +public class ClanHallAuctionManager +{ + protected static final Logger LOGGER = Logger.getLogger(ClanHallAuctionManager.class.getName()); + private final List _auctions = new ArrayList<>(); + + private static final String[] ITEM_INIT_DATA = + { + "(22, 0, 'NPC', 'NPC Clan', 'ClanHall', 22, 0, 'Moonstone Hall', 1, 20000000, 0, 1073037600000)", + "(23, 0, 'NPC', 'NPC Clan', 'ClanHall', 23, 0, 'Onyx Hall', 1, 20000000, 0, 1073037600000)", + "(24, 0, 'NPC', 'NPC Clan', 'ClanHall', 24, 0, 'Topaz Hall', 1, 20000000, 0, 1073037600000)", + "(25, 0, 'NPC', 'NPC Clan', 'ClanHall', 25, 0, 'Ruby Hall', 1, 20000000, 0, 1073037600000)", + "(26, 0, 'NPC', 'NPC Clan', 'ClanHall', 26, 0, 'Crystal Hall', 1, 20000000, 0, 1073037600000)", + "(27, 0, 'NPC', 'NPC Clan', 'ClanHall', 27, 0, 'Onyx Hall', 1, 20000000, 0, 1073037600000)", + "(28, 0, 'NPC', 'NPC Clan', 'ClanHall', 28, 0, 'Sapphire Hall', 1, 20000000, 0, 1073037600000)", + "(29, 0, 'NPC', 'NPC Clan', 'ClanHall', 29, 0, 'Moonstone Hall', 1, 20000000, 0, 1073037600000)", + "(30, 0, 'NPC', 'NPC Clan', 'ClanHall', 30, 0, 'Emerald Hall', 1, 20000000, 0, 1073037600000)", + "(31, 0, 'NPC', 'NPC Clan', 'ClanHall', 31, 0, 'The Atramental Barracks', 1, 8000000, 0, 1073037600000)", + "(32, 0, 'NPC', 'NPC Clan', 'ClanHall', 32, 0, 'The Scarlet Barracks', 1, 8000000, 0, 1073037600000)", + "(33, 0, 'NPC', 'NPC Clan', 'ClanHall', 33, 0, 'The Viridian Barracks', 1, 8000000, 0, 1073037600000)", + "(36, 0, 'NPC', 'NPC Clan', 'ClanHall', 36, 0, 'The Golden Chamber', 1, 50000000, 0, 1106827200000)", + "(37, 0, 'NPC', 'NPC Clan', 'ClanHall', 37, 0, 'The Silver Chamber', 1, 50000000, 0, 1106827200000)", + "(38, 0, 'NPC', 'NPC Clan', 'ClanHall', 38, 0, 'The Mithril Chamber', 1, 50000000, 0, 1106827200000)", + "(39, 0, 'NPC', 'NPC Clan', 'ClanHall', 39, 0, 'Silver Manor', 1, 50000000, 0, 1106827200000)", + "(40, 0, 'NPC', 'NPC Clan', 'ClanHall', 40, 0, 'Gold Manor', 1, 50000000, 0, 1106827200000)", + "(41, 0, 'NPC', 'NPC Clan', 'ClanHall', 41, 0, 'The Bronze Chamber', 1, 50000000, 0, 1106827200000)", + "(42, 0, 'NPC', 'NPC Clan', 'ClanHall', 42, 0, 'The Golden Chamber', 1, 50000000, 0, 1106827200000)", + "(43, 0, 'NPC', 'NPC Clan', 'ClanHall', 43, 0, 'The Silver Chamber', 1, 50000000, 0, 1106827200000)", + "(44, 0, 'NPC', 'NPC Clan', 'ClanHall', 44, 0, 'The Mithril Chamber', 1, 50000000, 0, 1106827200000)", + "(45, 0, 'NPC', 'NPC Clan', 'ClanHall', 45, 0, 'The Bronze Chamber', 1, 50000000, 0, 1106827200000)", + "(46, 0, 'NPC', 'NPC Clan', 'ClanHall', 46, 0, 'Silver Manor', 1, 50000000, 0, 1106827200000)", + "(47, 0, 'NPC', 'NPC Clan', 'ClanHall', 47, 0, 'Moonstone Hall', 1, 50000000, 0, 1106827200000)", + "(48, 0, 'NPC', 'NPC Clan', 'ClanHall', 48, 0, 'Onyx Hall', 1, 50000000, 0, 1106827200000)", + "(49, 0, 'NPC', 'NPC Clan', 'ClanHall', 49, 0, 'Emerald Hall', 1, 50000000, 0, 1106827200000)", + "(50, 0, 'NPC', 'NPC Clan', 'ClanHall', 50, 0, 'Sapphire Hall', 1, 50000000, 0, 1106827200000)", + "(51, 0, 'NPC', 'NPC Clan', 'ClanHall', 51, 0, 'Mont Chamber', 1, 50000000, 0, 1106827200000)", + "(52, 0, 'NPC', 'NPC Clan', 'ClanHall', 52, 0, 'Astaire Chamber', 1, 50000000, 0, 1106827200000)", + "(53, 0, 'NPC', 'NPC Clan', 'ClanHall', 53, 0, 'Aria Chamber', 1, 50000000, 0, 1106827200000)", + "(54, 0, 'NPC', 'NPC Clan', 'ClanHall', 54, 0, 'Yiana Chamber', 1, 50000000, 0, 1106827200000)", + "(55, 0, 'NPC', 'NPC Clan', 'ClanHall', 55, 0, 'Roien Chamber', 1, 50000000, 0, 1106827200000)", + "(56, 0, 'NPC', 'NPC Clan', 'ClanHall', 56, 0, 'Luna Chamber', 1, 50000000, 0, 1106827200000)", + "(57, 0, 'NPC', 'NPC Clan', 'ClanHall', 57, 0, 'Traban Chamber', 1, 50000000, 0, 1106827200000)", + "(58, 0, 'NPC', 'NPC Clan', 'ClanHall', 58, 0, 'Eisen Hall', 1, 20000000, 0, 1106827200000)", + "(59, 0, 'NPC', 'NPC Clan', 'ClanHall', 59, 0, 'Heavy Metal Hall', 1, 20000000, 0, 1106827200000)", + "(60, 0, 'NPC', 'NPC Clan', 'ClanHall', 60, 0, 'Molten Ore Hall', 1, 20000000, 0, 1106827200000)", + "(61, 0, 'NPC', 'NPC Clan', 'ClanHall', 61, 0, 'Titan Hall', 1, 20000000, 0, 1106827200000)" + }; + + // @formatter:off + private static final int[] ItemInitDataId = + { + 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 36, 37, 38, 39, 40, 41, 42, + 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61 + }; + // @formatter:on + + protected ClanHallAuctionManager() + { + load(); + } + + public void reload() + { + _auctions.clear(); + load(); + } + + private void load() + { + try (Connection con = DatabaseFactory.getConnection(); + Statement s = con.createStatement(); + ResultSet rs = s.executeQuery("SELECT id FROM auction ORDER BY id")) + { + while (rs.next()) + { + _auctions.add(new ClanHallAuction(rs.getInt("id"))); + } + + LOGGER.info(getClass().getSimpleName() + ": Loaded " + _auctions.size() + " auction(s)"); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Exception: AuctionManager.load(): " + e.getMessage(), e); + } + } + + public ClanHallAuction getAuction(int auctionId) + { + final int index = getAuctionIndex(auctionId); + if (index >= 0) + { + return _auctions.get(index); + } + + return null; + } + + public int getAuctionIndex(int auctionId) + { + ClanHallAuction auction; + for (int i = 0; i < _auctions.size(); i++) + { + auction = _auctions.get(i); + if ((auction != null) && (auction.getId() == auctionId)) + { + return i; + } + } + + return -1; + } + + public List getAuctions() + { + return _auctions; + } + + /** + * Init Clan NPC aution + * @param id + */ + public void initNPC(int id) + { + int i; + for (i = 0; i < ItemInitDataId.length; i++) + { + if (ItemInitDataId[i] == id) + { + break; + } + } + + if ((i >= ItemInitDataId.length) || (ItemInitDataId[i] != id)) + { + LOGGER.warning(getClass().getSimpleName() + ": Clan Hall auction not found for Id :" + id); + return; + } + + try (Connection con = DatabaseFactory.getConnection(); + Statement s = con.createStatement()) + { + s.executeUpdate("INSERT INTO `auction` VALUES " + ITEM_INIT_DATA[i]); + _auctions.add(new ClanHallAuction(id)); + LOGGER.info(getClass().getSimpleName() + ": Created auction for ClanHall: " + id); + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, getClass().getSimpleName() + ": Exception: Auction.initNPC(): " + e.getMessage(), e); + } + } + + public static ClanHallAuctionManager getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final ClanHallAuctionManager INSTANCE = new ClanHallAuctionManager(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/CoupleManager.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/CoupleManager.java new file mode 100644 index 0000000..90354fa --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/CoupleManager.java @@ -0,0 +1,153 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.managers; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.Statement; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.commons.database.DatabaseFactory; +import org.l2jmobius.gameserver.entity.World; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.holders.player.Couple; + +/** + * @author evill33t + */ +public class CoupleManager +{ + private static final Logger LOGGER = Logger.getLogger(CoupleManager.class.getName()); + + private final List _couples = new CopyOnWriteArrayList<>(); + + protected CoupleManager() + { + load(); + } + + public void reload() + { + _couples.clear(); + load(); + } + + private void load() + { + try (Connection con = DatabaseFactory.getConnection(); + Statement ps = con.createStatement(); + ResultSet rs = ps.executeQuery("SELECT id FROM mods_wedding ORDER BY id")) + { + while (rs.next()) + { + _couples.add(new Couple(rs.getInt("id"))); + } + + LOGGER.info(getClass().getSimpleName() + ": Loaded " + _couples.size() + " couples(s)"); + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "Exception: CoupleManager.load(): " + e.getMessage(), e); + } + } + + public Couple getCouple(int coupleId) + { + final int index = getCoupleIndex(coupleId); + return index >= 0 ? _couples.get(index) : null; + } + + public void createCouple(Player player1, Player player2) + { + if ((player1 == null) || (player2 == null) || (player1.getPartnerId() != 0) || (player2.getPartnerId() != 0)) + { + return; + } + + final int player1id = player1.getObjectId(); + final int player2id = player2.getObjectId(); + final Couple couple = new Couple(player1, player2); + _couples.add(couple); + player1.setPartnerId(player2id); + player2.setPartnerId(player1id); + player1.setCoupleId(couple.getId()); + player2.setCoupleId(couple.getId()); + } + + public void deleteCouple(int coupleId) + { + final int index = getCoupleIndex(coupleId); + final Couple couple = _couples.get(index); + if (couple == null) + { + return; + } + + final Player player1 = World.getPlayer(couple.getPlayer1Id()); + final Player player2 = World.getPlayer(couple.getPlayer2Id()); + if (player1 != null) + { + player1.setPartnerId(0); + player1.setMarried(false); + player1.setCoupleId(0); + } + + if (player2 != null) + { + player2.setPartnerId(0); + player2.setMarried(false); + player2.setCoupleId(0); + } + + couple.divorce(); + _couples.remove(index); + } + + public int getCoupleIndex(int coupleId) + { + int i = 0; + for (Couple temp : _couples) + { + if ((temp != null) && (temp.getId() == coupleId)) + { + return i; + } + + i++; + } + + return -1; + } + + public List getCouples() + { + return _couples; + } + + public static CoupleManager getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final CoupleManager INSTANCE = new CoupleManager(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/CursedWeaponsManager.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/CursedWeaponsManager.java new file mode 100644 index 0000000..0017692 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/CursedWeaponsManager.java @@ -0,0 +1,424 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.managers; + +import java.io.File; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.xml.parsers.DocumentBuilderFactory; + +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; + +import org.l2jmobius.commons.database.DatabaseFactory; +import org.l2jmobius.gameserver.config.GeneralConfig; +import org.l2jmobius.gameserver.config.ServerConfig; +import org.l2jmobius.gameserver.entity.World; +import org.l2jmobius.gameserver.entity.actor.Attackable; +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.holders.player.CursedWeapon; +import org.l2jmobius.gameserver.entity.actor.instance.Defender; +import org.l2jmobius.gameserver.entity.actor.instance.FeedableBeast; +import org.l2jmobius.gameserver.entity.actor.instance.FestivalMonster; +import org.l2jmobius.gameserver.entity.actor.instance.GrandBoss; +import org.l2jmobius.gameserver.entity.actor.instance.Guard; +import org.l2jmobius.gameserver.entity.actor.instance.RiftInvader; +import org.l2jmobius.gameserver.entity.item.instance.Item; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; + +/** + * @author Micht + */ +public class CursedWeaponsManager +{ + private static final Logger LOGGER = Logger.getLogger(CursedWeaponsManager.class.getName()); + + private Map _cursedWeapons; + + protected CursedWeaponsManager() + { + init(); + } + + private void init() + { + _cursedWeapons = new HashMap<>(); + if (!GeneralConfig.ALLOW_CURSED_WEAPONS) + { + return; + } + + load(); + restore(); + controlPlayers(); + LOGGER.info(getClass().getSimpleName() + ": Loaded : " + _cursedWeapons.size() + " cursed weapon(s)."); + } + + public void reload() + { + init(); + } + + private void load() + { + try + { + final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setValidating(false); + factory.setIgnoringComments(true); + + final File file = new File(ServerConfig.DATAPACK_ROOT + "/data/CursedWeapons.xml"); + if (!file.exists()) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Couldn't find data/" + file.getName()); + return; + } + + for (Node n = factory.newDocumentBuilder().parse(file).getFirstChild(); n != null; n = n.getNextSibling()) + { + if ("list".equalsIgnoreCase(n.getNodeName())) + { + for (Node d = n.getFirstChild(); d != null; d = d.getNextSibling()) + { + if ("item".equalsIgnoreCase(d.getNodeName())) + { + NamedNodeMap attrs = d.getAttributes(); + final int id = Integer.parseInt(attrs.getNamedItem("id").getNodeValue()); + final int skillId = Integer.parseInt(attrs.getNamedItem("skillId").getNodeValue()); + final String name = attrs.getNamedItem("name").getNodeValue(); + final CursedWeapon cw = new CursedWeapon(id, skillId, name); + int val; + for (Node cd = d.getFirstChild(); cd != null; cd = cd.getNextSibling()) + { + if ("dropRate".equalsIgnoreCase(cd.getNodeName())) + { + attrs = cd.getAttributes(); + val = Integer.parseInt(attrs.getNamedItem("val").getNodeValue()); + cw.setDropRate(val); + } + else if ("duration".equalsIgnoreCase(cd.getNodeName())) + { + attrs = cd.getAttributes(); + val = Integer.parseInt(attrs.getNamedItem("val").getNodeValue()); + cw.setDuration(val); + } + else if ("durationLost".equalsIgnoreCase(cd.getNodeName())) + { + attrs = cd.getAttributes(); + val = Integer.parseInt(attrs.getNamedItem("val").getNodeValue()); + cw.setDurationLost(val); + } + else if ("disapearChance".equalsIgnoreCase(cd.getNodeName())) + { + attrs = cd.getAttributes(); + val = Integer.parseInt(attrs.getNamedItem("val").getNodeValue()); + cw.setDisapearChance(val); + } + else if ("stageKills".equalsIgnoreCase(cd.getNodeName())) + { + attrs = cd.getAttributes(); + val = Integer.parseInt(attrs.getNamedItem("val").getNodeValue()); + cw.setStageKills(val); + } + } + + // Store cursed weapon + _cursedWeapons.put(id, cw); + } + } + } + } + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "Error parsing cursed weapons file.", e); + } + } + + private void restore() + { + try (Connection con = DatabaseFactory.getConnection(); + Statement s = con.createStatement(); + ResultSet rs = s.executeQuery("SELECT itemId, charId, playerKarma, playerPkKills, nbKills, endTime FROM cursed_weapons")) + { + // Retrieve the Player from the characters table of the database. + CursedWeapon cw; + while (rs.next()) + { + cw = _cursedWeapons.get(rs.getInt("itemId")); + cw.setPlayerId(rs.getInt("charId")); + cw.setPlayerKarma(rs.getInt("playerKarma")); + cw.setPlayerPkKills(rs.getInt("playerPkKills")); + cw.setNbKills(rs.getInt("nbKills")); + cw.setEndTime(rs.getLong("endTime")); + cw.reActivate(); + } + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Could not restore CursedWeapons data: " + e.getMessage(), e); + } + } + + private void controlPlayers() + { + // TODO: See comments below... + // This entire for loop should NOT be necessary, since it is already handled by + // CursedWeapon.endOfLife(). However, if we indeed *need* to duplicate it for safety, + // then we'd better make sure that it FULLY cleans up inactive cursed weapons! + // Undesired effects result otherwise, such as player with no zariche but with karma + // or a lost-child entry in the cursed weapons table, without a corresponding one in items... + + // Retrieve the Player from the characters table of the database. + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement("SELECT owner_id FROM items WHERE item_id=?")) + { + for (CursedWeapon cw : _cursedWeapons.values()) + { + if (cw.isActivated()) + { + continue; + } + + // Do an item check to be sure that the cursed weapon isn't hold by someone + final int itemId = cw.getItemId(); + ps.setInt(1, itemId); + try (ResultSet rset = ps.executeQuery()) + { + if (rset.next()) + { + // A player has the cursed weapon in his inventory ... + final int playerId = rset.getInt("owner_id"); + LOGGER.info("PROBLEM : Player " + playerId + " owns the cursed weapon " + itemId + " but he shouldn't."); + + // Delete the item + try (PreparedStatement delete = con.prepareStatement("DELETE FROM items WHERE owner_id=? AND item_id=?")) + { + delete.setInt(1, playerId); + delete.setInt(2, itemId); + if (delete.executeUpdate() != 1) + { + LOGGER.warning("Error while deleting cursed weapon " + itemId + " from userId " + playerId); + } + } + + // Restore the player's old karma and pk count + try (PreparedStatement update = con.prepareStatement("UPDATE characters SET karma=?, pkkills=? WHERE charId=?")) + { + update.setInt(1, cw.getPlayerKarma()); + update.setInt(2, cw.getPlayerPkKills()); + update.setInt(3, playerId); + if (update.executeUpdate() != 1) + { + LOGGER.warning("Error while updating karma & pkkills for userId " + cw.getPlayerId()); + } + } + + // clean up the cursed weapons table. + removeFromDb(itemId); + } + } + + ps.clearParameters(); + } + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Could not check CursedWeapons data: " + e.getMessage(), e); + } + } + + public synchronized void checkDrop(Attackable attackable, Player player) + { + if ((attackable instanceof Defender) || (attackable instanceof RiftInvader) || (attackable instanceof FestivalMonster) || (attackable instanceof Guard) || (attackable instanceof GrandBoss) || (attackable instanceof FeedableBeast)) + { + return; + } + + for (CursedWeapon cw : _cursedWeapons.values()) + { + if (cw.isActive()) + { + continue; + } + + if (cw.checkDrop(attackable, player)) + { + break; + } + } + } + + public void activate(Player player, Item item) + { + final CursedWeapon cw = _cursedWeapons.get(item.getId()); + if (player.isCursedWeaponEquipped()) // cannot own 2 cursed swords + { + final CursedWeapon cw2 = _cursedWeapons.get(player.getCursedWeaponEquippedId()); + + // TODO: give the bonus level in a more appropriate manner. + // The following code adds "_stageKills" levels. This will also show in the char status. + // I do not have enough info to know if the bonus should be shown in the pk count, or if it + // should be a full "_stageKills" bonus or just the remaining from the current count till the of the current stage... + // This code is a TEMP fix, so that the cursed weapon's bonus level can be observed with as little change in the code as possible, until proper info arises. + cw2.setNbKills(cw2.getStageKills() - 1); + cw2.increaseKills(); + + // erase the newly obtained cursed weapon + cw.setPlayer(player); // NECESSARY in order to find which inventory the weapon is in! + cw.endOfLife(); // expire the weapon and clean up. + } + else + { + cw.activate(player, item); + } + } + + public void drop(int itemId, Creature killer) + { + _cursedWeapons.get(itemId).dropIt(killer); + } + + public void increaseKills(int itemId) + { + _cursedWeapons.get(itemId).increaseKills(); + } + + public int getLevel(int itemId) + { + return _cursedWeapons.get(itemId).getLevel(); + } + + public static void announce(SystemMessage sm) + { + World.broadcastToAllOnlinePlayers(sm); + } + + public void checkPlayer(Player player) + { + if (player == null) + { + return; + } + + for (CursedWeapon cw : _cursedWeapons.values()) + { + if (cw.isActivated() && (player.getObjectId() == cw.getPlayerId())) + { + cw.setPlayer(player); + cw.setItem(player.getInventory().getItemByItemId(cw.getItemId())); + cw.giveSkill(); + player.setCursedWeaponEquippedId(cw.getItemId()); + + final SystemMessage sm = new SystemMessage(SystemMessageId.S1_HAS_S2_MINUTE_S_OF_USAGE_TIME_REMAINING); + sm.addString(cw.getName()); + // sm.addItemName(cw.getItemId()); + sm.addInt((int) ((cw.getEndTime() - System.currentTimeMillis()) / 60000)); + player.sendPacket(sm); + } + } + } + + public int checkOwnsWeaponId(int ownerId) + { + for (CursedWeapon cw : _cursedWeapons.values()) + { + if (cw.isActivated() && (ownerId == cw.getPlayerId())) + { + return cw.getItemId(); + } + } + + return -1; + } + + public static void removeFromDb(int itemId) + { + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement("DELETE FROM cursed_weapons WHERE itemId = ?")) + { + ps.setInt(1, itemId); + ps.executeUpdate(); + } + catch (SQLException e) + { + LOGGER.log(Level.SEVERE, "CursedWeaponsManager: Failed to remove data: " + e.getMessage(), e); + } + } + + public void saveData() + { + for (CursedWeapon cw : _cursedWeapons.values()) + { + cw.saveData(); + } + } + + public boolean isCursed(int itemId) + { + return _cursedWeapons.containsKey(itemId); + } + + public Collection getCursedWeapons() + { + return _cursedWeapons.values(); + } + + public Set getCursedWeaponsIds() + { + return _cursedWeapons.keySet(); + } + + public CursedWeapon getCursedWeapon(int itemId) + { + return _cursedWeapons.get(itemId); + } + + public void givePassive(int itemId) + { + try + { + _cursedWeapons.get(itemId).giveSkill(); + } + catch (Exception e) + { + /***/ + } + } + + public static CursedWeaponsManager getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final CursedWeaponsManager INSTANCE = new CursedWeaponsManager(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/CustomMailManager.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/CustomMailManager.java new file mode 100644 index 0000000..adb3e17 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/CustomMailManager.java @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.managers; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.commons.database.DatabaseFactory; +import org.l2jmobius.commons.threads.ThreadPool; +import org.l2jmobius.commons.util.StringUtil; +import org.l2jmobius.gameserver.config.custom.CustomMailManagerConfig; +import org.l2jmobius.gameserver.entity.World; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.item.enums.ItemProcessType; +import org.l2jmobius.gameserver.entity.item.holders.ItemEnchantHolder; +import org.l2jmobius.gameserver.network.enums.ChatType; +import org.l2jmobius.gameserver.network.serverpackets.CreatureSay; + +/** + * @author Mobius + */ +public class CustomMailManager +{ + private static final Logger LOGGER = Logger.getLogger(CustomMailManager.class.getName()); + + // SQL Statements + private static final String READ_SQL = "SELECT * FROM custom_mail"; + private static final String DELETE_SQL = "DELETE FROM custom_mail WHERE date=? AND receiver=?"; + + protected CustomMailManager() + { + ThreadPool.scheduleAtFixedRate(() -> + { + try (Connection con = DatabaseFactory.getConnection(); + Statement ps = con.createStatement(); + ResultSet rs = ps.executeQuery(READ_SQL)) + { + while (rs.next()) + { + final int playerId = rs.getInt("receiver"); + final Player player = World.getPlayer(playerId); + if ((player != null) && player.isOnline()) + { + // Create message. + final String items = rs.getString("items"); + player.sendPacket(new CreatureSay(null, ChatType.WHISPER, rs.getString("subject"), rs.getString("message"))); + final List itemHolders = new ArrayList<>(); + for (String str : items.split(";")) + { + if (str.contains(" ")) + { + final String[] split = str.split(" "); + final String itemId = split[0]; + final String itemCount = split[1]; + final String enchant = split.length > 2 ? split[2] : "0"; + if (StringUtil.isNumeric(itemId) && StringUtil.isNumeric(itemCount)) + { + itemHolders.add(new ItemEnchantHolder(Integer.parseInt(itemId), Integer.parseInt(itemCount), Integer.parseInt(enchant))); + } + } + else if (StringUtil.isNumeric(str)) + { + itemHolders.add(new ItemEnchantHolder(Integer.parseInt(str), 1)); + } + } + + if (!itemHolders.isEmpty()) + { + for (ItemEnchantHolder itemHolder : itemHolders) + { + player.addItem(ItemProcessType.TRANSFER, itemHolder.getId(), itemHolder.getCount(), itemHolder.getEnchantLevel(), null, true); + } + } + + // Delete entry from database. + try (PreparedStatement stmt = con.prepareStatement(DELETE_SQL)) + { + stmt.setString(1, rs.getString("date")); + stmt.setInt(2, playerId); + stmt.execute(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Error deleting entry from database: ", e); + } + + LOGGER.info(getClass().getSimpleName() + ": Message sent to " + player.getName() + "."); + } + } + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Error reading from database: ", e); + } + }, CustomMailManagerConfig.CUSTOM_MAIL_MANAGER_DELAY, CustomMailManagerConfig.CUSTOM_MAIL_MANAGER_DELAY); + + LOGGER.info(getClass().getSimpleName() + ": Enabled."); + } + + public static CustomMailManager getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final CustomMailManager INSTANCE = new CustomMailManager(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/DailyResetManager.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/DailyResetManager.java new file mode 100644 index 0000000..3fa81d7 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/DailyResetManager.java @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.managers; + +import java.util.Calendar; +import java.util.logging.Logger; + +import org.l2jmobius.commons.threads.ThreadPool; +import org.l2jmobius.commons.time.TimeUtil; +import org.l2jmobius.gameserver.data.sql.ClanTable; +import org.l2jmobius.gameserver.entity.World; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.clan.Clan; +import org.l2jmobius.gameserver.entity.clan.ClanMember; +import org.l2jmobius.gameserver.mechanics.events.EventDispatcher; +import org.l2jmobius.gameserver.mechanics.events.EventType; +import org.l2jmobius.gameserver.mechanics.events.holders.OnDailyReset; +import org.l2jmobius.gameserver.mechanics.olympiad.Olympiad; +import org.l2jmobius.gameserver.mechanics.sevensigns.SevenSigns; +import org.l2jmobius.gameserver.mechanics.sevensigns.SevenSignsFestival; + +/** + * @author Mobius + */ +public class DailyResetManager +{ + private static final Logger LOGGER = Logger.getLogger(DailyResetManager.class.getName()); + + protected DailyResetManager() + { + // Schedule reset everyday at 6:30. + final long nextResetTime = TimeUtil.getNextTime(6, 30).getTimeInMillis(); + final long currentTime = System.currentTimeMillis(); + + // Get today's 6:30 AM timestamp. + final Calendar calendar = Calendar.getInstance(); + calendar.set(Calendar.HOUR_OF_DAY, 6); + calendar.set(Calendar.MINUTE, 30); + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MILLISECOND, 0); + final long currentResetTime = calendar.getTimeInMillis(); + + // Check if 24 hours have passed since the last daily reset. + if ((currentTime < currentResetTime) || (GlobalVariablesManager.getInstance().getLong(GlobalVariablesManager.DAILY_TASK_RESET, 0) > currentResetTime)) + { + LOGGER.info(getClass().getSimpleName() + ": Next schedule at " + TimeUtil.getDateTimeString(nextResetTime) + "."); + } + else + { + LOGGER.info(getClass().getSimpleName() + ": Daily task will run now."); + onReset(); + } + + // Daily reset task. + final long startDelay = Math.max(0, nextResetTime - currentTime); + ThreadPool.scheduleAtFixedRate(this::onReset, startDelay, 86400000); // 86400000 = 1 day + + // Global save task. + ThreadPool.scheduleAtFixedRate(this::onSave, 1800000, 1800000); // 1800000 = 30 minutes + + // Recommend reset task (13:00). + ThreadPool.scheduleAtFixedRate(this::resetRecommends, TimeUtil.getNextTime(13, 0).getTimeInMillis() - currentTime, 86400000); // 86400000 = 1 day + } + + private void onReset() + { + LOGGER.info("Starting reset of daily tasks..."); + + // Store last reset time. + GlobalVariablesManager.getInstance().set(GlobalVariablesManager.DAILY_TASK_RESET, System.currentTimeMillis()); + + // Wednesday weekly tasks. + final Calendar calendar = Calendar.getInstance(); + if (calendar.get(Calendar.DAY_OF_WEEK) == Calendar.WEDNESDAY) + { + clanLeaderApply(); + } + + // Trigger daily reset event. + if (EventDispatcher.getInstance().hasListener(EventType.ON_DAILY_RESET)) + { + EventDispatcher.getInstance().notifyEvent(new OnDailyReset()); + } + + // Store player variables. + for (Player player : World.getPlayers()) + { + player.getVariables().storeMe(); + player.getAccountVariables().storeMe(); + } + + LOGGER.info("Daily tasks reset completed."); + } + + private void onSave() + { + GlobalVariablesManager.getInstance().storeMe(); + + if (Olympiad.getInstance().inCompPeriod()) + { + Olympiad.getInstance().saveOlympiadStatus(); + LOGGER.info("Olympiad System: Data updated."); + } + + SevenSigns.getInstance().saveSevenSignsStatus(); + if (!SevenSigns.getInstance().isSealValidationPeriod()) + { + SevenSignsFestival.getInstance().saveFestivalData(false); + } + LOGGER.info("SevenSigns: Data updated."); + } + + private void clanLeaderApply() + { + for (Clan clan : ClanTable.getInstance().getClans()) + { + if (clan.getNewLeaderId() != 0) + { + final ClanMember member = clan.getClanMember(clan.getNewLeaderId()); + if (member == null) + { + continue; + } + + clan.setNewLeader(member); + } + } + + LOGGER.info("Clan leaders have been updated."); + } + + private void resetRecommends() + { + for (Player player : World.getPlayers()) + { + player.restartRecom(); + player.updateUserInfo(); + } + } + + public static DailyResetManager getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final DailyResetManager INSTANCE = new DailyResetManager(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/DatabaseIdManager.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/DatabaseIdManager.java new file mode 100644 index 0000000..a883f86 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/DatabaseIdManager.java @@ -0,0 +1,293 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.managers; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.logging.Logger; + +import org.l2jmobius.commons.database.DatabaseFactory; +import org.l2jmobius.gameserver.config.IdManagerConfig; +import org.l2jmobius.gameserver.config.custom.WeddingConfig; + +/** + * Manages database cleanup operations and retrieval of used IDs for ID allocation. + * @author Mobius + */ +public class DatabaseIdManager +{ + private static final Logger LOGGER = Logger.getLogger(DatabaseIdManager.class.getName()); + + private static final String[] CLEANUP_QUERIES = + { + "DELETE FROM account_gsdata WHERE account_gsdata.account_name NOT IN (SELECT account_name FROM characters)", + "DELETE FROM character_contacts WHERE character_contacts.charId NOT IN (SELECT charId FROM characters)", + "DELETE FROM character_contacts WHERE character_contacts.contactId NOT IN (SELECT charId FROM characters)", + "DELETE FROM character_friends WHERE character_friends.charId NOT IN (SELECT charId FROM characters)", + "DELETE FROM character_friends WHERE character_friends.friendId NOT IN (SELECT charId FROM characters)", + "DELETE FROM character_hennas WHERE character_hennas.charId NOT IN (SELECT charId FROM characters)", + "DELETE FROM character_macroses WHERE character_macroses.charId NOT IN (SELECT charId FROM characters)", + "DELETE FROM character_quests WHERE character_quests.charId NOT IN (SELECT charId FROM characters)", + "DELETE FROM character_recipebook WHERE character_recipebook.charId NOT IN (SELECT charId FROM characters)", + "DELETE FROM character_recipeshoplist WHERE character_recipeshoplist.charId NOT IN (SELECT charId FROM characters)", + "DELETE FROM character_shortcuts WHERE character_shortcuts.charId NOT IN (SELECT charId FROM characters)", + "DELETE FROM character_skills WHERE character_skills.charId NOT IN (SELECT charId FROM characters)", + "DELETE FROM character_skills_save WHERE character_skills_save.charId NOT IN (SELECT charId FROM characters)", + "DELETE FROM character_subclasses WHERE character_subclasses.charId NOT IN (SELECT charId FROM characters)", + "DELETE FROM character_raid_points WHERE character_raid_points.charId NOT IN (SELECT charId FROM characters)", + "DELETE FROM character_instance_time WHERE character_instance_time.charId NOT IN (SELECT charId FROM characters)", + "DELETE FROM items WHERE items.owner_id NOT IN (SELECT charId FROM characters) AND items.owner_id NOT IN (SELECT clan_id FROM clan_data) AND items.owner_id != -1", + "DELETE FROM items WHERE items.owner_id = -1 AND loc LIKE 'MAIL' AND loc_data NOT IN (SELECT messageId FROM messages WHERE senderId = -1)", + "DELETE FROM item_auction_bid WHERE item_auction_bid.playerObjId NOT IN (SELECT charId FROM characters)", + "DELETE FROM item_attributes WHERE item_attributes.itemId NOT IN (SELECT object_id FROM items)", + "DELETE FROM cursed_weapons WHERE cursed_weapons.charId NOT IN (SELECT charId FROM characters)", + "DELETE FROM heroes WHERE heroes.charId NOT IN (SELECT charId FROM characters)", + "DELETE FROM olympiad_nobles WHERE olympiad_nobles.charId NOT IN (SELECT charId FROM characters)", + "DELETE FROM olympiad_nobles_eom WHERE olympiad_nobles_eom.charId NOT IN (SELECT charId FROM characters)", + "DELETE FROM pets WHERE pets.item_obj_id NOT IN (SELECT object_id FROM items)", + "DELETE FROM seven_signs WHERE seven_signs.charId NOT IN (SELECT charId FROM characters)", + "DELETE FROM merchant_lease WHERE merchant_lease.player_id NOT IN (SELECT charId FROM characters)", + "DELETE FROM character_recommends WHERE character_recommends.charId NOT IN (SELECT charId FROM characters)", + "DELETE FROM character_recommends WHERE character_recommends.target_id NOT IN (SELECT charId FROM characters)", + "DELETE FROM clan_data WHERE clan_data.leader_id NOT IN (SELECT charId FROM characters)", + "DELETE FROM clan_data WHERE clan_data.clan_id NOT IN (SELECT clanid FROM characters)", + "DELETE FROM olympiad_fights WHERE olympiad_fights.charOneId NOT IN (SELECT charId FROM characters)", + "DELETE FROM olympiad_fights WHERE olympiad_fights.charTwoId NOT IN (SELECT charId FROM characters)", + "DELETE FROM heroes_diary WHERE heroes_diary.charId NOT IN (SELECT charId FROM characters)", + "DELETE FROM character_offline_trade WHERE character_offline_trade.charId NOT IN (SELECT charId FROM characters)", + "DELETE FROM character_offline_trade_items WHERE character_offline_trade_items.charId NOT IN (SELECT charId FROM characters)", + "DELETE FROM character_offline_play WHERE character_offline_play.charId NOT IN (SELECT charId FROM characters)", + "DELETE FROM character_offline_play_group WHERE character_offline_play_group.charId NOT IN (SELECT charId FROM characters)", + "DELETE FROM character_offline_play_group WHERE character_offline_play_group.leaderId NOT IN (SELECT charId FROM characters)", + "DELETE FROM character_variables WHERE character_variables.charId NOT IN (SELECT charId FROM characters)", + "DELETE FROM clan_privs WHERE clan_privs.clan_id NOT IN (SELECT clan_id FROM clan_data)", + "DELETE FROM clan_skills WHERE clan_skills.clan_id NOT IN (SELECT clan_id FROM clan_data)", + "DELETE FROM clan_subpledges WHERE clan_subpledges.clan_id NOT IN (SELECT clan_id FROM clan_data)", + "DELETE FROM clan_wars WHERE clan_wars.clan1 NOT IN (SELECT clan_id FROM clan_data)", + "DELETE FROM clan_wars WHERE clan_wars.clan2 NOT IN (SELECT clan_id FROM clan_data)", + "DELETE FROM clanhall_functions WHERE clanhall_functions.hall_id NOT IN (SELECT id FROM clanhall WHERE ownerId <> 0 union all SELECT clanHallId FROM siegable_clanhall WHERE ownerId <> 0)", + "DELETE FROM siege_clans WHERE siege_clans.clan_id NOT IN (SELECT clan_id FROM clan_data)", + "DELETE FROM clan_notices WHERE clan_notices.clan_id NOT IN (SELECT clan_id FROM clan_data)", + "DELETE FROM auction_bid WHERE auction_bid.bidderId NOT IN (SELECT clan_id FROM clan_data)", + "DELETE FROM forums WHERE forums.forum_owner_id NOT IN (SELECT clan_id FROM clan_data) AND forums.forum_parent=2", + "DELETE FROM forums WHERE forums.forum_owner_id NOT IN (SELECT charId FROM characters) AND forums.forum_parent=3", + "DELETE FROM posts WHERE posts.post_forum_id NOT IN (SELECT forum_id FROM forums)", + "DELETE FROM topic WHERE topic.topic_forum_id NOT IN (SELECT forum_id FROM forums)" + }; + private static final String[] UPDATE_QUERIES = + { + "UPDATE clan_data SET auction_bid_at = 0 WHERE auction_bid_at NOT IN (SELECT auctionId FROM auction_bid)", + "UPDATE clan_data SET new_leader_id = 0 WHERE new_leader_id <> 0 AND new_leader_id NOT IN (SELECT charId FROM characters)", + "UPDATE clan_subpledges SET leader_id=0 WHERE clan_subpledges.leader_id NOT IN (SELECT charId FROM characters) AND leader_id > 0", + "UPDATE castle SET taxpercent=0 WHERE castle.id NOT IN (SELECT hasCastle FROM clan_data)", + "UPDATE characters SET clanid=0, clan_privs=0, wantspeace=0, subpledge=0, lvl_joined_academy=0, apprentice=0, sponsor=0, clan_join_expiry_time=0, clan_create_expiry_time=0 WHERE characters.clanid > 0 AND characters.clanid NOT IN (SELECT clan_id FROM clan_data)", + "UPDATE clanhall SET ownerId=0, paidUntil=0, paid=0 WHERE clanhall.ownerId NOT IN (SELECT clan_id FROM clan_data)" + }; + private static final String UPDATE_CHARACTER_STATUS_QUERY = "UPDATE characters SET online = 0"; + private static final String[] EXPIRATION_CLEANUP_QUERIES = + { + "DELETE FROM character_instance_time WHERE time <= ?", + "DELETE FROM character_skills_save WHERE restore_type = 1 AND systime <= ?" + }; + private static final String[] EXTRACT_USED_OBJECT_ID_QUERIES = + { + "SELECT charId FROM characters", + "SELECT object_id FROM items", + "SELECT clan_id FROM clan_data", + "SELECT object_id FROM itemsonground", + "SELECT messageId FROM messages" + }; + + private DatabaseIdManager() + { + } + + /** + * Executes database cleanup by removing records that are no longer referenced and updating certain records to a consistent state. This process helps to maintain database integrity and optimize space. + */ + public static void cleanDatabase() + { + if (!IdManagerConfig.DATABASE_CLEAN_UP) + { + return; + } + + int cleanCount = 0; + final long cleanupStart = System.currentTimeMillis(); + if (WeddingConfig.ALLOW_WEDDING) + { + try (Connection con = DatabaseFactory.getConnection(); + Statement statement = con.createStatement()) + { + statement.executeUpdate("DELETE FROM mods_wedding WHERE player1Id NOT IN (SELECT charId FROM characters)"); + statement.executeUpdate("DELETE FROM mods_wedding WHERE player2Id NOT IN (SELECT charId FROM characters)"); + } + catch (Exception e) + { + LOGGER.warning("DatabaseIdManager: Could not clean up invalid weddings: " + e); + } + } + + for (String query : CLEANUP_QUERIES) + { + try (Connection con = DatabaseFactory.getConnection(); + Statement statement = con.createStatement()) + { + cleanCount += statement.executeUpdate(query); + } + catch (Exception e) + { + LOGGER.warning("DatabaseIdManager: Could not execute cleanup query: " + query + " - " + e); + } + } + + LOGGER.info("DatabaseIdManager: Cleaned " + cleanCount + " elements in " + ((System.currentTimeMillis() - cleanupStart) / 1000) + " seconds."); + + for (String query : UPDATE_QUERIES) + { + try (Connection con = DatabaseFactory.getConnection(); + Statement statement = con.createStatement()) + { + statement.executeUpdate(query); + } + catch (Exception e) + { + LOGGER.warning("DatabaseIdManager: Could not execute update query: " + query + " - " + e); + } + } + } + + /** + * Updates the online status of all characters to 0, ensuring that characters are marked as offline on server restart or maintenance. + */ + public static void cleanCharacterStatus() + { + try (Connection con = DatabaseFactory.getConnection(); + Statement statement = con.createStatement()) + { + statement.executeUpdate(UPDATE_CHARACTER_STATUS_QUERY); + LOGGER.info("DatabaseIdManager: Updated character online status."); + } + catch (Exception e) + { + LOGGER.warning("DatabaseIdManager: Could not update characters online status: " + e); + } + } + + /** + * Removes expired entries from tables with expiration timestamps. Any records with timestamps older than the current time are deleted, maintaining an up-to-date state in tables with temporary data. + */ + public static void cleanTimestamps() + { + try (Connection con = DatabaseFactory.getConnection()) + { + int cleanCount = 0; + for (String query : EXPIRATION_CLEANUP_QUERIES) + { + try (PreparedStatement statement = con.prepareStatement(query)) + { + statement.setLong(1, System.currentTimeMillis()); + cleanCount += statement.executeUpdate(); + } + } + + LOGGER.info("DatabaseIdManager: Cleaned " + cleanCount + " expired timestamps."); + } + catch (Exception e) + { + LOGGER.warning("DatabaseIdManager: Could not clean expired timestamps from database: " + e); + } + } + + /** + * Retrieves a set of used IDs by querying database tables that store object IDs. The retrieved IDs are used to initialize the ID manager's allocation system, ensuring that existing IDs are not reallocated. + * @return a set of IDs that are currently in use across relevant database tables + */ + public static Set getUsedIds() + { + final Set usedIds = ConcurrentHashMap.newKeySet(); + + final ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); + final List> futures = new ArrayList<>(); + for (String query : EXTRACT_USED_OBJECT_ID_QUERIES) + { + futures.add(executor.submit(() -> + { + try (Connection con = DatabaseFactory.getConnection(); + Statement statement = con.createStatement(); + ResultSet result = statement.executeQuery(query)) + { + while (result.next()) + { + final int id = result.getInt(1); + if ((id >= IdManagerConfig.FIRST_OBJECT_ID) && (id <= IdManagerConfig.LAST_OBJECT_ID)) + { + usedIds.add(id); + } + else + { + LOGGER.warning("DatabaseIdManager: ID " + id + " in database is out of valid range (" + IdManagerConfig.FIRST_OBJECT_ID + " - " + IdManagerConfig.LAST_OBJECT_ID + ")"); + } + } + } + catch (Exception e) + { + LOGGER.severe("DatabaseIdManager: Could not initialize used IDs for query " + query + ": " + e.getMessage()); + } + })); + } + + for (Future future : futures) + { + try + { + future.get(); + } + catch (Exception e) + { + LOGGER.warning("Failed to parse file: " + e.getMessage()); + } + } + + executor.shutdown(); + try + { + executor.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS); + } + catch (InterruptedException e) + { + Thread.currentThread().interrupt(); + LOGGER.warning("DatabaseIdManager: Extraction interrupted: " + e.getMessage()); + } + + return usedIds; + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/DayNightSpawnManager.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/DayNightSpawnManager.java new file mode 100644 index 0000000..bf80c58 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/DayNightSpawnManager.java @@ -0,0 +1,189 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.managers; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.gameserver.entity.actor.Npc; +import org.l2jmobius.gameserver.entity.actor.instance.RaidBoss; +import org.l2jmobius.gameserver.entity.spawns.Spawn; +import org.l2jmobius.gameserver.taskmanagers.GameTimeTaskManager; + +/** + * @author godson + */ +public class DayNightSpawnManager +{ + private static final Logger LOGGER = Logger.getLogger(DayNightSpawnManager.class.getName()); + + private final List _dayCreatures = new ArrayList<>(); + private final List _nightCreatures = new ArrayList<>(); + private final Map _bosses = new ConcurrentHashMap<>(); + + protected DayNightSpawnManager() + { + // Prevent external initialization. + } + + public void addDayCreature(Spawn spawnDat) + { + _dayCreatures.add(spawnDat); + } + + public void addNightCreature(Spawn spawnDat) + { + _nightCreatures.add(spawnDat); + } + + /** + * Spawn Day Creatures, and Unspawn Night Creatures + */ + public void spawnDayCreatures() + { + spawnCreatures(_nightCreatures, _dayCreatures, "night", "day"); + } + + /** + * Spawn Night Creatures, and Unspawn Day Creatures + */ + public void spawnNightCreatures() + { + spawnCreatures(_dayCreatures, _nightCreatures, "day", "night"); + } + + /** + * Manage Spawn/Respawn + * @param unSpawnCreatures List with spawns must be unspawned + * @param spawnCreatures List with spawns must be spawned + * @param unspawnLogInfo String for log info for unspawned Npc + * @param spawnLogInfo String for log info for spawned Npc + */ + private void spawnCreatures(List unSpawnCreatures, List spawnCreatures, String unspawnLogInfo, String spawnLogInfo) + { + try + { + if (!unSpawnCreatures.isEmpty()) + { + int i = 0; + for (Spawn spawn : unSpawnCreatures) + { + if (spawn == null) + { + continue; + } + + spawn.stopRespawn(); + final Npc last = spawn.getLastSpawn(); + if (last != null) + { + last.deleteMe(); + i++; + } + } + + LOGGER.info("DayNightSpawnManager: Removed " + i + " " + unspawnLogInfo + " creatures"); + } + + int i = 0; + for (Spawn spawnDat : spawnCreatures) + { + if (spawnDat == null) + { + continue; + } + + spawnDat.startRespawn(); + spawnDat.doSpawn(); + i++; + } + + LOGGER.info("DayNightSpawnManager: Spawned " + i + " " + spawnLogInfo + " creatures"); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Error while spawning creatures: " + e.getMessage(), e); + } + } + + private void changeMode(int mode) + { + if (_nightCreatures.isEmpty() && _dayCreatures.isEmpty() && _bosses.isEmpty()) + { + return; + } + + switch (mode) + { + case 0: + { + spawnDayCreatures(); + break; + } + case 1: + { + spawnNightCreatures(); + break; + } + default: + { + LOGGER.warning("DayNightSpawnManager: Wrong mode sent"); + break; + } + } + } + + public DayNightSpawnManager trim() + { + ((ArrayList) _nightCreatures).trimToSize(); + ((ArrayList) _dayCreatures).trimToSize(); + return this; + } + + public void notifyChangeMode() + { + try + { + changeMode(GameTimeTaskManager.getInstance().isNight() ? 1 : 0); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Error while notifyChangeMode(): " + e.getMessage(), e); + } + } + + public void cleanUp() + { + _nightCreatures.clear(); + _dayCreatures.clear(); + _bosses.clear(); + } + + public static DayNightSpawnManager getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final DayNightSpawnManager INSTANCE = new DayNightSpawnManager(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/DimensionalRiftManager.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/DimensionalRiftManager.java new file mode 100644 index 0000000..05aba6e --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/DimensionalRiftManager.java @@ -0,0 +1,489 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.managers; + +import java.io.File; +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.xml.parsers.DocumentBuilderFactory; + +import org.w3c.dom.Document; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; + +import org.l2jmobius.commons.database.DatabaseFactory; +import org.l2jmobius.commons.util.Rnd; +import org.l2jmobius.gameserver.config.GeneralConfig; +import org.l2jmobius.gameserver.config.ServerConfig; +import org.l2jmobius.gameserver.data.SpawnTable; +import org.l2jmobius.gameserver.entity.actor.Npc; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.groups.Party; +import org.l2jmobius.gameserver.entity.item.enums.ItemProcessType; +import org.l2jmobius.gameserver.entity.item.instance.Item; +import org.l2jmobius.gameserver.entity.spawns.Spawn; +import org.l2jmobius.gameserver.mechanics.sevensigns.DimensionalRift; +import org.l2jmobius.gameserver.mechanics.sevensigns.DimensionalRiftRoom; +import org.l2jmobius.gameserver.network.serverpackets.NpcHtmlMessage; + +/** + * Dimensional Rift manager. + * @author kombat + */ +public class DimensionalRiftManager +{ + private static final Logger LOGGER = Logger.getLogger(DimensionalRiftManager.class.getName()); + private final Map> _rooms = new HashMap<>(7); + private static final int DIMENSIONAL_FRAGMENT_ITEM_ID = 7079; + + public static DimensionalRiftManager getInstance() + { + return SingletonHolder.INSTANCE; + } + + protected DimensionalRiftManager() + { + loadRooms(); + loadSpawns(); + } + + public DimensionalRiftRoom getRoom(byte type, byte room) + { + return _rooms.get(type) == null ? null : _rooms.get(type).get(room); + } + + private void loadRooms() + { + try (Connection con = DatabaseFactory.getConnection(); + Statement s = con.createStatement(); + ResultSet rs = s.executeQuery("SELECT * FROM dimensional_rift")) + { + while (rs.next()) + { + // 0 waiting room, 1 recruit, 2 soldier, 3 officer, 4 captain , 5 commander, 6 hero + final byte type = rs.getByte("type"); + final byte room_id = rs.getByte("room_id"); + + // coords related + final int xMin = rs.getInt("xMin"); + final int xMax = rs.getInt("xMax"); + final int yMin = rs.getInt("yMin"); + final int yMax = rs.getInt("yMax"); + final int z1 = rs.getInt("zMin"); + final int z2 = rs.getInt("zMax"); + final int xT = rs.getInt("xT"); + final int yT = rs.getInt("yT"); + final int zT = rs.getInt("zT"); + final boolean isBossRoom = rs.getByte("boss") > 0; + _rooms.computeIfAbsent(type, k -> new HashMap<>(9)); + + _rooms.get(type).put(room_id, new DimensionalRiftRoom(type, room_id, xMin, xMax, yMin, yMax, z1, z2, xT, yT, zT, isBossRoom)); + } + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Can't load Dimension Rift zones. " + e.getMessage(), e); + } + + final int typeSize = _rooms.keySet().size(); + int roomSize = 0; + for (Map room : _rooms.values()) + { + roomSize += room.keySet().size(); + } + + LOGGER.info(getClass().getSimpleName() + ": Loaded " + typeSize + " room types with " + roomSize + " rooms."); + } + + public void loadSpawns() + { + int countGood = 0; + int countBad = 0; + try + { + final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setValidating(false); + factory.setIgnoringComments(true); + + final File file = new File(ServerConfig.DATAPACK_ROOT, "data/DimensionalRift.xml"); + if (!file.exists()) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Couldn't find data/" + file.getName()); + return; + } + + final Document document = factory.newDocumentBuilder().parse(file); + NamedNodeMap attrs; + byte type; + byte roomId; + int mobId; + int x; + int y; + int z; + int delay; + int count; + for (Node rift = document.getFirstChild(); rift != null; rift = rift.getNextSibling()) + { + if ("rift".equalsIgnoreCase(rift.getNodeName())) + { + for (Node area = rift.getFirstChild(); area != null; area = area.getNextSibling()) + { + if ("area".equalsIgnoreCase(area.getNodeName())) + { + attrs = area.getAttributes(); + type = Byte.parseByte(attrs.getNamedItem("type").getNodeValue()); + for (Node room = area.getFirstChild(); room != null; room = room.getNextSibling()) + { + if ("room".equalsIgnoreCase(room.getNodeName())) + { + attrs = room.getAttributes(); + roomId = Byte.parseByte(attrs.getNamedItem("id").getNodeValue()); + for (Node spawn = room.getFirstChild(); spawn != null; spawn = spawn.getNextSibling()) + { + if ("spawn".equalsIgnoreCase(spawn.getNodeName())) + { + attrs = spawn.getAttributes(); + mobId = Integer.parseInt(attrs.getNamedItem("mobId").getNodeValue()); + delay = Integer.parseInt(attrs.getNamedItem("delay").getNodeValue()); + count = Integer.parseInt(attrs.getNamedItem("count").getNodeValue()); + if (!_rooms.containsKey(type)) + { + LOGGER.warning("Type " + type + " not found!"); + } + else if (!_rooms.get(type).containsKey(roomId)) + { + LOGGER.warning("Room " + roomId + " in Type " + type + " not found!"); + } + + for (int i = 0; i < count; i++) + { + final DimensionalRiftRoom riftRoom = _rooms.get(type).get(roomId); + x = riftRoom.getRandomX(); + y = riftRoom.getRandomY(); + z = riftRoom.getTeleportCoorinates().getZ(); + if (_rooms.containsKey(type) && _rooms.get(type).containsKey(roomId)) + { + final Spawn spawnDat = new Spawn(mobId); + spawnDat.setAmount(1); + spawnDat.setXYZ(x, y, z); + spawnDat.setHeading(-1); + spawnDat.setRespawnDelay(delay); + SpawnTable.getInstance().addSpawn(spawnDat); + _rooms.get(type).get(roomId).getSpawns().add(spawnDat); + countGood++; + } + else + { + countBad++; + } + } + } + } + } + } + } + } + } + } + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Error on loading dimensional rift spawns: " + e.getMessage(), e); + } + + LOGGER.info(getClass().getSimpleName() + ": Loaded " + countGood + " dimensional rift spawns, " + countBad + " errors."); + } + + public void reload() + { + for (Map rooms : _rooms.values()) + { + for (DimensionalRiftRoom room : rooms.values()) + { + room.getSpawns().clear(); + } + + rooms.clear(); + } + + _rooms.clear(); + loadRooms(); + loadSpawns(); + } + + public boolean checkIfInRiftZone(int x, int y, int z, boolean ignorePeaceZone) + { + if (ignorePeaceZone) + { + return _rooms.get((byte) 0).get((byte) 1).checkIfInZone(x, y, z); + } + + return _rooms.get((byte) 0).get((byte) 1).checkIfInZone(x, y, z) && !_rooms.get((byte) 0).get((byte) 0).checkIfInZone(x, y, z); + } + + public boolean checkIfInPeaceZone(int x, int y, int z) + { + return _rooms.get((byte) 0).get((byte) 0).checkIfInZone(x, y, z); + } + + public void teleportToWaitingRoom(Player player) + { + player.teleToLocation(getRoom((byte) 0, (byte) 0).getTeleportCoorinates()); + } + + public synchronized void start(Player player, byte type, Npc npc) + { + boolean canPass = true; + if (!player.isInParty()) + { + showHtmlFile(player, "data/html/seven_signs/rift/NoParty.htm", npc); + return; + } + + final Party party = player.getParty(); + if (party.getLeaderObjectId() != player.getObjectId()) + { + showHtmlFile(player, "data/html/seven_signs/rift/NotPartyLeader.htm", npc); + return; + } + + if (party.isInDimensionalRift()) + { + handleCheat(player, npc); + return; + } + + if (party.getMemberCount() < GeneralConfig.RIFT_MIN_PARTY_SIZE) + { + final NpcHtmlMessage html = new NpcHtmlMessage(npc.getObjectId()); + html.setFile(player, "data/html/seven_signs/rift/SmallParty.htm"); + html.replace("%npc_name%", npc.getName()); + html.replace("%count%", Integer.toString(GeneralConfig.RIFT_MIN_PARTY_SIZE)); + player.sendPacket(html); + return; + } + + // max parties inside is rooms count - 1 + if (!isAllowedEnter(type)) + { + player.sendMessage("Rift is full. Try later."); + return; + } + + for (Player p : party.getMembers()) + { + if (!checkIfInPeaceZone(p.getX(), p.getY(), p.getZ())) + { + canPass = false; + break; + } + } + + if (!canPass) + { + showHtmlFile(player, "data/html/seven_signs/rift/NotInWaitingRoom.htm", npc); + return; + } + + Item i; + final int count = getNeededItems(type); + for (Player p : party.getMembers()) + { + i = p.getInventory().getItemByItemId(DIMENSIONAL_FRAGMENT_ITEM_ID); + if (i == null) + { + canPass = false; + break; + } + + if (i.getCount() > 0) + { + if (i.getCount() < getNeededItems(type)) + { + canPass = false; + break; + } + } + else + { + canPass = false; + break; + } + } + + if (!canPass) + { + final NpcHtmlMessage html = new NpcHtmlMessage(npc.getObjectId()); + html.setFile(player, "data/html/seven_signs/rift/NoFragments.htm"); + html.replace("%npc_name%", npc.getName()); + html.replace("%count%", Integer.toString(count)); + player.sendPacket(html); + return; + } + + for (Player p : party.getMembers()) + { + i = p.getInventory().getItemByItemId(DIMENSIONAL_FRAGMENT_ITEM_ID); + if (!p.destroyItem(ItemProcessType.FEE, i, count, null, false)) + { + final NpcHtmlMessage html = new NpcHtmlMessage(npc.getObjectId()); + html.setFile(player, "data/html/seven_signs/rift/NoFragments.htm"); + html.replace("%npc_name%", npc.getName()); + html.replace("%count%", Integer.toString(count)); + player.sendPacket(html); + return; + } + } + + byte room; + List emptyRooms; + do + { + emptyRooms = getFreeRooms(type); + room = emptyRooms.get(Rnd.get(1, emptyRooms.size()) - 1); + } + + // find empty room + while (_rooms.get(type).get(room).isPartyInside()); + new DimensionalRift(party, type, room); + } + + public void killRift(DimensionalRift d) + { + if (d.getTeleportTimerTask() != null) + { + d.getTeleportTimerTask().cancel(); + } + + d.setTeleportTimerTask(null); + + if (d.getTeleportTimer() != null) + { + d.getTeleportTimer().cancel(); + } + + d.setTeleportTimer(null); + + if (d.getSpawnTimerTask() != null) + { + d.getSpawnTimerTask().cancel(); + } + + d.setSpawnTimerTask(null); + + if (d.getSpawnTimer() != null) + { + d.getSpawnTimer().cancel(); + } + + d.setSpawnTimer(null); + } + + private int getNeededItems(byte type) + { + switch (type) + { + case 1: + { + return GeneralConfig.RIFT_ENTER_COST_RECRUIT; + } + case 2: + { + return GeneralConfig.RIFT_ENTER_COST_SOLDIER; + } + case 3: + { + return GeneralConfig.RIFT_ENTER_COST_OFFICER; + } + case 4: + { + return GeneralConfig.RIFT_ENTER_COST_CAPTAIN; + } + case 5: + { + return GeneralConfig.RIFT_ENTER_COST_COMMANDER; + } + case 6: + { + return GeneralConfig.RIFT_ENTER_COST_HERO; + } + default: + { + throw new IndexOutOfBoundsException(); + } + } + } + + public void showHtmlFile(Player player, String file, Npc npc) + { + final NpcHtmlMessage html = new NpcHtmlMessage(npc.getObjectId()); + html.setFile(player, file); + html.replace("%npc_name%", npc.getName()); + player.sendPacket(html); + } + + public void handleCheat(Player player, Npc npc) + { + showHtmlFile(player, "data/html/seven_signs/rift/Cheater.htm", npc); + if (!player.isGM()) + { + LOGGER.warning(player + " was cheating in dimension rift area!"); + PunishmentManager.handleIllegalPlayerAction(player, "Warning!! Character " + player.getName() + " tried to cheat in dimensional rift.", GeneralConfig.DEFAULT_PUNISH); + } + } + + public boolean isAllowedEnter(byte type) + { + int count = 0; + for (DimensionalRiftRoom room : _rooms.get(type).values()) + { + if (room.isPartyInside()) + { + count++; + } + } + + return (count < (_rooms.get(type).size() - 1)); + } + + public List getFreeRooms(byte type) + { + final List list = new ArrayList<>(); + for (DimensionalRiftRoom room : _rooms.get(type).values()) + { + if (!room.isPartyInside()) + { + list.add(room.getRoom()); + } + } + + return list; + } + + private static class SingletonHolder + { + protected static final DimensionalRiftManager INSTANCE = new DimensionalRiftManager(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/DuelManager.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/DuelManager.java new file mode 100644 index 0000000..13d4f27 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/DuelManager.java @@ -0,0 +1,232 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.managers; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; + +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.holders.player.Duel; +import org.l2jmobius.gameserver.mechanics.skill.Skill; +import org.l2jmobius.gameserver.network.serverpackets.ServerPacket; + +public class DuelManager +{ + private final Map _duels = new ConcurrentHashMap<>(); + private final AtomicInteger _currentDuelId = new AtomicInteger(); + + protected DuelManager() + { + } + + public Duel getDuel(int duelId) + { + return _duels.get(duelId); + } + + public void addDuel(Player playerA, Player playerB, int partyDuel) + { + if ((playerA == null) || (playerB == null)) + { + return; + } + + // Return if a player has PvPFlag. + final String engagedInPvP = "The duel was canceled because a duelist engaged in PvP combat."; + if (partyDuel == 1) + { + boolean playerInPvP = false; + for (Player temp : playerA.getParty().getMembers()) + { + if (temp.getPvpFlag() != 0) + { + playerInPvP = true; + break; + } + } + + if (!playerInPvP) + { + for (Player temp : playerB.getParty().getMembers()) + { + if (temp.getPvpFlag() != 0) + { + playerInPvP = true; + break; + } + } + } + + // A player has PvP flag. + if (playerInPvP) + { + for (Player temp : playerA.getParty().getMembers()) + { + temp.sendMessage(engagedInPvP); + } + + for (Player temp : playerB.getParty().getMembers()) + { + temp.sendMessage(engagedInPvP); + } + return; + } + } + else + { + if ((playerA.getPvpFlag() != 0) || (playerB.getPvpFlag() != 0)) + { + playerA.sendMessage(engagedInPvP); + playerB.sendMessage(engagedInPvP); + return; + } + } + + final int duelId = _currentDuelId.incrementAndGet(); + _duels.put(duelId, new Duel(playerA, playerB, partyDuel, duelId)); + } + + public void removeDuel(Duel duel) + { + _duels.remove(duel.getId()); + } + + public void doSurrender(Player player) + { + if ((player == null) || !player.isInDuel()) + { + return; + } + + final Duel duel = getDuel(player.getDuelId()); + duel.doSurrender(player); + } + + /** + * Updates player states. + * @param player - the dying player + */ + public void onPlayerDefeat(Player player) + { + if ((player == null) || !player.isInDuel()) + { + return; + } + + final Duel duel = getDuel(player.getDuelId()); + if (duel != null) + { + duel.onPlayerDefeat(player); + } + } + + /** + * Registers a buff which will be removed if the duel ends + * @param player + * @param buff + */ + public void onBuff(Player player, Skill buff) + { + if ((player == null) || !player.isInDuel() || (buff == null)) + { + return; + } + + final Duel duel = getDuel(player.getDuelId()); + if (duel != null) + { + duel.onBuff(player, buff); + } + } + + /** + * Removes player from duel. + * @param player - the removed player + */ + public void onRemoveFromParty(Player player) + { + if ((player == null) || !player.isInDuel()) + { + return; + } + + final Duel duel = getDuel(player.getDuelId()); + if (duel != null) + { + duel.onRemoveFromParty(player); + } + } + + /** + * Broadcasts a packet to the team opposing the given player. + * @param player + * @param packet + */ + public void broadcastToOppositTeam(Player player, ServerPacket packet) + { + if ((player == null) || !player.isInDuel()) + { + return; + } + + final Duel duel = getDuel(player.getDuelId()); + if (duel == null) + { + return; + } + + if ((duel.getPlayerA() == null) || (duel.getPlayerB() == null)) + { + return; + } + + if (duel.getPlayerA() == player) + { + duel.broadcastToTeam2(packet); + } + else if (duel.getPlayerB() == player) + { + duel.broadcastToTeam1(packet); + } + else if (duel.isPartyDuel()) + { + if ((duel.getPlayerA().getParty() != null) && duel.getPlayerA().getParty().getMembers().contains(player)) + { + duel.broadcastToTeam2(packet); + } + else if ((duel.getPlayerB().getParty() != null) && duel.getPlayerB().getParty().getMembers().contains(player)) + { + duel.broadcastToTeam1(packet); + } + } + } + + public static DuelManager getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final DuelManager INSTANCE = new DuelManager(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/EventDropManager.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/EventDropManager.java new file mode 100644 index 0000000..07401d4 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/EventDropManager.java @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.managers; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.l2jmobius.commons.util.Rnd; +import org.l2jmobius.gameserver.config.PlayerConfig; +import org.l2jmobius.gameserver.config.RatesConfig; +import org.l2jmobius.gameserver.entity.actor.Attackable; +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.holders.npc.EventDropHolder; +import org.l2jmobius.gameserver.mechanics.script.LongTimeEvent; + +/** + * @author Mobius + */ +public class EventDropManager +{ + private static final Map> EVENT_DROPS = new ConcurrentHashMap<>(1); + + public void addDrops(LongTimeEvent longTimeEvent, List dropList) + { + EVENT_DROPS.put(longTimeEvent, dropList); + } + + public void removeDrops(LongTimeEvent longTimeEvent) + { + EVENT_DROPS.remove(longTimeEvent); + } + + public void doEventDrop(Creature attacker, Attackable attackable) + { + if (EVENT_DROPS.isEmpty()) + { + return; + } + + // Event items drop only for players. + if ((attacker == null) || !attacker.isPlayable() || attackable.isFakePlayer()) + { + return; + } + + // Event items drop only within a default 9 level difference. + final Player player = attacker.asPlayer(); + if ((player.getLevel() - attackable.getLevel()) > RatesConfig.EVENT_ITEM_MAX_LEVEL_DIFFERENCE) + { + return; + } + + for (List eventDrops : EVENT_DROPS.values()) + { + DROPS: for (EventDropHolder drop : eventDrops) + { + if (!drop.getMonsterIds().isEmpty() && !drop.getMonsterIds().contains(attackable.getId())) + { + continue DROPS; + } + + final int monsterLevel = attackable.getLevel(); + if ((monsterLevel >= drop.getMinLevel()) && (monsterLevel <= drop.getMaxLevel()) && (Rnd.get(100d) < drop.getChance())) + { + final int itemId = drop.getItemId(); + final int itemCount = Rnd.get(drop.getMin(), drop.getMax()); + if (PlayerConfig.AUTO_LOOT_ITEM_IDS.contains(itemId) || PlayerConfig.AUTO_LOOT || attackable.isFlying()) + { + player.doAutoLoot(attackable, itemId, itemCount); // Give the item to the player that has killed the attackable. + } + else + { + attackable.dropItem(player, itemId, itemCount); // Drop the item on the ground. + } + } + } + } + } + + public static EventDropManager getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final EventDropManager INSTANCE = new EventDropManager(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/FakePlayerChatManager.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/FakePlayerChatManager.java new file mode 100644 index 0000000..d0ce21d --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/FakePlayerChatManager.java @@ -0,0 +1,205 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.managers; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Logger; + +import org.w3c.dom.Document; + +import org.l2jmobius.commons.threads.ThreadPool; +import org.l2jmobius.commons.util.IXmlReader; +import org.l2jmobius.commons.util.Rnd; +import org.l2jmobius.gameserver.config.custom.FakePlayersConfig; +import org.l2jmobius.gameserver.data.SpawnTable; +import org.l2jmobius.gameserver.data.holders.FakePlayerChatHolder; +import org.l2jmobius.gameserver.data.xml.FakePlayerData; +import org.l2jmobius.gameserver.entity.actor.Npc; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.spawns.Spawn; +import org.l2jmobius.gameserver.geoengine.GeoEngine; +import org.l2jmobius.gameserver.network.enums.ChatType; +import org.l2jmobius.gameserver.network.serverpackets.CreatureSay; +import org.l2jmobius.gameserver.util.StatSet; + +/** + * @author Mobius + */ +public class FakePlayerChatManager implements IXmlReader +{ + private static final Logger LOGGER = Logger.getLogger(FakePlayerChatManager.class.getName()); + + private static final List MESSAGES = new ArrayList<>(); + private static final int MIN_DELAY = 5000; + private static final int MAX_DELAY = 15000; + + protected FakePlayerChatManager() + { + load(); + } + + @Override + public void load() + { + if (FakePlayersConfig.FAKE_PLAYERS_ENABLED) + { + FakePlayerData.getInstance().report(); + if (FakePlayersConfig.FAKE_PLAYER_CHAT) + { + MESSAGES.clear(); + parseDatapackFile("data/FakePlayerChatData.xml"); + LOGGER.info(getClass().getSimpleName() + ": Loaded " + MESSAGES.size() + " chat templates."); + } + } + } + + @Override + public void parseDocument(Document document, File file) + { + forEach(document, "list", listNode -> forEach(listNode, "fakePlayerChat", fakePlayerChatNode -> + { + // Parse attributes of the "fakePlayerChat" element. + final StatSet set = new StatSet(parseAttributes(fakePlayerChatNode)); + + // Add a new FakePlayerChatHolder to the MESSAGES collection. + MESSAGES.add(new FakePlayerChatHolder(set.getString("fpcName"), set.getString("searchMethod"), set.getString("searchText"), set.getString("answers"))); + })); + } + + public void manageChat(Player player, String fpcName, String message) + { + ThreadPool.schedule(() -> manageResponce(player, fpcName, message), Rnd.get(MIN_DELAY, MAX_DELAY)); + } + + public void manageChat(Player player, String fpcName, String message, int minDelay, int maxDelay) + { + ThreadPool.schedule(() -> manageResponce(player, fpcName, message), Rnd.get(minDelay, maxDelay)); + } + + private void manageResponce(Player player, String fpcName, String message) + { + if (player == null) + { + return; + } + + final String text = message.toLowerCase(); + + // tricky question + if (text.contains("can you see me")) + { + final Spawn spawn = SpawnTable.getInstance().getAnySpawn(FakePlayerData.getInstance().getNpcIdByName(fpcName)); + if (spawn != null) + { + final Npc npc = spawn.getLastSpawn(); + if (npc != null) + { + if (npc.calculateDistance2D(player) < 3000) + { + if (GeoEngine.getInstance().canSeeTarget(npc, player) && !player.isInvisible()) + { + sendChat(player, fpcName, Rnd.nextBoolean() ? "i am not blind" : Rnd.nextBoolean() ? "of course i can" : "yes"); + } + else + { + sendChat(player, fpcName, Rnd.nextBoolean() ? "i know you are around" : Rnd.nextBoolean() ? "not at the moment :P" : "no, where are you?"); + } + } + else + { + sendChat(player, fpcName, Rnd.nextBoolean() ? "nope, can't see you" : Rnd.nextBoolean() ? "nope" : "no"); + } + return; + } + } + } + + for (FakePlayerChatHolder chatHolder : MESSAGES) + { + if (!chatHolder.getFpcName().equals(fpcName) && !chatHolder.getFpcName().equals("ALL")) + { + continue; + } + + switch (chatHolder.getSearchMethod()) + { + case "EQUALS": + { + if (text.equals(chatHolder.getSearchText().get(0))) + { + sendChat(player, fpcName, chatHolder.getAnswers().get(Rnd.get(chatHolder.getAnswers().size()))); + } + break; + } + case "STARTS_WITH": + { + if (text.startsWith(chatHolder.getSearchText().get(0))) + { + sendChat(player, fpcName, chatHolder.getAnswers().get(Rnd.get(chatHolder.getAnswers().size()))); + } + break; + } + case "CONTAINS": + { + boolean allFound = true; + for (String word : chatHolder.getSearchText()) + { + if (!text.contains(word)) + { + allFound = false; + } + } + + if (allFound) + { + sendChat(player, fpcName, chatHolder.getAnswers().get(Rnd.get(chatHolder.getAnswers().size()))); + } + break; + } + } + } + } + + public void sendChat(Player player, String fpcName, String message) + { + final Spawn spawn = SpawnTable.getInstance().getAnySpawn(FakePlayerData.getInstance().getNpcIdByName(fpcName)); + if (spawn != null) + { + final Npc npc = spawn.getLastSpawn(); + if (npc != null) + { + player.sendPacket(new CreatureSay(npc, ChatType.WHISPER, fpcName, message)); + } + } + } + + public static FakePlayerChatManager getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final FakePlayerChatManager INSTANCE = new FakePlayerChatManager(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/FishingChampionshipManager.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/FishingChampionshipManager.java new file mode 100644 index 0000000..868d797 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/FishingChampionshipManager.java @@ -0,0 +1,544 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.managers; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.commons.database.DatabaseFactory; +import org.l2jmobius.commons.threads.ThreadPool; +import org.l2jmobius.commons.util.Rnd; +import org.l2jmobius.gameserver.config.GeneralConfig; +import org.l2jmobius.gameserver.data.xml.ItemData; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.instance.Folk; +import org.l2jmobius.gameserver.entity.item.enums.ItemProcessType; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.serverpackets.NpcHtmlMessage; +import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; + +/** + * @author n0nam3 + * @date 08/08/2010 15:11 + */ +public class FishingChampionshipManager +{ + protected static final Logger LOGGER = Logger.getLogger(FishingChampionshipManager.class.getName()); + + private static final String INSERT = "INSERT INTO fishing_championship(player_name,fish_length,rewarded) VALUES (?,?,?)"; + private static final String DELETE = "DELETE FROM fishing_championship"; + private static final String SELECT = "SELECT `player_name`, `fish_length`, `rewarded` FROM fishing_championship"; + + private static final FishingChampionshipManager INSTANCE = new FishingChampionshipManager(); + + public static FishingChampionshipManager getInstance() + { + return INSTANCE; + } + + protected long _enddate = 0; + protected final List _playersName = new ArrayList<>(); + protected final List _fishLength = new ArrayList<>(); + protected final List _winPlayersName = new ArrayList<>(); + protected final List _winFishLength = new ArrayList<>(); + protected final List _tmpPlayers = new ArrayList<>(); + protected final List _winPlayers = new ArrayList<>(); + protected double _minFishLength = 0; + protected boolean _needRefresh = true; + + private FishingChampionshipManager() + { + restoreData(); + refreshWinResult(); + recalculateMinLength(); + + if (_enddate <= System.currentTimeMillis()) + { + _enddate = System.currentTimeMillis(); + new finishChamp().run(); + } + else + { + ThreadPool.schedule(new finishChamp(), _enddate - System.currentTimeMillis()); + } + } + + protected void setEndOfChamp() + { + final Calendar finishtime = Calendar.getInstance(); + finishtime.setTimeInMillis(_enddate); + finishtime.set(Calendar.MINUTE, 0); + finishtime.set(Calendar.SECOND, 0); + finishtime.add(Calendar.DAY_OF_MONTH, 6); + finishtime.set(Calendar.DAY_OF_WEEK, 3); + finishtime.set(Calendar.HOUR_OF_DAY, 19); + _enddate = finishtime.getTimeInMillis(); + } + + private void restoreData() + { + _enddate = GlobalVariablesManager.getInstance().getLong("fishChampionshipEnd", 0); + + try (Connection con = DatabaseFactory.getConnection()) + { + final PreparedStatement statement = con.prepareStatement(SELECT); + final ResultSet rs = statement.executeQuery(); + while (rs.next()) + { + final int rewarded = rs.getInt("rewarded"); + if (rewarded == 0) + { + _tmpPlayers.add(new Fisher(rs.getString("player_name"), rs.getDouble("fish_length"), 0)); + } + else if (rewarded > 0) + { + _winPlayers.add(new Fisher(rs.getString("player_name"), rs.getDouble("fish_length"), rewarded)); + } + } + + rs.close(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "FishingChampionshipManager: can't restore fishing championship info: " + e.getMessage(), e); + } + } + + public synchronized void newFish(Player pl, int lureId) + { + if (!GeneralConfig.ALT_FISH_CHAMPIONSHIP_ENABLED) + { + return; + } + + double len = Rnd.get(60, 89) + (Rnd.get(0, 1000) / 1000.); + if ((lureId >= 8484) && (lureId <= 8486)) + { + len += Rnd.get(0, 3000) / 1000.; + } + + pl.sendPacket(new SystemMessage(SystemMessageId.YOU_CAUGHT_A_FISH_S1_IN_LENGTH).addString(String.valueOf(len))); + + if (_tmpPlayers.size() < 5) + { + for (Fisher fisher : _tmpPlayers) + { + if (fisher.getName().equalsIgnoreCase(pl.getName())) + { + if (fisher.getLength() < len) + { + fisher.setLength(len); + pl.sendPacket(SystemMessageId.BECAUSE_OF_THE_SIZE_OF_FISH_CAUGHT_YOU_WILL_BE_REGISTERED_IN_THE_RANKING); + recalculateMinLength(); + } + return; + } + } + + _tmpPlayers.add(new Fisher(pl.getName(), len, 0)); + pl.sendPacket(SystemMessageId.BECAUSE_OF_THE_SIZE_OF_FISH_CAUGHT_YOU_WILL_BE_REGISTERED_IN_THE_RANKING); + recalculateMinLength(); + } + else if (_minFishLength < len) + { + for (Fisher fisher : _tmpPlayers) + { + if (fisher.getName().equalsIgnoreCase(pl.getName())) + { + if (fisher.getLength() < len) + { + fisher.setLength(len); + pl.sendPacket(SystemMessageId.BECAUSE_OF_THE_SIZE_OF_FISH_CAUGHT_YOU_WILL_BE_REGISTERED_IN_THE_RANKING); + recalculateMinLength(); + } + return; + } + } + + Fisher minFisher = null; + double minLen = 99999.; + for (Fisher fisher : _tmpPlayers) + { + if (fisher.getLength() < minLen) + { + minFisher = fisher; + minLen = minFisher.getLength(); + } + } + + _tmpPlayers.remove(minFisher); + _tmpPlayers.add(new Fisher(pl.getName(), len, 0)); + pl.sendPacket(SystemMessageId.BECAUSE_OF_THE_SIZE_OF_FISH_CAUGHT_YOU_WILL_BE_REGISTERED_IN_THE_RANKING); + recalculateMinLength(); + } + } + + private void recalculateMinLength() + { + double minLen = 99999.; + for (Fisher fisher : _tmpPlayers) + { + if (fisher.getLength() < minLen) + { + minLen = fisher.getLength(); + } + } + + _minFishLength = minLen; + } + + public long getTimeRemaining() + { + return (_enddate - System.currentTimeMillis()) / 60000; + } + + public String getWinnerName(int par) + { + return _winPlayersName.size() >= par ? _winPlayersName.get(par - 1) : "None"; + } + + public String getCurrentName(int par) + { + return _playersName.size() >= par ? _playersName.get(par - 1) : "None"; + } + + public String getFishLength(int par) + { + return _winFishLength.size() >= par ? _winFishLength.get(par - 1) : "0"; + } + + public String getCurrentFishLength(int par) + { + return _fishLength.size() >= par ? _fishLength.get(par - 1) : "0"; + } + + public boolean isWinner(String playerName) + { + for (String name : _winPlayersName) + { + if (name.equals(playerName)) + { + return true; + } + } + + return false; + } + + public void getReward(Player pl) + { + for (Fisher fisher : _winPlayers) + { + if (fisher.getName().equalsIgnoreCase(pl.getName()) && (fisher.getRewardType() != 2)) + { + int rewardCnt = 0; + for (int x = 0; x < _winPlayersName.size(); x++) + { + if (_winPlayersName.get(x).equalsIgnoreCase(pl.getName())) + { + switch (x) + { + case 0: + { + rewardCnt = GeneralConfig.ALT_FISH_CHAMPIONSHIP_REWARD_1; + break; + } + case 1: + { + rewardCnt = GeneralConfig.ALT_FISH_CHAMPIONSHIP_REWARD_2; + break; + } + case 2: + { + rewardCnt = GeneralConfig.ALT_FISH_CHAMPIONSHIP_REWARD_3; + break; + } + case 3: + { + rewardCnt = GeneralConfig.ALT_FISH_CHAMPIONSHIP_REWARD_4; + break; + } + case 4: + { + rewardCnt = GeneralConfig.ALT_FISH_CHAMPIONSHIP_REWARD_5; + break; + } + } + } + } + + fisher.setRewardType(2); + if (rewardCnt > 0) + { + pl.addItem(ItemProcessType.REWARD, GeneralConfig.ALT_FISH_CHAMPIONSHIP_REWARD_ITEM, rewardCnt, null, true); + + final NpcHtmlMessage html = new NpcHtmlMessage(); + html.setFile(pl, "data/html/fisherman/championship/fish_event_reward001.htm"); + pl.sendPacket(html); + } + } + } + } + + public void showMidResult(Player pl) + { + final NpcHtmlMessage html = new NpcHtmlMessage(); + + if (_needRefresh) + { + html.setFile(pl, "data/html/fisherman/championship/fish_event003.htm"); + pl.sendPacket(html); + + refreshResult(); + ThreadPool.schedule(new needRefresh(), 60000); + return; + } + + html.setFile(pl, "data/html/fisherman/championship/fish_event002.htm"); + + String str = null; + for (int x = 1; x <= 5; x++) + { + str += "" + x + ""; + str += "" + getCurrentName(x) + ""; + str += "" + getCurrentFishLength(x) + ""; + } + + html.replace("%TABLE%", str); + html.replace("%prizeItem%", ItemData.getInstance().getTemplate(GeneralConfig.ALT_FISH_CHAMPIONSHIP_REWARD_ITEM).getName()); + html.replace("%prizeFirst%", String.valueOf(GeneralConfig.ALT_FISH_CHAMPIONSHIP_REWARD_1)); + html.replace("%prizeTwo%", String.valueOf(GeneralConfig.ALT_FISH_CHAMPIONSHIP_REWARD_2)); + html.replace("%prizeThree%", String.valueOf(GeneralConfig.ALT_FISH_CHAMPIONSHIP_REWARD_3)); + html.replace("%prizeFour%", String.valueOf(GeneralConfig.ALT_FISH_CHAMPIONSHIP_REWARD_4)); + html.replace("%prizeFive%", String.valueOf(GeneralConfig.ALT_FISH_CHAMPIONSHIP_REWARD_5)); + pl.sendPacket(html); + } + + public void showChampScreen(Player pl, Folk npc) + { + final NpcHtmlMessage html = new NpcHtmlMessage(); + html.setFile(pl, "data/html/fisherman/championship/fish_event001.htm"); + + String str = null; + for (int x = 1; x <= 5; x++) + { + str += "" + x + ""; + str += "" + getWinnerName(x) + ""; + str += "" + getFishLength(x) + ""; + } + + html.replace("%TABLE%", str); + html.replace("%prizeItem%", ItemData.getInstance().getTemplate(GeneralConfig.ALT_FISH_CHAMPIONSHIP_REWARD_ITEM).getName()); + html.replace("%prizeFirst%", String.valueOf(GeneralConfig.ALT_FISH_CHAMPIONSHIP_REWARD_1)); + html.replace("%prizeTwo%", String.valueOf(GeneralConfig.ALT_FISH_CHAMPIONSHIP_REWARD_2)); + html.replace("%prizeThree%", String.valueOf(GeneralConfig.ALT_FISH_CHAMPIONSHIP_REWARD_3)); + html.replace("%prizeFour%", String.valueOf(GeneralConfig.ALT_FISH_CHAMPIONSHIP_REWARD_4)); + html.replace("%prizeFive%", String.valueOf(GeneralConfig.ALT_FISH_CHAMPIONSHIP_REWARD_5)); + html.replace("%refresh%", String.valueOf(getTimeRemaining())); + html.replace("%objectId%", String.valueOf(npc.getObjectId())); + pl.sendPacket(html); + } + + public void shutdown() + { + GlobalVariablesManager.getInstance().set("fishChampionshipEnd", _enddate); + + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement(DELETE); + statement.execute(); + statement.close(); + + for (Fisher fisher : _winPlayers) + { + statement = con.prepareStatement(INSERT); + statement.setString(1, fisher.getName()); + statement.setDouble(2, fisher.getLength()); + statement.setInt(3, fisher.getRewardType()); + statement.execute(); + statement.close(); + } + + for (Fisher fisher : _tmpPlayers) + { + statement = con.prepareStatement(INSERT); + statement.setString(1, fisher.getName()); + statement.setDouble(2, fisher.getLength()); + statement.setInt(3, 0); + statement.execute(); + statement.close(); + } + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "FishingChampionshipManager: can't update infos: " + e.getMessage(), e); + } + } + + private synchronized void refreshResult() + { + _needRefresh = false; + + _playersName.clear(); + _fishLength.clear(); + + Fisher fisher1; + Fisher fisher2; + + for (int x = 0; x <= (_tmpPlayers.size() - 1); x++) + { + for (int y = 0; y <= (_tmpPlayers.size() - 2); y++) + { + fisher1 = _tmpPlayers.get(y); + fisher2 = _tmpPlayers.get(y + 1); + if (fisher1.getLength() < fisher2.getLength()) + { + _tmpPlayers.set(y, fisher2); + _tmpPlayers.set(y + 1, fisher1); + } + } + } + + for (int x = 0; x <= (_tmpPlayers.size() - 1); x++) + { + _playersName.add(_tmpPlayers.get(x).getName()); + _fishLength.add(String.valueOf(_tmpPlayers.get(x).getLength())); + } + } + + protected void refreshWinResult() + { + _winPlayersName.clear(); + _winFishLength.clear(); + + Fisher fisher1; + Fisher fisher2; + + for (int x = 0; x <= (_winPlayers.size() - 1); x++) + { + for (int y = 0; y <= (_winPlayers.size() - 2); y++) + { + fisher1 = _winPlayers.get(y); + fisher2 = _winPlayers.get(y + 1); + if (fisher1.getLength() < fisher2.getLength()) + { + _winPlayers.set(y, fisher2); + _winPlayers.set(y + 1, fisher1); + } + } + } + + for (int x = 0; x <= (_winPlayers.size() - 1); x++) + { + _winPlayersName.add(_winPlayers.get(x).getName()); + _winFishLength.add(String.valueOf(_winPlayers.get(x).getLength())); + } + } + + private class finishChamp implements Runnable + { + protected finishChamp() + { + // Do nothing. + } + + @Override + public void run() + { + _winPlayers.clear(); + for (Fisher fisher : _tmpPlayers) + { + fisher.setRewardType(1); + _winPlayers.add(fisher); + } + + _tmpPlayers.clear(); + + refreshWinResult(); + setEndOfChamp(); + shutdown(); + + LOGGER.info("FishingChampionshipManager : new event period start."); + ThreadPool.schedule(new finishChamp(), _enddate - System.currentTimeMillis()); + } + } + + private class needRefresh implements Runnable + { + protected needRefresh() + { + // Do nothing. + } + + @Override + public void run() + { + _needRefresh = true; + } + } + + private class Fisher + { + private double _length; + private final String _name; + private int _reward; + + public Fisher(String name, double length, int rewardType) + { + _name = name; + _length = length; + _reward = rewardType; + } + + public void setLength(double value) + { + _length = value; + } + + public void setRewardType(int value) + { + _reward = value; + } + + public String getName() + { + return _name; + } + + public int getRewardType() + { + return _reward; + } + + public double getLength() + { + return _length; + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/GlobalVariablesManager.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/GlobalVariablesManager.java new file mode 100644 index 0000000..5745392 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/GlobalVariablesManager.java @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.managers; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Map.Entry; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.commons.database.DatabaseFactory; +import org.l2jmobius.gameserver.mechanics.variables.AbstractVariables; + +public class GlobalVariablesManager extends AbstractVariables +{ + private static final Logger LOGGER = Logger.getLogger(GlobalVariablesManager.class.getName()); + + // SQL Queries. + private static final String SELECT_QUERY = "SELECT * FROM global_variables"; + private static final String DELETE_QUERY = "DELETE FROM global_variables"; + private static final String INSERT_QUERY = "REPLACE INTO global_variables (var, value) VALUES (?, ?)"; + + // Public variable names + public static final String DAILY_TASK_RESET = "DAILY_TASK_RESET"; + + protected GlobalVariablesManager() + { + restoreMe(); + } + + public boolean restoreMe() + { + // Restore previous variables. + try (Connection con = DatabaseFactory.getConnection(); + Statement st = con.createStatement(); + ResultSet rset = st.executeQuery(SELECT_QUERY)) + { + while (rset.next()) + { + set(rset.getString("var"), rset.getString("value")); + } + } + catch (SQLException e) + { + LOGGER.warning(getClass().getSimpleName() + ": Couldn't restore global variables."); + return false; + } + + LOGGER.info(getClass().getSimpleName() + ": Loaded " + getSet().size() + " variables."); + return true; + } + + public boolean storeMe() + { + try (Connection con = DatabaseFactory.getConnection(); + Statement del = con.createStatement(); + PreparedStatement st = con.prepareStatement(INSERT_QUERY)) + { + // Clear previous entries. + del.execute(DELETE_QUERY); + + // Insert all variables. + for (Entry entry : getSet().entrySet()) + { + st.setString(1, entry.getKey()); + st.setString(2, String.valueOf(entry.getValue())); + st.addBatch(); + } + + st.executeBatch(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Couldn't save global variables to database.", e); + return false; + } + + LOGGER.info(getClass().getSimpleName() + ": Stored " + getSet().size() + " variables."); + return true; + } + + public boolean deleteMe() + { + try (Connection con = DatabaseFactory.getConnection(); + Statement del = con.createStatement()) + { + del.execute(DELETE_QUERY); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Couldn't delete global variables to database.", e); + return false; + } + + return true; + } + + /** + * Gets the single instance of {@code GlobalVariablesManager}. + * @return single instance of {@code GlobalVariablesManager} + */ + public static GlobalVariablesManager getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final GlobalVariablesManager INSTANCE = new GlobalVariablesManager(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/GrandBossManager.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/GrandBossManager.java new file mode 100644 index 0000000..57f39a8 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/GrandBossManager.java @@ -0,0 +1,400 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.managers; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.commons.database.DatabaseFactory; +import org.l2jmobius.commons.threads.ThreadPool; +import org.l2jmobius.gameserver.data.xml.NpcData; +import org.l2jmobius.gameserver.entity.Location; +import org.l2jmobius.gameserver.entity.WorldObject; +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.instance.GrandBoss; +import org.l2jmobius.gameserver.entity.zone.type.BossZone; +import org.l2jmobius.gameserver.util.StatSet; + +/** + * Grand Boss manager. + * @author DaRkRaGe, Emperorc + */ +public class GrandBossManager +{ + // SQL queries + private static final String DELETE_GRAND_BOSS_LIST = "DELETE FROM grandboss_list"; + private static final String INSERT_GRAND_BOSS_LIST = "INSERT INTO grandboss_list (player_id,zone) VALUES (?,?)"; + private static final String UPDATE_GRAND_BOSS_DATA = "UPDATE grandboss_data set loc_x = ?, loc_y = ?, loc_z = ?, heading = ?, respawn_time = ?, currentHP = ?, currentMP = ?, status = ? where boss_id = ?"; + private static final String UPDATE_GRAND_BOSS_DATA2 = "UPDATE grandboss_data set status = ? where boss_id = ?"; + + protected static final Logger LOGGER = Logger.getLogger(GrandBossManager.class.getName()); + + protected static final Map BOSSES = new ConcurrentHashMap<>(); + + protected static Map _storedInfo = new HashMap<>(); + + private final Map _bossStatus = new ConcurrentHashMap<>(); + + private final Map _zones = new ConcurrentHashMap<>(); + + protected GrandBossManager() + { + init(); + } + + private void init() + { + try (Connection con = DatabaseFactory.getConnection(); + Statement s = con.createStatement(); + ResultSet rs = s.executeQuery("SELECT * from grandboss_data ORDER BY boss_id")) + { + while (rs.next()) + { + // Read all info from DB, and store it for AI to read and decide what to do. + // faster than accessing DB in real time + final StatSet info = new StatSet(); + final int bossId = rs.getInt("boss_id"); + info.set("loc_x", rs.getInt("loc_x")); + info.set("loc_y", rs.getInt("loc_y")); + info.set("loc_z", rs.getInt("loc_z")); + info.set("heading", rs.getInt("heading")); + info.set("respawn_time", rs.getLong("respawn_time")); + info.set("currentHP", rs.getDouble("currentHP")); + info.set("currentMP", rs.getDouble("currentMP")); + final int status = rs.getInt("status"); + _bossStatus.put(bossId, status); + _storedInfo.put(bossId, info); + LOGGER.info(getClass().getSimpleName() + ": " + NpcData.getInstance().getTemplate(bossId).getName() + "(" + bossId + ") status is " + status + "."); + if (status > 0) + { + LOGGER.info(getClass().getSimpleName() + ": Next spawn date of " + NpcData.getInstance().getTemplate(bossId).getName() + " is " + new Date(info.getLong("respawn_time")) + "."); + } + } + + LOGGER.info(getClass().getSimpleName() + ": Loaded " + _storedInfo.size() + " Instances"); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Could not load grandboss_data table: " + e.getMessage(), e); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Error while initializing GrandBossManager: " + e.getMessage(), e); + } + + ThreadPool.scheduleAtFixedRate(this::storeMe, 5 * 60 * 1000, 5 * 60 * 1000); + } + + /** + * Zone Functions + */ + public void initZones() + { + final Map> zones = new HashMap<>(); + for (Integer zoneId : _zones.keySet()) + { + zones.put(zoneId, new ArrayList<>()); + } + + try (Connection con = DatabaseFactory.getConnection(); + Statement s = con.createStatement(); + ResultSet rs = s.executeQuery("SELECT * from grandboss_list ORDER BY player_id")) + { + while (rs.next()) + { + zones.get(rs.getInt("zone")).add(rs.getInt("player_id")); + } + + LOGGER.info(getClass().getSimpleName() + ": Initialized " + _zones.size() + " Grand Boss Zones"); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Could not load grandboss_list table: " + e.getMessage(), e); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Error while initializing GrandBoss zones: " + e.getMessage(), e); + } + + for (Entry e : _zones.entrySet()) + { + e.getValue().setAllowedPlayers(zones.get(e.getKey())); + } + + zones.clear(); + } + + public void addZone(BossZone zone) + { + _zones.put(zone.getId(), zone); + } + + public BossZone getZone(int zoneId) + { + return _zones.get(zoneId); + } + + public BossZone getZone(Creature creature) + { + for (BossZone zone : _zones.values()) + { + if (zone.isCharacterInZone(creature)) + { + return zone; + } + } + + return null; + } + + public BossZone getZone(Location loc) + { + return getZone(loc.getX(), loc.getY(), loc.getZ()); + } + + public BossZone getZone(int x, int y, int z) + { + for (BossZone zone : _zones.values()) + { + if (zone.isInsideZone(x, y, z)) + { + return zone; + } + } + + return null; + } + + public boolean checkIfInZone(String zoneType, WorldObject obj) + { + final BossZone temp = getZone(obj.getX(), obj.getY(), obj.getZ()); + return (temp != null) && temp.getName().equalsIgnoreCase(zoneType); + } + + public boolean checkIfInZone(Player player) + { + return (player != null) && (getZone(player.getX(), player.getY(), player.getZ()) != null); + } + + public int getStatus(int bossId) + { + return _bossStatus.get(bossId); + } + + public void setStatus(int bossId, int status) + { + _bossStatus.put(bossId, status); + LOGGER.info(getClass().getSimpleName() + ": Updated " + NpcData.getInstance().getTemplate(bossId).getName() + "(" + bossId + ") status to " + status); + updateDb(bossId, true); + } + + /** + * Adds a GrandBoss to the list of bosses. + * @param boss + */ + public void addBoss(GrandBoss boss) + { + if (boss != null) + { + BOSSES.put(boss.getId(), boss); + } + } + + public GrandBoss getBoss(int bossId) + { + return BOSSES.get(bossId); + } + + public StatSet getStatSet(int bossId) + { + return _storedInfo.get(bossId); + } + + public void setStatSet(int bossId, StatSet info) + { + _storedInfo.put(bossId, info); + updateDb(bossId, false); + } + + public boolean storeMe() + { + try (Connection con = DatabaseFactory.getConnection(); + Statement s = con.createStatement()) + { + s.executeUpdate(DELETE_GRAND_BOSS_LIST); + + try (PreparedStatement insert = con.prepareStatement(INSERT_GRAND_BOSS_LIST)) + { + for (Entry e : _zones.entrySet()) + { + final List list = e.getValue().getAllowedPlayers(); + if ((list == null) || list.isEmpty()) + { + continue; + } + + for (Integer player : list) + { + insert.setInt(1, player); + insert.setInt(2, e.getKey()); + insert.executeUpdate(); + insert.clearParameters(); + } + } + } + + for (Entry e : _storedInfo.entrySet()) + { + final GrandBoss boss = BOSSES.get(e.getKey()); + final StatSet info = e.getValue(); + if ((boss == null) || (info == null)) + { + try (PreparedStatement update = con.prepareStatement(UPDATE_GRAND_BOSS_DATA2)) + { + update.setInt(1, _bossStatus.get(e.getKey())); + update.setInt(2, e.getKey()); + update.executeUpdate(); + update.clearParameters(); + } + } + else + { + try (PreparedStatement update = con.prepareStatement(UPDATE_GRAND_BOSS_DATA)) + { + update.setInt(1, boss.getX()); + update.setInt(2, boss.getY()); + update.setInt(3, boss.getZ()); + update.setInt(4, boss.getHeading()); + update.setLong(5, info.getLong("respawn_time")); + double hp = boss.getCurrentHp(); + double mp = boss.getCurrentMp(); + if (boss.isDead()) + { + hp = boss.getMaxHp(); + mp = boss.getMaxMp(); + } + + update.setDouble(6, hp); + update.setDouble(7, mp); + update.setInt(8, _bossStatus.get(e.getKey())); + update.setInt(9, e.getKey()); + update.executeUpdate(); + update.clearParameters(); + } + } + } + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Couldn't store grandbosses to database:" + e.getMessage(), e); + return false; + } + + return true; + } + + private void updateDb(int bossId, boolean statusOnly) + { + try (Connection con = DatabaseFactory.getConnection()) + { + final GrandBoss boss = BOSSES.get(bossId); + final StatSet info = _storedInfo.get(bossId); + if (statusOnly || (boss == null) || (info == null)) + { + try (PreparedStatement ps = con.prepareStatement(UPDATE_GRAND_BOSS_DATA2)) + { + ps.setInt(1, _bossStatus.get(bossId)); + ps.setInt(2, bossId); + ps.executeUpdate(); + } + } + else + { + try (PreparedStatement ps = con.prepareStatement(UPDATE_GRAND_BOSS_DATA)) + { + ps.setInt(1, boss.getX()); + ps.setInt(2, boss.getY()); + ps.setInt(3, boss.getZ()); + ps.setInt(4, boss.getHeading()); + ps.setLong(5, info.getLong("respawn_time")); + double hp = boss.getCurrentHp(); + double mp = boss.getCurrentMp(); + if (boss.isDead()) + { + hp = boss.getMaxHp(); + mp = boss.getMaxMp(); + } + + ps.setDouble(6, hp); + ps.setDouble(7, mp); + ps.setInt(8, _bossStatus.get(bossId)); + ps.setInt(9, bossId); + ps.executeUpdate(); + } + } + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Couldn't update grandbosses to database:" + e.getMessage(), e); + } + } + + /** + * Saves all Grand Boss info and then clears all info from memory, including all schedules. + */ + public void cleanUp() + { + storeMe(); + + BOSSES.clear(); + _storedInfo.clear(); + _bossStatus.clear(); + _zones.clear(); + } + + public Map getZones() + { + return _zones; + } + + /** + * Gets the single instance of {@code GrandBossManager}. + * @return single instance of {@code GrandBossManager} + */ + public static GrandBossManager getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final GrandBossManager INSTANCE = new GrandBossManager(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/IdManager.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/IdManager.java new file mode 100644 index 0000000..24e47a6 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/IdManager.java @@ -0,0 +1,205 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.managers; + +import java.util.BitSet; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import java.util.logging.Logger; + +import org.l2jmobius.gameserver.config.IdManagerConfig; +import org.l2jmobius.gameserver.util.PrimeCapacityAllocator; + +/** + * Manages unique IDs for objects. + * @author Mobius + */ +public class IdManager +{ + private static final Logger LOGGER = Logger.getLogger(IdManager.class.getName()); + + private static final int TOTAL_ID_COUNT = IdManagerConfig.LAST_OBJECT_ID - IdManagerConfig.FIRST_OBJECT_ID; + + private BitSet _freeIds; + private int _freeIdCount; + private int _nextFreeId; + private final Lock _lock = new ReentrantLock(); + + public IdManager() + { + // Cleanup database. + DatabaseIdManager.cleanDatabase(); + DatabaseIdManager.cleanCharacterStatus(); + DatabaseIdManager.cleanTimestamps(); + + // Initialize BitSet with initial capacity or next prime. + try + { + _freeIds = new BitSet(PrimeCapacityAllocator.nextCapacity(IdManagerConfig.INITIAL_CAPACITY)); + _freeIds.clear(); + _freeIdCount = TOTAL_ID_COUNT; + + // Register used ids. + for (int usedObjectId : DatabaseIdManager.getUsedIds()) + { + final int objectId = usedObjectId - IdManagerConfig.FIRST_OBJECT_ID; + if (objectId < 0) + { + continue; + } + + _freeIds.set(objectId); + _freeIdCount--; + } + + _nextFreeId = _freeIds.nextClearBit(0); + } + catch (Exception e) + { + LOGGER.severe("IdManager: Could not be initialized properly: " + e.getMessage()); + } + + LOGGER.info("IdManager: " + _freeIds.size() + " ids available."); + } + + /** + * Increases the capacity of the BitSet to accommodate more IDs, based on the current utilization and threshold.
        + * Uses the next prime number for optimal BitSet size, up to the maximum allowed ID range. + */ + private void increaseBitSetCapacity() + { + final int currentSize = _freeIds.size(); + final int newSize = Math.min(PrimeCapacityAllocator.nextCapacity((int) (currentSize * IdManagerConfig.RESIZE_MULTIPLIER)), TOTAL_ID_COUNT); + + // Only resize if the new size is larger than the current size. + if (newSize > currentSize) + { + final BitSet newBitSet = new BitSet(newSize); + newBitSet.or(_freeIds); + _freeIds = newBitSet; + LOGGER.info("IdManager: Increased BitSet capacity to " + newSize); + } + } + + /** + * Allocates and returns the next available unique ID.
        + * If the BitSet reaches the defined utilization threshold, it is resized to maintain available capacity. + * @return the next unique ID + * @throws NullPointerException if there are no valid IDs remaining in the configured range + */ + public int getNextId() + { + _lock.lock(); + try + { + final int newId = _nextFreeId; + _freeIds.set(newId); + _freeIdCount--; + + // Check utilization and increase capacity if needed. + final double utilization = (double) (TOTAL_ID_COUNT - _freeIdCount) / _freeIds.size(); + if (utilization >= IdManagerConfig.RESIZE_THRESHOLD) + { + increaseBitSetCapacity(); + _nextFreeId = _freeIds.nextClearBit(0); // Reset to the first available ID in the resized BitSet. + } + + int nextFree = _freeIds.nextClearBit(newId); + if (nextFree < 0) + { + nextFree = _freeIds.nextClearBit(0); + } + + if (nextFree < 0) + { + if (_freeIds.size() < TOTAL_ID_COUNT) + { + increaseBitSetCapacity(); + _nextFreeId = _freeIds.nextClearBit(0); // Reset again if resizing occurred. + } + else + { + throw new NullPointerException("IdManager: Ran out of valid ids."); + } + } + + _nextFreeId = nextFree; + + return newId + IdManagerConfig.FIRST_OBJECT_ID; + } + finally + { + _lock.unlock(); + } + } + + /** + * Releases a previously allocated ID, making it available for future allocations. + * @param objectId the ID to release + */ + public void releaseId(int objectId) + { + _lock.lock(); + try + { + if ((objectId - IdManagerConfig.FIRST_OBJECT_ID) > -1) + { + _freeIds.clear(objectId - IdManagerConfig.FIRST_OBJECT_ID); + _freeIdCount++; + } + else + { + LOGGER.warning("IdManager: Release objectID " + objectId + " failed (< " + IdManagerConfig.FIRST_OBJECT_ID + ")"); + } + } + finally + { + _lock.unlock(); + } + } + + /** + * Retrieves the current count of available IDs within the range. + * @return the number of available IDs + */ + public int getAvailableIdCount() + { + _lock.lock(); + try + { + return _freeIdCount; + } + finally + { + _lock.unlock(); + } + } + + public static IdManager getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final IdManager INSTANCE = new IdManager(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/InstanceManager.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/InstanceManager.java new file mode 100644 index 0000000..14b3261 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/InstanceManager.java @@ -0,0 +1,435 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.managers; + +import java.io.File; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.w3c.dom.Document; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; + +import org.l2jmobius.commons.database.DatabaseFactory; +import org.l2jmobius.commons.util.IXmlReader; +import org.l2jmobius.gameserver.entity.WorldObject; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.instancezone.Instance; +import org.l2jmobius.gameserver.entity.instancezone.InstanceWorld; + +/** + * Instance manager. + * @author evill33t, GodKratos, Mobius + */ +public class InstanceManager implements IXmlReader +{ + private static final Map INSTANCES = new ConcurrentHashMap<>(); + private final Map _instanceWorlds = new ConcurrentHashMap<>(); + private int _dynamic = 300000; + + // InstanceId Names + private static final Map _instanceIdNames = new HashMap<>(); + + // Instance templates + private final Map _instanceTemplates = new ConcurrentHashMap<>(); + private final Map> _playerTimes = new ConcurrentHashMap<>(); + + // SQL Queries + private static final String ADD_INSTANCE_TIME = "INSERT INTO character_instance_time (charId,instanceId,time) values (?,?,?) ON DUPLICATE KEY UPDATE time=?"; + private static final String RESTORE_INSTANCE_TIMES = "SELECT instanceId,time FROM character_instance_time WHERE charId=?"; + private static final String DELETE_INSTANCE_TIME = "DELETE FROM character_instance_time WHERE charId=? AND instanceId=?"; + + protected InstanceManager() + { + // Creates the multiverse. + INSTANCES.put(-1, new Instance(-1)); + LOGGER.info(getClass().getSimpleName() + ": Multiverse Instance created."); + + // Creates the universe. + INSTANCES.put(0, new Instance(0)); + LOGGER.info(getClass().getSimpleName() + ": Universe Instance created."); + load(); + } + + @Override + public void load() + { + _instanceIdNames.clear(); + final File nameFile = new File("data/InstanceNames.xml"); + if (nameFile.exists()) + { + parseDatapackFile("data/InstanceNames.xml"); + LOGGER.info(getClass().getSimpleName() + ": Loaded " + _instanceIdNames.size() + " instance names."); + } + + // Load instance templates. + _instanceTemplates.clear(); + final File instanceFolder = new File("data/instances"); + if (instanceFolder.exists()) + { + parseDatapackDirectory("data/instances", true); + LOGGER.info(getClass().getSimpleName() + ": Loaded " + _instanceTemplates.size() + " instance templates."); + } + } + + /** + * @param playerObjId + * @param id + * @return + */ + public long getInstanceTime(int playerObjId, int id) + { + if (!_playerTimes.containsKey(playerObjId)) + { + restoreInstanceTimes(playerObjId); + } + + if (_playerTimes.get(playerObjId).containsKey(id)) + { + return _playerTimes.get(playerObjId).get(id); + } + + return -1; + } + + /** + * @param playerObjId + * @return + */ + public Map getAllInstanceTimes(int playerObjId) + { + if (!_playerTimes.containsKey(playerObjId)) + { + restoreInstanceTimes(playerObjId); + } + + return _playerTimes.get(playerObjId); + } + + /** + * @param playerObjId + * @param id + * @param time + */ + public void setInstanceTime(int playerObjId, int id, long time) + { + if (!_playerTimes.containsKey(playerObjId)) + { + restoreInstanceTimes(playerObjId); + } + + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement(ADD_INSTANCE_TIME)) + { + ps.setInt(1, playerObjId); + ps.setInt(2, id); + ps.setLong(3, time); + ps.setLong(4, time); + ps.execute(); + _playerTimes.get(playerObjId).put(id, time); + } + catch (Exception e) + { + LOGGER.warning(getClass().getSimpleName() + ": Could not insert character instance time data: " + e.getMessage()); + } + } + + /** + * @param playerObjId + * @param id + */ + public void deleteInstanceTime(int playerObjId, int id) + { + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement(DELETE_INSTANCE_TIME)) + { + ps.setInt(1, playerObjId); + ps.setInt(2, id); + ps.execute(); + _playerTimes.get(playerObjId).remove(id); + } + catch (Exception e) + { + LOGGER.warning(getClass().getSimpleName() + ": Could not delete character instance time data: " + e.getMessage()); + } + } + + /** + * @param playerObjId + */ + public void restoreInstanceTimes(int playerObjId) + { + if (_playerTimes.containsKey(playerObjId)) + { + return; // already restored + } + + _playerTimes.put(playerObjId, new ConcurrentHashMap<>()); + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement(RESTORE_INSTANCE_TIMES)) + { + ps.setInt(1, playerObjId); + try (ResultSet rs = ps.executeQuery()) + { + while (rs.next()) + { + final int id = rs.getInt("instanceId"); + final long time = rs.getLong("time"); + if (time < System.currentTimeMillis()) + { + deleteInstanceTime(playerObjId, id); + } + else + { + _playerTimes.get(playerObjId).put(id, time); + } + } + } + } + catch (Exception e) + { + LOGGER.warning(getClass().getSimpleName() + ": Could not delete character instance time data: " + e.getMessage()); + } + } + + /** + * @param id + * @return + */ + public String getInstanceIdName(int id) + { + return _instanceIdNames.getOrDefault(id, "UnknownInstance"); + } + + @Override + public void parseDocument(Document document, File file) + { + for (Node n = document.getFirstChild(); n != null; n = n.getNextSibling()) + { + switch (n.getNodeName()) + { + case "list": + { + NamedNodeMap attrs; + for (Node d = n.getFirstChild(); d != null; d = d.getNextSibling()) + { + if ("instance".equals(d.getNodeName())) + { + attrs = d.getAttributes(); + _instanceIdNames.put(parseInteger(attrs, "id"), attrs.getNamedItem("name").getNodeValue()); + } + } + break; + } + case "instance": + { + final NamedNodeMap attrs = n.getAttributes(); + _instanceTemplates.put(parseInteger(attrs, "id"), file.getPath().substring(file.getPath().lastIndexOf(File.separator + "instances" + File.separator) + 11)); + break; + } + } + } + } + + /** + * @param world + */ + public void addWorld(InstanceWorld world) + { + _instanceWorlds.put(world.getInstanceId(), world); + } + + /** + * @param instanceId + * @return + */ + public InstanceWorld getWorld(int instanceId) + { + return _instanceWorlds.get(instanceId); + } + + /** + * @param object + * @return the InstanceWorld of the object + */ + public InstanceWorld getWorld(WorldObject object) + { + return _instanceWorlds.get(object.getInstanceId()); + } + + /** + * Check if the player have a World Instance where it's allowed to enter. + * @param player the player to check + * @return the instance world + */ + public InstanceWorld getPlayerWorld(Player player) + { + for (InstanceWorld temp : _instanceWorlds.values()) + { + if ((temp != null) && (temp.isAllowed(player))) + { + return temp; + } + } + + return null; + } + + /** + * @param instanceid + */ + public void destroyInstance(int instanceid) + { + if (instanceid <= 0) + { + return; + } + + final Instance temp = INSTANCES.get(instanceid); + if (temp != null) + { + temp.removeNpcs(); + temp.removePlayers(); + temp.removeDoors(); + temp.cancelTimer(); + INSTANCES.remove(instanceid); + _instanceWorlds.remove(instanceid); + } + } + + /** + * @param instanceid + * @return + */ + public Instance getInstance(int instanceid) + { + return INSTANCES.get(instanceid); + } + + /** + * @return + */ + public Map getInstances() + { + return INSTANCES; + } + + /** + * @param objectId + * @return + */ + public int getPlayer(int objectId) + { + for (Instance temp : INSTANCES.values()) + { + if (temp == null) + { + continue; + } + + // check if the player is in any active instance + if (temp.containsPlayer(objectId)) + { + return temp.getId(); + } + } + + // 0 is default instance aka the world + return 0; + } + + /** + * @param id + * @param templateId + * @return + */ + public boolean createInstanceFromTemplate(int id, int templateId) + { + if (getInstance(id) != null) + { + return false; + } + + final Instance instance = new Instance(id); + INSTANCES.put(id, instance); + instance.loadInstanceTemplate(templateId); + instance.spawnDoors(); + instance.spawnGroup("general"); + return true; + } + + /** + * Create a new instance with a dynamic instance id based on a template (or null) + * @param templateId the instance template id + * @return + */ + public synchronized Instance createDynamicInstance(int templateId) + { + while (getInstance(_dynamic) != null) + { + _dynamic++; + if (_dynamic == Integer.MAX_VALUE) + { + LOGGER.warning(getClass().getSimpleName() + ": More then " + (Integer.MAX_VALUE - 300000) + " instances created"); + _dynamic = 300000; + } + } + + final Instance instance = new Instance(_dynamic); + INSTANCES.put(_dynamic, instance); + if (templateId > 0) + { + instance.loadInstanceTemplate(templateId); + instance.spawnDoors(); + instance.spawnGroup("general"); + } + + return instance; + } + + /** + * Get instance template file name by template ID. + * @param id template id of instance + * @return instance template if found, otherwise {@code null} + */ + public String getInstanceTemplateFileName(int id) + { + return _instanceTemplates.get(id); + } + + /** + * Gets the single instance of {@code InstanceManager}. + * @return single instance of {@code InstanceManager} + */ + public static InstanceManager getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final InstanceManager INSTANCE = new InstanceManager(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/ItemManager.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/ItemManager.java new file mode 100644 index 0000000..6c551d4 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/ItemManager.java @@ -0,0 +1,253 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.managers; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.util.concurrent.ScheduledFuture; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.commons.database.DatabaseFactory; +import org.l2jmobius.commons.threads.ThreadPool; +import org.l2jmobius.commons.util.StringUtil; +import org.l2jmobius.gameserver.config.GeneralConfig; +import org.l2jmobius.gameserver.config.PlayerConfig; +import org.l2jmobius.gameserver.entity.World; +import org.l2jmobius.gameserver.entity.WorldObject; +import org.l2jmobius.gameserver.entity.actor.Attackable; +import org.l2jmobius.gameserver.entity.actor.Creature; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.instance.EventMonster; +import org.l2jmobius.gameserver.entity.item.enums.ItemLocation; +import org.l2jmobius.gameserver.entity.item.enums.ItemProcessType; +import org.l2jmobius.gameserver.entity.item.instance.Item; +import org.l2jmobius.gameserver.entity.itemcontainer.Inventory; +import org.l2jmobius.gameserver.mechanics.events.EventDispatcher; +import org.l2jmobius.gameserver.mechanics.events.EventType; +import org.l2jmobius.gameserver.mechanics.events.holders.item.OnItemCreate; +import org.l2jmobius.gameserver.util.GMAudit; + +/** + * Manages the creation and destruction of items within the game. + * @author Mobius + */ +public class ItemManager +{ + private static final Logger LOGGER = Logger.getLogger(ItemManager.class.getName()); + private static final Logger LOGGER_ITEMS = Logger.getLogger("item"); + + private ItemManager() + { + } + + /** + * Creates a new item based on the specified parameters and logs the creation process.
        + * This overloaded version does not require a reference object. + * @param process an ItemProcessType identifier of the process triggering this action + * @param itemId the item ID of the item to be created + * @param count the quantity of the item for stackable items + * @param actor the player requesting the item creation + * @return the created item + */ + public static Item createItem(ItemProcessType process, int itemId, int count, Player actor) + { + return createItem(process, itemId, count, actor, null); + } + + /** + * Creates an item with a specified ID, quantity, and logs the creation.
        + * This method handles loot privileges, adds the item to the world, configures logging settings and notifies scripts of the creation. + * @param process an ItemProcessType identifier for the process triggering the action (e.g., "LOOT" or "BUY") + * @param itemId the unique ID of the item to create + * @param count the quantity of the item for stackable items + * @param actor the creature (typically a player) requesting the item creation + * @param reference an object referencing the action, such as an NPC selling an item or previous item in transformation + * @return the created item with the specified parameters + */ + public static Item createItem(ItemProcessType process, int itemId, int count, Creature actor, Object reference) + { + // Create and Init the Item corresponding to the Item Identifier. + final Item item = new Item(IdManager.getInstance().getNextId(), itemId); + if ((process == ItemProcessType.LOOT) && !PlayerConfig.AUTO_LOOT_ITEM_IDS.contains(itemId)) + { + ScheduledFuture itemLootShedule; + if ((reference instanceof Attackable) && ((Attackable) reference).isRaid()) // Loot privilege for raids. + { + // If in CommandChannel and was killing a World/RaidBoss. + final Attackable raid = (Attackable) reference; + if ((raid.getFirstCommandChannelAttacked() != null) && !PlayerConfig.AUTO_LOOT_RAIDS) + { + item.setOwnerId(raid.getFirstCommandChannelAttacked().getLeaderObjectId()); + itemLootShedule = ThreadPool.schedule(new ResetOwner(item), PlayerConfig.LOOT_RAIDS_PRIVILEGE_INTERVAL); + item.setItemLootShedule(itemLootShedule); + } + } + else if (!PlayerConfig.AUTO_LOOT || ((reference instanceof EventMonster) && ((EventMonster) reference).eventDropOnGround())) + { + item.setOwnerId(actor.getObjectId()); + itemLootShedule = ThreadPool.schedule(new ResetOwner(item), 15000); + item.setItemLootShedule(itemLootShedule); + } + } + + // Add the Item object to the World. + World.addObject(item); + + // Set Item parameters. + if (item.isStackable() && (count > 1)) + { + item.setCount(count); + } + + if ((GeneralConfig.LOG_ITEMS && ((!GeneralConfig.LOG_ITEMS_SMALL_LOG) && (!GeneralConfig.LOG_ITEMS_IDS_ONLY))) || (GeneralConfig.LOG_ITEMS_SMALL_LOG && (item.isEquipable() || (item.getId() == Inventory.ADENA_ID))) || (GeneralConfig.LOG_ITEMS_IDS_ONLY && GeneralConfig.LOG_ITEMS_IDS_LIST.contains(item.getId()))) + { + if (item.isEnchanted()) + { + LOGGER_ITEMS.info(StringUtil.concat("CREATE:", String.valueOf(process), ", item ", String.valueOf(item.getObjectId()), ":+", String.valueOf(item.getEnchantLevel()), " ", item.getTemplate().getName(), "(", String.valueOf(item.getCount()), "), ", String.valueOf(actor), ", ", String.valueOf(reference))); + } + else + { + LOGGER_ITEMS.info(StringUtil.concat("CREATE:", String.valueOf(process), ", item ", String.valueOf(item.getObjectId()), ":", item.getTemplate().getName(), "(", String.valueOf(item.getCount()), "), ", String.valueOf(actor), ", ", String.valueOf(reference))); + } + } + + if ((actor != null) && actor.isGM() && GeneralConfig.GMAUDIT) + { + String referenceName = "no-reference"; + if (reference instanceof WorldObject) + { + referenceName = (((WorldObject) reference).getName() != null ? ((WorldObject) reference).getName() : "no-name"); + } + else if (reference instanceof String) + { + referenceName = (String) reference; + } + + final String targetName = (actor.getTarget() != null ? actor.getTarget().getName() : "no-target"); + GMAudit.logAction(String.valueOf(actor), StringUtil.concat(String.valueOf(process), "(id: ", String.valueOf(itemId), " count: ", String.valueOf(count), " name: ", item.getItemName(), " objId: ", String.valueOf(item.getObjectId()), ")"), targetName, StringUtil.concat("Object referencing this action is: ", referenceName)); + } + + // Notify to scripts. + if (EventDispatcher.getInstance().hasListener(EventType.ON_ITEM_CREATE, item.getTemplate())) + { + EventDispatcher.getInstance().notifyEventAsync(new OnItemCreate(item, actor, reference), item.getTemplate()); + } + + return item; + } + + /** + * Destroys the specified item, making it unusable, removes it from the world and logs the deletion.
        + * This method also handles special cases, such as deleting pets associated with pet control items. + * @param process an ItemProcessType identifier for the process triggering this action + * @param item the item instance to be destroyed + * @param actor the player requesting the item destruction + * @param reference an object referencing the action, such as an NPC or another item + */ + public static void destroyItem(ItemProcessType process, Item item, Player actor, Object reference) + { + synchronized (item) + { + final long old = item.getCount(); + item.setCount(0); + item.setOwnerId(0); + item.setItemLocation(ItemLocation.VOID); + item.setLastChange(Item.REMOVED); + + World.removeObject(item); + IdManager.getInstance().releaseId(item.getObjectId()); + + if ((process != null) && (process != ItemProcessType.NONE)) + { + if ((GeneralConfig.LOG_ITEMS && ((!GeneralConfig.LOG_ITEMS_SMALL_LOG) && (!GeneralConfig.LOG_ITEMS_IDS_ONLY))) || (GeneralConfig.LOG_ITEMS_SMALL_LOG && (item.isEquipable() || (item.getId() == Inventory.ADENA_ID))) || (GeneralConfig.LOG_ITEMS_IDS_ONLY && GeneralConfig.LOG_ITEMS_IDS_LIST.contains(item.getId()))) + { + if (item.isEnchanted()) + { + LOGGER_ITEMS.info(StringUtil.concat("DELETE:", String.valueOf(process), ", item ", String.valueOf(item.getObjectId()), ":+", String.valueOf(item.getEnchantLevel()), " ", item.getTemplate().getName(), "(", String.valueOf(item.getCount()), "), PrevCount(", String.valueOf(old), "), ", String.valueOf(actor), ", ", String.valueOf(reference))); + } + else + { + LOGGER_ITEMS.info(StringUtil.concat("DELETE:", String.valueOf(process), ", item ", String.valueOf(item.getObjectId()), ":", item.getTemplate().getName(), "(", String.valueOf(item.getCount()), "), PrevCount(", String.valueOf(old), "), ", String.valueOf(actor), ", ", String.valueOf(reference))); + } + } + + if ((actor != null) && actor.isGM() && GeneralConfig.GMAUDIT) + { + String referenceName = "no-reference"; + if (reference instanceof WorldObject) + { + referenceName = (((WorldObject) reference).getName() != null ? ((WorldObject) reference).getName() : "no-name"); + } + else if (reference instanceof String) + { + referenceName = (String) reference; + } + + final String targetName = (actor.getTarget() != null ? actor.getTarget().getName() : "no-target"); + GMAudit.logAction(String.valueOf(actor), StringUtil.concat(String.valueOf(process), "(id: ", String.valueOf(item.getId()), " count: ", String.valueOf(item.getCount()), " itemObjId: ", String.valueOf(item.getObjectId()), ")"), targetName, StringUtil.concat("Object referencing this action is: ", referenceName)); + } + } + + // If it's a pet control item, delete the pet as well. + if (item.getTemplate().isPetItem()) + { + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement statement = con.prepareStatement("DELETE FROM pets WHERE item_obj_id=?")) + { + // Delete the pet in database. + statement.setInt(1, item.getObjectId()); + statement.execute(); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "ItemManager: Could not delete pet objectid:", e); + } + } + } + } + + /** + * Runnable class that resets the owner of an item to 0 after a specified time interval.
        + * This is typically used for loot items that need a temporary owner before becoming free to collect. + */ + protected static class ResetOwner implements Runnable + { + Item _item; + + public ResetOwner(Item item) + { + _item = item; + } + + @Override + public void run() + { + // Set owner id to 0 only when location is VOID. + if (_item.getItemLocation() == ItemLocation.VOID) + { + _item.setOwnerId(0); + } + + _item.setItemLootShedule(null); + } + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/ItemsOnGroundManager.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/ItemsOnGroundManager.java new file mode 100644 index 0000000..338b743 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/ItemsOnGroundManager.java @@ -0,0 +1,258 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.managers; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Collection; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.commons.database.DatabaseFactory; +import org.l2jmobius.commons.threads.ThreadPool; +import org.l2jmobius.gameserver.config.GeneralConfig; +import org.l2jmobius.gameserver.entity.World; +import org.l2jmobius.gameserver.entity.item.instance.Item; +import org.l2jmobius.gameserver.taskmanagers.ItemsAutoDestroyTaskManager; + +/** + * This class manage all items on ground. + * @author Enforcer + */ +public class ItemsOnGroundManager implements Runnable +{ + private static final Logger LOGGER = Logger.getLogger(ItemsOnGroundManager.class.getName()); + + private final Collection _items = ConcurrentHashMap.newKeySet(); + + protected ItemsOnGroundManager() + { + if (GeneralConfig.SAVE_DROPPED_ITEM_INTERVAL > 0) + { + ThreadPool.scheduleAtFixedRate(this, GeneralConfig.SAVE_DROPPED_ITEM_INTERVAL, GeneralConfig.SAVE_DROPPED_ITEM_INTERVAL); + } + + load(); + } + + private void load() + { + // If SaveDroppedItem is false, may want to delete all items previously stored to avoid add old items on reactivate. + if (!GeneralConfig.SAVE_DROPPED_ITEM && GeneralConfig.CLEAR_DROPPED_ITEM_TABLE) + { + emptyTable(); + } + + if (!GeneralConfig.SAVE_DROPPED_ITEM) + { + return; + } + + // If DestroyPlayerDroppedItem was previously false, items currently protected will be added to ItemsAutoDestroy. + if (GeneralConfig.DESTROY_DROPPED_PLAYER_ITEM) + { + String str = null; + if (!GeneralConfig.DESTROY_EQUIPABLE_PLAYER_ITEM) + { + // Recycle misc. items only. + str = "UPDATE itemsonground SET drop_time = ? WHERE drop_time = -1 AND equipable = 0"; + } + else if (GeneralConfig.DESTROY_EQUIPABLE_PLAYER_ITEM) + { + // Recycle all items including equip-able. + str = "UPDATE itemsonground SET drop_time = ? WHERE drop_time = -1"; + } + + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement(str)) + { + ps.setLong(1, System.currentTimeMillis()); + ps.execute(); + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, getClass().getSimpleName() + ": Error while updating table ItemsOnGround " + e.getMessage(), e); + } + } + + // Add items to world. + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement("SELECT object_id,item_id,count,enchant_level,x,y,z,drop_time,equipable FROM itemsonground")) + { + int count = 0; + try (ResultSet rs = ps.executeQuery()) + { + Item item; + while (rs.next()) + { + item = new Item(rs.getInt(1), rs.getInt(2)); + World.addObject(item); + + // this check and.. + if (item.isStackable() && (rs.getInt(3) > 1)) + { + item.setCount(rs.getInt(3)); + } + + // This, are really necessary? + if (rs.getInt(4) > 0) + { + item.setEnchantLevel(rs.getInt(4)); + } + + item.setXYZ(rs.getInt(5), rs.getInt(6), rs.getInt(7)); + item.setWorldRegion(World.getRegion(item)); + item.getWorldRegion().addVisibleObject(item); + final long dropTime = rs.getLong(8); + item.setDropTime(dropTime); + item.setProtected(dropTime == -1); + item.setSpawned(true); + World.addVisibleObject(item, item.getWorldRegion()); + _items.add(item); + count++; + + // Add to ItemsAutoDestroy only items not protected. + if (!GeneralConfig.LIST_PROTECTED_ITEMS.contains(item.getId()) && (dropTime > -1) && (((GeneralConfig.AUTODESTROY_ITEM_AFTER > 0) && !item.getTemplate().hasExImmediateEffect()) || ((GeneralConfig.HERB_AUTO_DESTROY_TIME > 0) && item.getTemplate().hasExImmediateEffect()))) + { + ItemsAutoDestroyTaskManager.getInstance().addItem(item); + } + } + } + + LOGGER.info(getClass().getSimpleName() + ": Loaded " + count + " items."); + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, getClass().getSimpleName() + ": Error while loading ItemsOnGround " + e.getMessage(), e); + } + + if (GeneralConfig.EMPTY_DROPPED_ITEM_TABLE_AFTER_LOAD) + { + emptyTable(); + } + } + + public void save(Item item) + { + if (!GeneralConfig.SAVE_DROPPED_ITEM) + { + return; + } + + _items.add(item); + } + + public void removeObject(Item item) + { + if (GeneralConfig.SAVE_DROPPED_ITEM) + { + _items.remove(item); + } + } + + public void saveInDb() + { + run(); + } + + public void cleanUp() + { + _items.clear(); + } + + public void emptyTable() + { + try (Connection con = DatabaseFactory.getConnection(); + Statement s = con.createStatement()) + { + s.executeUpdate("DELETE FROM itemsonground"); + } + catch (Exception e1) + { + LOGGER.log(Level.SEVERE, getClass().getSimpleName() + ": Error while cleaning table ItemsOnGround " + e1.getMessage(), e1); + } + } + + @Override + public synchronized void run() + { + if (!GeneralConfig.SAVE_DROPPED_ITEM) + { + return; + } + + emptyTable(); + + if (_items.isEmpty()) + { + return; + } + + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement("INSERT INTO itemsonground(object_id,item_id,count,enchant_level,x,y,z,drop_time,equipable) VALUES(?,?,?,?,?,?,?,?,?)")) + { + for (Item item : _items) + { + if ((item == null) || CursedWeaponsManager.getInstance().isCursed(item.getId())) + { + continue; // Cursed Items not saved to ground, prevent double save. + } + + try + { + ps.setInt(1, item.getObjectId()); + ps.setInt(2, item.getId()); + ps.setInt(3, item.getCount()); + ps.setInt(4, item.getEnchantLevel()); + ps.setInt(5, item.getX()); + ps.setInt(6, item.getY()); + ps.setInt(7, item.getZ()); + ps.setLong(8, item.isProtected() ? -1 : item.getDropTime()); // Item is protected or AutoDestroyed. + ps.setLong(9, item.isEquipable() ? 1 : 0); // set equip-able + ps.execute(); + ps.clearParameters(); + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, getClass().getSimpleName() + ": Error while inserting into table ItemsOnGround: " + e.getMessage(), e); + } + } + } + catch (SQLException e) + { + LOGGER.log(Level.SEVERE, getClass().getSimpleName() + ": SQL error while storing items on ground: " + e.getMessage(), e); + } + } + + /** + * Gets the single instance of {@code ItemsOnGroundManager}. + * @return single instance of {@code ItemsOnGroundManager} + */ + public static ItemsOnGroundManager getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final ItemsOnGroundManager INSTANCE = new ItemsOnGroundManager(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/MercTicketManager.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/MercTicketManager.java new file mode 100644 index 0000000..2cd3252 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/MercTicketManager.java @@ -0,0 +1,438 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.managers; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.Statement; +import java.util.Collection; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.commons.database.DatabaseFactory; +import org.l2jmobius.gameserver.data.xml.NpcData; +import org.l2jmobius.gameserver.entity.World; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.instance.Defender; +import org.l2jmobius.gameserver.entity.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.entity.item.enums.ItemLocation; +import org.l2jmobius.gameserver.entity.item.instance.Item; +import org.l2jmobius.gameserver.mechanics.siege.Castle; + +/** + * This class is similar to the SiegeGuardManager, except it handles the loading of the mercenary tickets that are dropped on castle floors by the castle lords.
        + * These tickets (a.k.a. badges) need to be read after each server reboot except when the server crashed in the middle of an ongoing siege.
        + * In addition, this class keeps track of the added tickets, in order to properly limit the number of mercenaries in each castle and the number of mercenaries from each mercenary type.
        + * Finally, we provide auxiliary functions to identify the castle in which each item (and its corresponding NPC) belong to, in order to help avoid mixing them up. + * @author yellowperil, Fulminus + */ +public class MercTicketManager +{ + private static final Logger LOGGER = Logger.getLogger(MercTicketManager.class.getName()); + + private static final Collection DROPPED_TICKETS = ConcurrentHashMap.newKeySet(); + + // TODO: move all these values into siege.properties + // max tickets per merc type = 10 + (castleid * 2)? + // max ticker per castle = 40 + (castleid * 20)? + // @formatter:off + private static final int[] MAX_MERC_PER_TYPE = + { + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, // Gludio + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, // Dion + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, // Giran + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, // Oren + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, // Aden + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, // Innadril + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, // Goddard + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, // Rune + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20 // Schuttgart + }; + + // TODO: not retail like: clan level 5 - 30 ticks max, level 7+ - 50 max + protected static final int[] MERCS_MAX_PER_CASTLE = + { + 100, // Gludio + 150, // Dion + 200, // Giran + 300, // Oren + 400, // Aden + 400, // Innadril + 400, // Goddard + 400, // Rune + 400 // Schuttgart + }; + + private static final int[] ITEM_IDS = + { + 3960, 3961, 3962, 3963, 3964, 3965, 3966, 3967, 3968, 3969, 6115, 6116, 6117, 6118, 6119, 6120, 6121, 6122, 6123, 6124, 6038, 6039, 6040, 6041, 6042, 6043, 6044, 6045, 6046, 6047, 6175, 6176, 6177, 6178, 6179, 6180, 6181, 6182, 6183, 6184, 6235, 6236, 6237, 6238, 6239, 6240, 6241, 6242, 6243, 6244, 6295, 6296, // Gludio + 3973, 3974, 3975, 3976, 3977, 3978, 3979, 3980, 3981, 3982, 6125, 6126, 6127, 6128, 6129, 6130, 6131, 6132, 6133, 6134, 6051, 6052, 6053, 6054, 6055, 6056, 6057, 6058, 6059, 6060, 6185, 6186, 6187, 6188, 6189, 6190, 6191, 6192, 6193, 6194, 6245, 6246, 6247, 6248, 6249, 6250, 6251, 6252, 6253, 6254, 6297, 6298, // Dion + 3986, 3987, 3988, 3989, 3990, 3991, 3992, 3993, 3994, 3995, 6135, 6136, 6137, 6138, 6139, 6140, 6141, 6142, 6143, 6144, 6064, 6065, 6066, 6067, 6068, 6069, 6070, 6071, 6072, 6073, 6195, 6196, 6197, 6198, 6199, 6200, 6201, 6202, 6203, 6204, 6255, 6256, 6257, 6258, 6259, 6260, 6261, 6262, 6263, 6264, 6299, 6300, // Giran + 3999, 4000, 4001, 4002, 4003, 4004, 4005, 4006, 4007, 4008, 6145, 6146, 6147, 6148, 6149, 6150, 6151, 6152, 6153, 6154, 6077, 6078, 6079, 6080, 6081, 6082, 6083, 6084, 6085, 6086, 6205, 6206, 6207, 6208, 6209, 6210, 6211, 6212, 6213, 6214, 6265, 6266, 6267, 6268, 6269, 6270, 6271, 6272, 6273, 6274, 6301, 6302, // Oren + 4012, 4013, 4014, 4015, 4016, 4017, 4018, 4019, 4020, 4021, 6155, 6156, 6157, 6158, 6159, 6160, 6161, 6162, 6163, 6164, 6090, 6091, 6092, 6093, 6094, 6095, 6096, 6097, 6098, 6099, 6215, 6216, 6217, 6218, 6219, 6220, 6221, 6222, 6223, 6224, 6275, 6276, 6277, 6278, 6279, 6280, 6281, 6282, 6283, 6284, 6303, 6304, // Aden + 5205, 5206, 5207, 5208, 5209, 5210, 5211, 5212, 5213, 5214, 6165, 6166, 6167, 6168, 6169, 6170, 6171, 6172, 6173, 6174, 6105, 6106, 6107, 6108, 6109, 6110, 6111, 6112, 6113, 6114, 6225, 6226, 6227, 6228, 6229, 6230, 6231, 6232, 6233, 6234, 6285, 6286, 6287, 6288, 6289, 6290, 6291, 6292, 6293, 6294, 6305, 6306, // Innadril + 6779, 6780, 6781, 6782, 6783, 6784, 6785, 6786, 6787, 6788, 6802, 6803, 6804, 6805, 6806, 6807, 6808, 6809, 6810, 6811, 6792, 6793, 6794, 6795, 6796, 6797, 6798, 6799, 6800, 6801, 6812, 6813, 6814, 6815, 6816, 6817, 6818, 6819, 6820, 6821, 6822, 6823, 6824, 6825, 6826, 6827, 6828, 6829, 6830, 6831, 6832, 6833, // Goddard + 7973, 7974, 7975, 7976, 7977, 7978, 7979, 7980, 7981, 7982, 7998, 7999, 8000, 8001, 8002, 8003, 8004, 8005, 8006, 8007, 7988, 7989, 7990, 7991, 7992, 7993, 7994, 7995, 7996, 7997, 8008, 8009, 8010, 8011, 8012, 8013, 8014, 8015, 8016, 8017, 8018, 8019, 8020, 8021, 8022, 8023, 8024, 8025, 8026, 8027, 8028, 8029, // Rune + 7918, 7919, 7920, 7921, 7922, 7923, 7924, 7925, 7926, 7927, 7941, 7942, 7943, 7944, 7945, 7946, 7947, 7948, 7949, 7950, 7931, 7932, 7933, 7934, 7935, 7936, 7937, 7938, 7939, 7940, 7951, 7952, 7953, 7954, 7955, 7956, 7957, 7958, 7959, 7960, 7961, 7962, 7963, 7964, 7965, 7966, 7967, 7968, 7969, 7970, 7971, 7972 // Schuttgart + }; + + private static final int[] NPC_IDS = + { + 35010, 35011, 35012, 35013, 35014, 35015, 35016, 35017, 35018, 35019, 35020, 35021, 35022, 35023, 35024, 35025, 35026, 35027, 35028, 35029, 35030,35031,35032,35033,35034,35035,35036,35037,35038,35039, 35040, 35041, 35042, 35043, 35044, 35045, 35046, 35047, 35048, 35049, 35050, 35051, 35052, 35053, 35054, 35055, 35056, 35057, 35058, 35059, 35060, 35061, // Gludio + 35010, 35011, 35012, 35013, 35014, 35015, 35016, 35017, 35018, 35019, 35020, 35021, 35022, 35023, 35024, 35025, 35026, 35027, 35028, 35029, 35030,35031,35032,35033,35034,35035,35036,35037,35038,35039, 35040, 35041, 35042, 35043, 35044, 35045, 35046, 35047, 35048, 35049, 35050, 35051, 35052, 35053, 35054, 35055, 35056, 35057, 35058, 35059, 35060, 35061, // Dion + 35010, 35011, 35012, 35013, 35014, 35015, 35016, 35017, 35018, 35019, 35020, 35021, 35022, 35023, 35024, 35025, 35026, 35027, 35028, 35029, 35030,35031,35032,35033,35034,35035,35036,35037,35038,35039, 35040, 35041, 35042, 35043, 35044, 35045, 35046, 35047, 35048, 35049, 35050, 35051, 35052, 35053, 35054, 35055, 35056, 35057, 35058, 35059, 35060, 35061, // Giran + 35010, 35011, 35012, 35013, 35014, 35015, 35016, 35017, 35018, 35019, 35020, 35021, 35022, 35023, 35024, 35025, 35026, 35027, 35028, 35029, 35030,35031,35032,35033,35034,35035,35036,35037,35038,35039, 35040, 35041, 35042, 35043, 35044, 35045, 35046, 35047, 35048, 35049, 35050, 35051, 35052, 35053, 35054, 35055, 35056, 35057, 35058, 35059, 35060, 35061, // Oren + 35010, 35011, 35012, 35013, 35014, 35015, 35016, 35017, 35018, 35019, 35020, 35021, 35022, 35023, 35024, 35025, 35026, 35027, 35028, 35029, 35030,35031,35032,35033,35034,35035,35036,35037,35038,35039, 35040, 35041, 35042, 35043, 35044, 35045, 35046, 35047, 35048, 35049, 35050, 35051, 35052, 35053, 35054, 35055, 35056, 35057, 35058, 35059, 35060, 35061, // Aden + 35010, 35011, 35012, 35013, 35014, 35015, 35016, 35017, 35018, 35019, 35020, 35021, 35022, 35023, 35024, 35025, 35026, 35027, 35028, 35029, 35030,35031,35032,35033,35034,35035,35036,35037,35038,35039, 35040, 35041, 35042, 35043, 35044, 35045, 35046, 35047, 35048, 35049, 35050, 35051, 35052, 35053, 35054, 35055, 35056, 35057, 35058, 35059, 35060, 35061, // Innadril + 35010, 35011, 35012, 35013, 35014, 35015, 35016, 35017, 35018, 35019, 35020, 35021, 35022, 35023, 35024, 35025, 35026, 35027, 35028, 35029, 35030,35031,35032,35033,35034,35035,35036,35037,35038,35039, 35040, 35041, 35042, 35043, 35044, 35045, 35046, 35047, 35048, 35049, 35050, 35051, 35052, 35053, 35054, 35055, 35056, 35057, 35058, 35059, 35060, 35061, // Goddard + 35010, 35011, 35012, 35013, 35014, 35015, 35016, 35017, 35018, 35019, 35020, 35021, 35022, 35023, 35024, 35025, 35026, 35027, 35028, 35029, 35030,35031,35032,35033,35034,35035,35036,35037,35038,35039, 35040, 35041, 35042, 35043, 35044, 35045, 35046, 35047, 35048, 35049, 35050, 35051, 35052, 35053, 35054, 35055, 35056, 35057, 35058, 35059, 35060, 35061, // Rune + 35010, 35011, 35012, 35013, 35014, 35015, 35016, 35017, 35018, 35019, 35020, 35021, 35022, 35023, 35024, 35025, 35026, 35027, 35028, 35029, 35030,35031,35032,35033,35034,35035,35036,35037,35038,35039, 35040, 35041, 35042, 35043, 35044, 35045, 35046, 35047, 35048, 35049, 35050, 35051, 35052, 35053, 35054, 35055, 35056, 35057, 35058, 35059, 35060, 35061, // Schuttgart + }; + // @formatter:on + + private static final int GUARDIAN_TYPES_COUNT = 52; + + protected MercTicketManager() + { + load(); + } + + // returns the castleId for the passed ticket item id + public int getTicketCastleId(int itemId) + { + for (int i = 0; i < 9; i++) // CastleID`s from 1 to 9 minus; + { + for (int i2 = 0; i2 < 50; i2 += 10) + { + // Simplified if statement; + if ((itemId >= ITEM_IDS[i2 + (i * GUARDIAN_TYPES_COUNT)]) && (itemId <= ITEM_IDS[i2 + 9 + (i * GUARDIAN_TYPES_COUNT)])) + { + return i + 1; + } + } + + if ((itemId >= ITEM_IDS[50]) && (itemId <= ITEM_IDS[51])) + { + return i + 1; + } + } + + return -1; + } + + public void reload() + { + DROPPED_TICKETS.clear(); + load(); + } + + /** + * Load merc tickets into the world. + */ + private void load() + { + try (Connection con = DatabaseFactory.getConnection(); + Statement s = con.createStatement(); + ResultSet rs = s.executeQuery("SELECT * FROM castle_siege_guards Where isHired = 1")) + { + int npcId; + int itemId; + int x; + int y; + int z; + final int[] mercPlaced = new int[20]; + + // start index to begin the search for the itemId corresponding to this NPC + // this will help with: + // a) skip unnecessary iterations in the search loop + // b) avoid finding the wrong itemId whenever tickets of different spawn the same npc! + int startindex = 0; + + while (rs.next()) + { + npcId = rs.getInt("npcId"); + x = rs.getInt("x"); + y = rs.getInt("y"); + z = rs.getInt("z"); + final Castle castle = CastleManager.getInstance().getCastle(x, y, z); + if (castle != null) + { + if (mercPlaced[castle.getResidenceId() - 1] >= MERCS_MAX_PER_CASTLE[castle.getResidenceId() - 1]) + { + continue; + } + + startindex = GUARDIAN_TYPES_COUNT * (castle.getResidenceId() - 1); + mercPlaced[castle.getResidenceId() - 1] += 1; + } + + // Find the FIRST ticket itemId with spawns the saved NPC in the saved location. + for (int i = startindex; i < (startindex + GUARDIAN_TYPES_COUNT); i++) + { + if (NPC_IDS[i] == npcId) // Find the index of the item used. + { + // only handle tickets if a siege is not ongoing in this npc's castle + if ((castle != null) && !castle.getSiege().isInProgress()) + { + itemId = ITEM_IDS[i]; + + // Create the ticket in the gameworld. + final Item dropticket = new Item(IdManager.getInstance().getNextId(), itemId); + dropticket.setItemLocation(ItemLocation.VOID); + dropticket.dropMe(null, x, y, z); + dropticket.setDropTime(0); // avoids it from being removed by the auto item destroyer + World.addObject(dropticket); + DROPPED_TICKETS.add(dropticket); + } + break; + } + } + } + + rs.close(); + s.close(); + + LOGGER.info(getClass().getSimpleName() + ": Loaded " + DROPPED_TICKETS.size() + " Mercenary Tickets"); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Exception: loadMercenaryData(): " + e.getMessage(), e); + } + } + + /** + * Checks if the passed item has reached the limit of number of dropped tickets that this SPECIFIC item may have in its castle + * @param itemId + * @return + */ + public boolean isAtTypeLimit(int itemId) + { + int limit = -1; + + // find the max value for this item + for (int i = 0; i < ITEM_IDS.length; i++) + { + if (ITEM_IDS[i] == itemId) // Find the index of the item used. + { + limit = MAX_MERC_PER_TYPE[i]; + break; + } + } + + if (limit <= 0) + { + return true; + } + + int count = 0; + for (Item ticket : DROPPED_TICKETS) + { + if ((ticket != null) && (ticket.getId() == itemId)) + { + count++; + } + } + + return count >= limit; + } + + /** + * Checks if the passed item belongs to a castle which has reached its limit of number of dropped tickets. + * @param itemId + * @return + */ + public boolean isAtCasleLimit(int itemId) + { + final int castleId = getTicketCastleId(itemId); + if (castleId <= 0) + { + return true; + } + + final int limit = MERCS_MAX_PER_CASTLE[castleId - 1]; + if (limit <= 0) + { + return true; + } + + int count = 0; + for (Item ticket : DROPPED_TICKETS) + { + if ((ticket != null) && (getTicketCastleId(ticket.getId()) == castleId)) + { + count++; + } + } + + return count >= limit; + } + + public int getMaxAllowedMerc(int castleId) + { + return MERCS_MAX_PER_CASTLE[castleId - 1]; + } + + public boolean isTooCloseToAnotherTicket(int x, int y, int z) + { + for (Item item : DROPPED_TICKETS) + { + final double dx = x - item.getX(); + final double dy = y - item.getY(); + final double dz = z - item.getZ(); + if (((dx * dx) + (dy * dy) + (dz * dz)) < (25 * 25)) + { + return true; + } + } + + return false; + } + + /** + * addTicket actions 1) find the npc that needs to be saved in the mercenary spawns, given this item 2) Use the passed character's location info to add the spawn 3) create a copy of the item to drop in the world returns the id of the mercenary npc that was added to the spawn returns -1 if this + * fails. + * @param itemId + * @param player + * @return + */ + public int addTicket(int itemId, Player player) + { + final Castle castle = CastleManager.getInstance().getCastle(player); + if (castle == null) + { + return -1; + } + + final int x = player.getX(); + final int y = player.getY(); + final int z = player.getZ(); + final int heading = player.getHeading(); + for (int i = 0; i < ITEM_IDS.length; i++) + { + if (ITEM_IDS[i] == itemId) // Find the index of the item used. + { + spawnMercenary(NPC_IDS[i], x, y, z, 3000); + + // Hire merc for this castle. NpcId is at the same index as the item used. + castle.getSiege().getSiegeGuardManager().hireMerc(x, y, z, heading, NPC_IDS[i]); + + // Create the ticket in the gameworld. + final Item dropticket = new Item(IdManager.getInstance().getNextId(), itemId); + dropticket.setItemLocation(ItemLocation.VOID); + dropticket.dropMe(null, x, y, z); + dropticket.setDropTime(0); // avoids it from beeing removed by the auto item destroyer + World.addObject(dropticket); // add to the world + + // and keep track of this ticket in the list + DROPPED_TICKETS.add(dropticket); + + return NPC_IDS[i]; + } + } + + return -1; + } + + private void spawnMercenary(int npcId, int x, int y, int z, int despawnDelay) + { + final NpcTemplate template = NpcData.getInstance().getTemplate(npcId); + if (template == null) + { + return; + } + + final Defender npc = new Defender(template); + npc.setCurrentHpMp(npc.getMaxHp(), npc.getMaxMp()); + npc.setDecayed(false); + npc.spawnMe(x, y, z + 20); + if (despawnDelay > 0) + { + npc.scheduleDespawn(despawnDelay); + } + } + + /** + * Delete all tickets from a castle; remove the items from the world and remove references to them from this class + * @param castleId + */ + public void deleteTickets(int castleId) + { + for (Item item : DROPPED_TICKETS) + { + if ((item != null) && (getTicketCastleId(item.getId()) == castleId)) + { + item.decayMe(); + // World.removeObject(item); // redundant - decayMe() already calls World.removeObject + DROPPED_TICKETS.remove(item); + } + } + } + + /** + * remove a single ticket and its associated spawn from the world (used when the castle lord picks up a ticket, for example) + * @param item + */ + public void removeTicket(Item item) + { + final int itemId = item.getId(); + int npcId = -1; + + // Find the FIRST ticket itemId with spawns the saved NPC in the saved location. + for (int i = 0; i < ITEM_IDS.length; i++) + { + if (ITEM_IDS[i] == itemId) // Find the index of the item used. + { + npcId = NPC_IDS[i]; + break; + } + } + + // find the castle where this item is + final Castle castle = CastleManager.getInstance().getCastleById(getTicketCastleId(itemId)); + if ((npcId > 0) && (castle != null)) + { + (new SiegeGuardManager(castle)).removeMerc(npcId, item.getX(), item.getY(), item.getZ()); + } + + DROPPED_TICKETS.remove(item); + } + + public int[] getItemIds() + { + return ITEM_IDS; + } + + public Collection getDroppedTickets() + { + return DROPPED_TICKETS; + } + + /** + * Gets the single instance of {@code MercTicketManager}. + * @return single instance of {@code MercTicketManager} + */ + public static MercTicketManager getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final MercTicketManager INSTANCE = new MercTicketManager(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/PcCafePointsManager.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/PcCafePointsManager.java new file mode 100644 index 0000000..873f109 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/PcCafePointsManager.java @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.managers; + +import org.l2jmobius.commons.threads.ThreadPool; +import org.l2jmobius.commons.util.Rnd; +import org.l2jmobius.gameserver.config.custom.PremiumSystemConfig; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.zone.ZoneId; +import org.l2jmobius.gameserver.network.serverpackets.ExPCCafePointInfo; + +public class PcCafePointsManager +{ + public void run(Player player) + { + // PC-points only premium accounts. + if (!PremiumSystemConfig.PC_CAFE_ENABLED || !PremiumSystemConfig.PC_CAFE_RETAIL_LIKE || (!player.hasEnteredWorld())) + { + return; + } + + ThreadPool.scheduleAtFixedRate(() -> giveRetailPcCafePont(player), PremiumSystemConfig.PC_CAFE_REWARD_TIME, PremiumSystemConfig.PC_CAFE_REWARD_TIME); + } + + public void giveRetailPcCafePont(Player player) + { + if (!PremiumSystemConfig.PC_CAFE_ENABLED || !PremiumSystemConfig.PC_CAFE_RETAIL_LIKE || (player.isOnlineInt() == 0) || (!player.hasPremiumStatus() && PremiumSystemConfig.PC_CAFE_ONLY_PREMIUM) || player.isInOfflineMode()) + { + return; + } + + int points = PremiumSystemConfig.ACQUISITION_PC_CAFE_RETAIL_LIKE_POINTS; + + if (points >= PremiumSystemConfig.PC_CAFE_MAX_POINTS) + { + player.sendMessage("The maximum accumulation allowed of PC cafe points has been exceeded. You can no longer acquire PC cafe points."); + return; + } + + if (PremiumSystemConfig.PC_CAFE_RANDOM_POINT) + { + points = Rnd.get(points / 2, points); + } + + if (PremiumSystemConfig.PC_CAFE_ENABLE_DOUBLE_POINTS && (Rnd.get(100) < PremiumSystemConfig.PC_CAFE_DOUBLE_POINTS_CHANCE)) + { + points *= 2; + player.sendMessage("Double points! You acquired " + points + " PC Cafe Points."); + } + else + { + player.sendMessage("You have acquired " + points + " PC Cafe points."); + } + + if ((player.getPcCafePoints() + points) > PremiumSystemConfig.PC_CAFE_MAX_POINTS) + { + points = PremiumSystemConfig.PC_CAFE_MAX_POINTS - player.getPcCafePoints(); + } + + player.setPcCafePoints(player.getPcCafePoints() + points); + player.sendPacket(new ExPCCafePointInfo(player.getPcCafePoints(), points, 1)); + } + + public void givePcCafePoint(Player player, double exp) + { + if (PremiumSystemConfig.PC_CAFE_RETAIL_LIKE || !PremiumSystemConfig.PC_CAFE_ENABLED || player.isInsideZone(ZoneId.PEACE) || player.isInsideZone(ZoneId.PVP) || player.isInsideZone(ZoneId.SIEGE) || (player.isOnlineInt() == 0) || player.isJailed()) + { + return; + } + + // PC-points only premium accounts. + if (PremiumSystemConfig.PC_CAFE_ONLY_PREMIUM && !player.hasPremiumStatus()) + { + return; + } + + if (player.getPcCafePoints() >= PremiumSystemConfig.PC_CAFE_MAX_POINTS) + { + player.sendMessage("The maximum accumulation allowed of PC cafe points has been exceeded. You can no longer acquire PC cafe points."); + return; + } + + int points = (int) (exp * 0.0001 * PremiumSystemConfig.PC_CAFE_POINT_RATE); + if (PremiumSystemConfig.PC_CAFE_RANDOM_POINT) + { + points = Rnd.get(points / 2, points); + } + + if ((points == 0) && (exp > 0) && PremiumSystemConfig.PC_CAFE_REWARD_LOW_EXP_KILLS && (Rnd.get(100) < PremiumSystemConfig.PC_CAFE_LOW_EXP_KILLS_CHANCE)) + { + points = 1; // minimum points + } + + if (points <= 0) + { + return; + } + + if (PremiumSystemConfig.PC_CAFE_ENABLE_DOUBLE_POINTS && (Rnd.get(100) < PremiumSystemConfig.PC_CAFE_DOUBLE_POINTS_CHANCE)) + { + points *= 2; + player.sendMessage("Double points! You acquired " + points + " PC Cafe Points."); + } + else + { + player.sendMessage("You have acquired " + points + " PC Cafe points."); + } + + if ((player.getPcCafePoints() + points) > PremiumSystemConfig.PC_CAFE_MAX_POINTS) + { + points = PremiumSystemConfig.PC_CAFE_MAX_POINTS - player.getPcCafePoints(); + } + + player.setPcCafePoints(player.getPcCafePoints() + points); + player.sendPacket(new ExPCCafePointInfo(player.getPcCafePoints(), points, 0)); + } + + /** + * Gets the single instance of {@code PcCafePointsManager}. + * @return single instance of {@code PcCafePointsManager} + */ + public static PcCafePointsManager getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final PcCafePointsManager INSTANCE = new PcCafePointsManager(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/PetitionManager.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/PetitionManager.java new file mode 100644 index 0000000..bdba797 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/PetitionManager.java @@ -0,0 +1,445 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.managers; + +import java.time.Instant; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Logger; + +import org.l2jmobius.gameserver.config.PlayerConfig; +import org.l2jmobius.gameserver.data.xml.AdminData; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.mechanics.petition.Petition; +import org.l2jmobius.gameserver.mechanics.petition.PetitionState; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.enums.ChatType; +import org.l2jmobius.gameserver.network.serverpackets.CreatureSay; +import org.l2jmobius.gameserver.network.serverpackets.NpcHtmlMessage; +import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; + +/** + * Petition Manager + * @author Tempy + */ +public class PetitionManager +{ + protected static final Logger LOGGER = Logger.getLogger(PetitionManager.class.getName()); + + private static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); + + private final Map _pendingPetitions; + private final Map _completedPetitions; + + protected PetitionManager() + { + _pendingPetitions = new HashMap<>(); + _completedPetitions = new HashMap<>(); + } + + public void clearCompletedPetitions() + { + final int numPetitions = _pendingPetitions.size(); + _completedPetitions.clear(); + LOGGER.info(getClass().getSimpleName() + ": Completed petition data cleared. " + numPetitions + " petitions removed."); + } + + public void clearPendingPetitions() + { + final int numPetitions = _pendingPetitions.size(); + _pendingPetitions.clear(); + LOGGER.info(getClass().getSimpleName() + ": Pending petition queue cleared. " + numPetitions + " petitions removed."); + } + + public boolean acceptPetition(Player respondingAdmin, int petitionId) + { + if (!isValidPetition(petitionId)) + { + return false; + } + + final Petition currPetition = _pendingPetitions.get(petitionId); + if (currPetition.getResponder() != null) + { + return false; + } + + currPetition.setResponder(respondingAdmin); + currPetition.setState(PetitionState.IN_PROCESS); + + // Petition application accepted. (Send to Petitioner). + currPetition.sendPetitionerPacket(new SystemMessage(SystemMessageId.PETITION_APPLICATION_ACCEPTED)); + + // Petition application accepted. Reciept No. is . + SystemMessage sm = new SystemMessage(SystemMessageId.YOUR_PETITION_APPLICATION_HAS_BEEN_ACCEPTED_RECEIPT_NO_IS_S1); + sm.addInt(currPetition.getId()); + currPetition.sendResponderPacket(sm); + + // Petition consultation with underway. + sm = new SystemMessage(SystemMessageId.PETITION_CONSULTATION_WITH_S1_UNDER_WAY); + sm.addString(currPetition.getPetitioner().getName()); + currPetition.sendResponderPacket(sm); + + // Set responder name on petitioner instance. + currPetition.getPetitioner().setLastPetitionGmName(currPetition.getResponder().getName()); + return true; + } + + public boolean cancelActivePetition(Player player) + { + for (Petition currPetition : _pendingPetitions.values()) + { + if ((currPetition.getPetitioner() != null) && (currPetition.getPetitioner().getObjectId() == player.getObjectId())) + { + return (currPetition.endPetitionConsultation(PetitionState.PETITIONER_CANCEL)); + } + + if ((currPetition.getResponder() != null) && (currPetition.getResponder().getObjectId() == player.getObjectId())) + { + return (currPetition.endPetitionConsultation(PetitionState.RESPONDER_CANCEL)); + } + } + + return false; + } + + public void checkPetitionMessages(Player petitioner) + { + if (petitioner != null) + { + for (Petition currPetition : _pendingPetitions.values()) + { + if (currPetition == null) + { + continue; + } + + if ((currPetition.getPetitioner() != null) && (currPetition.getPetitioner().getObjectId() == petitioner.getObjectId())) + { + for (CreatureSay logMessage : currPetition.getLogMessages()) + { + petitioner.sendPacket(logMessage); + } + + return; + } + } + } + } + + public boolean endActivePetition(Player player) + { + if (!player.isGM()) + { + return false; + } + + for (Petition currPetition : _pendingPetitions.values()) + { + if (currPetition == null) + { + continue; + } + + if ((currPetition.getResponder() != null) && (currPetition.getResponder().getObjectId() == player.getObjectId())) + { + return currPetition.endPetitionConsultation(PetitionState.COMPLETED); + } + } + + return false; + } + + public Map getCompletedPetitions() + { + return _completedPetitions; + } + + public Map getPendingPetitions() + { + return _pendingPetitions; + } + + public int getPendingPetitionCount() + { + return _pendingPetitions.size(); + } + + public int getPlayerTotalPetitionCount(Player player) + { + if (player == null) + { + return 0; + } + + int petitionCount = 0; + for (Petition currPetition : _pendingPetitions.values()) + { + if (currPetition == null) + { + continue; + } + + if ((currPetition.getPetitioner() != null) && (currPetition.getPetitioner().getObjectId() == player.getObjectId())) + { + petitionCount++; + } + } + + for (Petition currPetition : _completedPetitions.values()) + { + if (currPetition == null) + { + continue; + } + + if ((currPetition.getPetitioner() != null) && (currPetition.getPetitioner().getObjectId() == player.getObjectId())) + { + petitionCount++; + } + } + + return petitionCount; + } + + public boolean isPetitionInProcess() + { + for (Petition currPetition : _pendingPetitions.values()) + { + if (currPetition == null) + { + continue; + } + + if (currPetition.getState() == PetitionState.IN_PROCESS) + { + return true; + } + } + + return false; + } + + public boolean isPetitionInProcess(int petitionId) + { + return isValidPetition(petitionId) && (_pendingPetitions.get(petitionId).getState() == PetitionState.IN_PROCESS); + } + + public boolean isPlayerInConsultation(Player player) + { + if (player != null) + { + for (Petition currPetition : _pendingPetitions.values()) + { + if ((currPetition == null) || (currPetition.getState() != PetitionState.IN_PROCESS)) + { + continue; + } + + if (((currPetition.getPetitioner() != null) && (currPetition.getPetitioner().getObjectId() == player.getObjectId())) || ((currPetition.getResponder() != null) && (currPetition.getResponder().getObjectId() == player.getObjectId()))) + { + return true; + } + } + } + + return false; + } + + public boolean isPetitioningAllowed() + { + return PlayerConfig.PETITIONING_ALLOWED; + } + + public boolean isPlayerPetitionPending(Player petitioner) + { + if (petitioner != null) + { + for (Petition currPetition : _pendingPetitions.values()) + { + if (currPetition == null) + { + continue; + } + + if ((currPetition.getPetitioner() != null) && (currPetition.getPetitioner().getObjectId() == petitioner.getObjectId())) + { + return true; + } + } + } + + return false; + } + + private boolean isValidPetition(int petitionId) + { + return _pendingPetitions.containsKey(petitionId); + } + + public boolean rejectPetition(Player respondingAdmin, int petitionId) + { + if (!isValidPetition(petitionId)) + { + return false; + } + + final Petition currPetition = _pendingPetitions.get(petitionId); + if (currPetition.getResponder() != null) + { + return false; + } + + currPetition.setResponder(respondingAdmin); + return currPetition.endPetitionConsultation(PetitionState.RESPONDER_REJECT); + } + + public boolean sendActivePetitionMessage(Player player, String messageText) + { + // if (!isPlayerInConsultation(player)) + // return false; + CreatureSay cs; + for (Petition currPetition : _pendingPetitions.values()) + { + if (currPetition == null) + { + continue; + } + + if ((currPetition.getPetitioner() != null) && (currPetition.getPetitioner().getObjectId() == player.getObjectId())) + { + cs = new CreatureSay(player, ChatType.PETITION_PLAYER, player.getName(), messageText); + currPetition.addLogMessage(cs); + + currPetition.sendResponderPacket(cs); + currPetition.sendPetitionerPacket(cs); + return true; + } + + if ((currPetition.getResponder() != null) && (currPetition.getResponder().getObjectId() == player.getObjectId())) + { + cs = new CreatureSay(player, ChatType.PETITION_GM, player.getName(), messageText); + currPetition.addLogMessage(cs); + + currPetition.sendResponderPacket(cs); + currPetition.sendPetitionerPacket(cs); + return true; + } + } + + return false; + } + + public void sendPendingPetitionList(Player player) + { + final StringBuilder htmlContent = new StringBuilder(600 + (_pendingPetitions.size() * 300)); + htmlContent.append("
        Petition Menu

        "); + + if (_pendingPetitions.isEmpty()) + { + htmlContent.append(""); + } + else + { + htmlContent.append(""); + } + + boolean color = true; + int petcount = 0; + for (Petition currPetition : _pendingPetitions.values()) + { + if (currPetition == null) + { + continue; + } + + htmlContent.append(""); + color = !color; + petcount++; + if (petcount > 10) + { + htmlContent.append(""); + break; + } + } + + htmlContent.append("

        There are no currently pending petitions.
        Current Petitions:
        "); + htmlContent.append("" + currPetition.getTypeAsString() + "
        " + DATE_FORMAT.format(Instant.ofEpochMilli(currPetition.getSubmitTime()).atZone(ZoneId.systemDefault()))); + htmlContent.append("" + currPetition.getPetitioner().getName() + "
        "); + if (currPetition.getState() != PetitionState.IN_PROCESS) + { + htmlContent.append("
        "); + } + else + { + htmlContent.append("" + currPetition.getResponder().getName() + ""); + } + htmlContent.append("
        " + currPetition.getTypeAsString() + "
        There is more pending petition...
        "); + + final NpcHtmlMessage htmlMsg = new NpcHtmlMessage(); + htmlMsg.setHtml(htmlContent.toString()); + player.sendPacket(htmlMsg); + } + + public int submitPetition(Player petitioner, String petitionText, int petitionType) + { + // Create a new petition instance and add it to the list of pending petitions. + final Petition newPetition = new Petition(petitioner, petitionText, petitionType); + final int newPetitionId = newPetition.getId(); + _pendingPetitions.put(newPetitionId, newPetition); + + // Notify all GMs that a new petition has been submitted. + final String msgContent = petitioner.getName() + " has submitted a new petition."; // (ID: " + newPetitionId + ")."; + AdminData.getInstance().broadcastToGMs(new CreatureSay(petitioner, ChatType.HERO_VOICE, "Petition System", msgContent)); + return newPetitionId; + } + + public void viewPetition(Player player, int petitionId) + { + if (!player.isGM() || !isValidPetition(petitionId)) + { + return; + } + + final Petition currPetition = _pendingPetitions.get(petitionId); + final NpcHtmlMessage html = new NpcHtmlMessage(); + html.setFile(player, "data/html/admin/petition.htm"); + html.replace("%petition%", String.valueOf(currPetition.getId())); + html.replace("%time%", DATE_FORMAT.format(Instant.ofEpochMilli(currPetition.getSubmitTime()).atZone(ZoneId.systemDefault()))); + html.replace("%type%", currPetition.getTypeAsString()); + html.replace("%petitioner%", currPetition.getPetitioner().getName()); + html.replace("%online%", currPetition.getPetitioner().isOnline() ? "00FF00" : "999999"); + html.replace("%text%", currPetition.getContent()); + player.sendPacket(html); + } + + /** + * Gets the single instance of {@code PetitionManager}. + * @return single instance of {@code PetitionManager} + */ + public static PetitionManager getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final PetitionManager INSTANCE = new PetitionManager(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/PrecautionaryRestartManager.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/PrecautionaryRestartManager.java new file mode 100644 index 0000000..24498f0 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/PrecautionaryRestartManager.java @@ -0,0 +1,190 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.managers; + +import java.lang.management.ManagementFactory; +import java.util.logging.Logger; + +import javax.management.Attribute; +import javax.management.AttributeList; +import javax.management.MBeanServer; +import javax.management.ObjectName; + +import org.l2jmobius.commons.threads.ThreadPool; +import org.l2jmobius.gameserver.Shutdown; +import org.l2jmobius.gameserver.config.ServerConfig; +import org.l2jmobius.gameserver.entity.World; +import org.l2jmobius.gameserver.entity.WorldObject; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.instance.GrandBoss; +import org.l2jmobius.gameserver.entity.actor.instance.RaidBoss; +import org.l2jmobius.gameserver.mechanics.siege.Castle; + +/** + * @author Mobius + */ +public class PrecautionaryRestartManager +{ + private static final Logger LOGGER = Logger.getLogger(PrecautionaryRestartManager.class.getName()); + + private static final String SYSTEM_CPU_LOAD_VAR = "SystemCpuLoad"; + private static final String PROCESS_CPU_LOAD_VAR = "ProcessCpuLoad"; + + private static boolean _restarting = false; + + protected PrecautionaryRestartManager() + { + ThreadPool.scheduleAtFixedRate(() -> + { + if (_restarting) + { + return; + } + + if (ServerConfig.PRECAUTIONARY_RESTART_CPU && (getCpuLoad(SYSTEM_CPU_LOAD_VAR) > ServerConfig.PRECAUTIONARY_RESTART_PERCENTAGE)) + { + if (serverBizzy()) + { + return; + } + + LOGGER.info("PrecautionaryRestartManager: CPU usage over " + ServerConfig.PRECAUTIONARY_RESTART_PERCENTAGE + "%."); + LOGGER.info("PrecautionaryRestartManager: Server is using " + getCpuLoad(PROCESS_CPU_LOAD_VAR) + "%."); + World.broadcastToAllOnlinePlayers("Server will restart in 10 minutes.", false); + Shutdown.getInstance().startShutdown(null, 600, true); + } + + if (ServerConfig.PRECAUTIONARY_RESTART_MEMORY && (getProcessRamLoad() > ServerConfig.PRECAUTIONARY_RESTART_PERCENTAGE)) + { + if (serverBizzy()) + { + return; + } + + LOGGER.info("PrecautionaryRestartManager: Memory usage over " + ServerConfig.PRECAUTIONARY_RESTART_PERCENTAGE + "%."); + World.broadcastToAllOnlinePlayers("Server will restart in 10 minutes.", false); + Shutdown.getInstance().startShutdown(null, 600, true); + } + }, ServerConfig.PRECAUTIONARY_RESTART_DELAY, ServerConfig.PRECAUTIONARY_RESTART_DELAY); + } + + private static double getCpuLoad(String var) + { + try + { + final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); + final ObjectName name = ObjectName.getInstance("java.lang:type=OperatingSystem"); + final AttributeList list = mbs.getAttributes(name, new String[] + { + var + }); + + if (list.isEmpty()) + { + return 0; + } + + final Attribute att = (Attribute) list.get(0); + final Double value = (Double) att.getValue(); + if (value == -1) + { + return 0; + } + + return (value * 1000) / 10d; + } + catch (Exception e) + { + } + + return 0; + } + + private static double getProcessRamLoad() + { + final Runtime runTime = Runtime.getRuntime(); + final long totalMemory = runTime.maxMemory(); + final long usedMemory = totalMemory - ((totalMemory - runTime.totalMemory()) + runTime.freeMemory()); + return (usedMemory * 100) / totalMemory; + } + + private boolean serverBizzy() + { + for (Castle castle : CastleManager.getInstance().getCastles()) + { + if ((castle != null) && castle.getSiege().isInProgress()) + { + return true; + } + } + + for (Player player : World.getPlayers()) + { + if ((player == null) || player.isInOfflineMode()) + { + continue; + } + + if (player.isInOlympiadMode()) + { + return true; + } + + if (player.isOnEvent()) + { + return true; + } + + if (player.isInInstance()) + { + return true; + } + + final WorldObject target = player.getTarget(); + if ((target instanceof RaidBoss) || (target instanceof GrandBoss)) + { + return true; + } + } + + return false; + } + + public void restartEnabled() + { + _restarting = true; + } + + public void restartAborted() + { + _restarting = false; + } + + public static PrecautionaryRestartManager getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final PrecautionaryRestartManager INSTANCE = new PrecautionaryRestartManager(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/PremiumManager.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/PremiumManager.java new file mode 100644 index 0000000..1b03370 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/PremiumManager.java @@ -0,0 +1,238 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.managers; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; +import java.util.logging.Logger; + +import org.l2jmobius.commons.database.DatabaseFactory; +import org.l2jmobius.commons.threads.ThreadPool; +import org.l2jmobius.gameserver.config.custom.PremiumSystemConfig; +import org.l2jmobius.gameserver.entity.World; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.mechanics.events.Containers; +import org.l2jmobius.gameserver.mechanics.events.EventType; +import org.l2jmobius.gameserver.mechanics.events.ListenersContainer; +import org.l2jmobius.gameserver.mechanics.events.holders.actor.player.OnPlayerLogin; +import org.l2jmobius.gameserver.mechanics.events.holders.actor.player.OnPlayerLogout; +import org.l2jmobius.gameserver.mechanics.events.listeners.ConsumerEventListener; + +/** + * @author Mobius + */ +public class PremiumManager +{ + private static final Logger LOGGER = Logger.getLogger(PremiumManager.class.getName()); + + private static final String LOAD_SQL = "SELECT account_name,enddate FROM account_premium WHERE account_name = ?"; + private static final String UPDATE_SQL = "REPLACE INTO account_premium (account_name,enddate) VALUE (?,?)"; + private static final String DELETE_SQL = "DELETE FROM account_premium WHERE account_name = ?"; + + class PremiumExpireTask implements Runnable + { + final Player _player; + + PremiumExpireTask(Player player) + { + _player = player; + } + + @Override + public void run() + { + _player.setPremiumStatus(false); + } + } + + // Data Cache + private final Map _premiumData = new ConcurrentHashMap<>(); + + // expireTasks + private final Map> _expiretasks = new ConcurrentHashMap<>(); + + // Listeners + private final ListenersContainer _listenerContainer = Containers.Players(); + + private final Consumer _playerLoginEvent = event -> + { + final Player player = event.getPlayer(); + final String accountName = PremiumSystemConfig.ACCOUNT_WIDE_PREMIUM ? player.getAccountName() : player.getName(); + loadPremiumData(accountName); + final long now = System.currentTimeMillis(); + final long premiumExpiration = getPremiumExpiration(accountName); + player.setPremiumStatus(premiumExpiration > now); + if (player.hasPremiumStatus()) + { + startExpireTask(player, premiumExpiration - now); + } + else if (premiumExpiration > 0) + { + removePremiumStatus(accountName, false); + } + }; + + private final Consumer _playerLogoutEvent = event -> + { + stopExpireTask(event.getPlayer()); + }; + + protected PremiumManager() + { + _listenerContainer.addListener(new ConsumerEventListener(_listenerContainer, EventType.ON_PLAYER_LOGIN, _playerLoginEvent, this)); + _listenerContainer.addListener(new ConsumerEventListener(_listenerContainer, EventType.ON_PLAYER_LOGOUT, _playerLogoutEvent, this)); + } + + /** + * @param player + * @param delay + */ + private void startExpireTask(Player player, long delay) + { + _expiretasks.put(PremiumSystemConfig.ACCOUNT_WIDE_PREMIUM ? player.getAccountName().toLowerCase() : player.getName().toLowerCase(), ThreadPool.schedule(new PremiumExpireTask(player), delay)); + } + + /** + * @param player + */ + private void stopExpireTask(Player player) + { + final ScheduledFuture task = _expiretasks.remove(PremiumSystemConfig.ACCOUNT_WIDE_PREMIUM ? player.getAccountName().toLowerCase() : player.getName().toLowerCase()); + if (task != null) + { + task.cancel(false); + } + } + + private void loadPremiumData(String accountName) + { + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement stmt = con.prepareStatement(LOAD_SQL)) + { + stmt.setString(1, accountName.toLowerCase()); + try (ResultSet rset = stmt.executeQuery()) + { + while (rset.next()) + { + _premiumData.put(rset.getString(1).toLowerCase(), rset.getLong(2)); + } + } + } + catch (SQLException e) + { + LOGGER.warning("Problem with PremiumManager: " + e.getMessage()); + } + } + + public long getPremiumExpiration(String accountName) + { + return _premiumData.getOrDefault(accountName.toLowerCase(), 0L); + } + + public void addPremiumTime(String accountName, int timeValue, TimeUnit timeUnit) + { + final long addTime = timeUnit.toMillis(timeValue); + final long now = System.currentTimeMillis(); + + // New premium task at least from now. + final long oldPremiumExpiration = Math.max(now, getPremiumExpiration(accountName)); + final long newPremiumExpiration = oldPremiumExpiration + addTime; + + // UPDATE DATABASE + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement stmt = con.prepareStatement(UPDATE_SQL)) + { + stmt.setString(1, accountName.toLowerCase()); + stmt.setLong(2, newPremiumExpiration); + stmt.execute(); + } + catch (SQLException e) + { + LOGGER.warning("Problem with PremiumManager: " + e.getMessage()); + } + + // UPDATE CACHE + _premiumData.put(accountName.toLowerCase(), newPremiumExpiration); + + // UPDATE PlAYER PREMIUMSTATUS + for (Player player : World.getPlayers()) + { + if (accountName.equalsIgnoreCase(player.getAccountName())) + { + stopExpireTask(player); + startExpireTask(player, newPremiumExpiration - now); + if (!player.hasPremiumStatus()) + { + player.setPremiumStatus(true); + } + break; + } + } + } + + public void removePremiumStatus(String accountName, boolean checkOnline) + { + if (checkOnline) + { + for (Player player : World.getPlayers()) + { + if (accountName.equalsIgnoreCase(player.getAccountName()) && player.hasPremiumStatus()) + { + player.setPremiumStatus(false); + stopExpireTask(player); + break; + } + } + } + + // UPDATE CACHE + _premiumData.remove(accountName.toLowerCase()); + + // UPDATE DATABASE + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement stmt = con.prepareStatement(DELETE_SQL)) + { + stmt.setString(1, accountName.toLowerCase()); + stmt.execute(); + } + catch (SQLException e) + { + LOGGER.warning("Problem with PremiumManager: " + e.getMessage()); + } + } + + public static PremiumManager getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final PremiumManager INSTANCE = new PremiumManager(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/PunishmentManager.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/PunishmentManager.java new file mode 100644 index 0000000..72612d1 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/PunishmentManager.java @@ -0,0 +1,203 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.managers; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Logger; + +import org.l2jmobius.commons.database.DatabaseFactory; +import org.l2jmobius.commons.threads.ThreadPool; +import org.l2jmobius.gameserver.LoginServerThread; +import org.l2jmobius.gameserver.data.holders.PunishmentHolder; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.enums.player.IllegalActionPunishmentType; +import org.l2jmobius.gameserver.entity.actor.tasks.player.IllegalPlayerActionTask; +import org.l2jmobius.gameserver.mechanics.punishment.PunishmentAffect; +import org.l2jmobius.gameserver.mechanics.punishment.PunishmentTask; +import org.l2jmobius.gameserver.mechanics.punishment.PunishmentType; + +/** + * @author UnAfraid, Skache + */ +public class PunishmentManager +{ + private static final Logger LOGGER = Logger.getLogger(PunishmentManager.class.getName()); + + private final Map _tasks = new ConcurrentHashMap<>(); + + protected PunishmentManager() + { + load(); + } + + private void load() + { + // Initiate task holders. + for (PunishmentAffect affect : PunishmentAffect.values()) + { + _tasks.put(affect, new PunishmentHolder()); + } + + int initiated = 0; + int expired = 0; + + // Load punishments. + try (Connection con = DatabaseFactory.getConnection(); + Statement st = con.createStatement(); + ResultSet rset = st.executeQuery("SELECT * FROM punishments")) + { + while (rset.next()) + { + final int id = rset.getInt("id"); + final String key = rset.getString("key"); + final PunishmentAffect affect = PunishmentAffect.getByName(rset.getString("affect")); + final PunishmentType type = PunishmentType.getByName(rset.getString("type")); + final long expirationTime = rset.getLong("expiration"); + if ((type != null) && (affect != null)) + { + if ((expirationTime > 0) && (System.currentTimeMillis() > expirationTime)) // expired task. + { + expired++; + } + else + { + initiated++; + _tasks.get(affect).addPunishment(new PunishmentTask(id, key, affect, type, expirationTime, rset.getString("reason"), rset.getString("punishedBy"), true)); + } + } + } + } + catch (Exception e) + { + LOGGER.warning(getClass().getSimpleName() + ": Error while loading punishments: " + e.getMessage()); + } + + LOGGER.info(getClass().getSimpleName() + ": Loaded " + initiated + " active and " + expired + " expired punishments."); + } + + public void startPunishment(PunishmentTask task) + { + final PunishmentAffect affect = task.getAffect(); + if (task.getType() == PunishmentType.BAN) + { + switch (affect) + { + case ACCOUNT: + { + final String accountName = (String) task.getKey(); + LoginServerThread.getInstance().sendAccessLevel(accountName, -1); + break; + } + case CHARACTER: + { + final String charId = String.valueOf(task.getKey()); + changeCharAccessLevel(charId, -1); + break; + } + } + } + + _tasks.get(affect).addPunishment(task); + } + + public void stopPunishment(Object key, PunishmentAffect affect, PunishmentType type) + { + final PunishmentTask task = getPunishment(key, affect, type); + if (task == null) + { + return; + } + + if (type == PunishmentType.BAN) + { + switch (affect) + { + case ACCOUNT: + { + final String accountName = (String) task.getKey(); + LoginServerThread.getInstance().sendAccessLevel(accountName, 0); + break; + } + case CHARACTER: + { + final String charId = String.valueOf(task.getKey()); + changeCharAccessLevel(charId, 0); + break; + } + } + } + + _tasks.get(affect).stopPunishment(task); + } + + private void changeCharAccessLevel(String charId, int lvl) + { + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement("UPDATE characters SET accessLevel=? WHERE charId=?")) + { + ps.setInt(1, lvl); + ps.setString(2, charId); + ps.executeUpdate(); + } + catch (SQLException e) + { + LOGGER.warning(getClass().getSimpleName() + ": Error updating access level for character: " + charId + ". " + e.getMessage()); + } + } + + public boolean hasPunishment(Object key, PunishmentAffect affect, PunishmentType type) + { + final PunishmentHolder holder = _tasks.get(affect); + return holder.hasPunishment(String.valueOf(key), type); + } + + public long getPunishmentExpiration(Object key, PunishmentAffect affect, PunishmentType type) + { + final PunishmentTask p = getPunishment(key, affect, type); + return p != null ? p.getExpirationTime() : 0; + } + + public PunishmentTask getPunishment(Object key, PunishmentAffect affect, PunishmentType type) + { + return _tasks.get(affect).getPunishment(String.valueOf(key), type); + } + + public static void handleIllegalPlayerAction(Player actor, String message, IllegalActionPunishmentType punishment) + { + ThreadPool.schedule(new IllegalPlayerActionTask(actor, message, punishment), 5000); + } + + public static PunishmentManager getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final PunishmentManager INSTANCE = new PunishmentManager(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/RaidBossPointsManager.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/RaidBossPointsManager.java new file mode 100644 index 0000000..93b684f --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/RaidBossPointsManager.java @@ -0,0 +1,278 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.managers; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.commons.database.DatabaseFactory; +import org.l2jmobius.commons.threads.ThreadPool; +import org.l2jmobius.commons.time.TimeUtil; +import org.l2jmobius.gameserver.config.FeatureConfig; +import org.l2jmobius.gameserver.data.sql.ClanTable; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.clan.Clan; + +/** + * @author Kerberos, JIV + * @version 8/24/10 + */ +public class RaidBossPointsManager +{ + private static final Logger LOGGER = Logger.getLogger(RaidBossPointsManager.class.getName()); + + private final Map> _list = new ConcurrentHashMap<>(); + + public RaidBossPointsManager() + { + init(); + + // Start reset task at 00:10 and repeat every 24 hours. + ThreadPool.scheduleAtFixedRate(this::resetRaidPoints, TimeUtil.getNextTime(0, 10).getTimeInMillis() - System.currentTimeMillis(), 86400000); + } + + private void init() + { + try (Connection con = DatabaseFactory.getConnection(); + Statement s = con.createStatement(); + ResultSet rs = s.executeQuery("SELECT `charId`,`boss_id`,`points` FROM `character_raid_points`")) + { + while (rs.next()) + { + final int charId = rs.getInt("charId"); + final int bossId = rs.getInt("boss_id"); + final int points = rs.getInt("points"); + Map values = _list.get(charId); + if (values == null) + { + values = new HashMap<>(); + } + + values.put(bossId, points); + _list.put(charId, values); + } + + LOGGER.info(getClass().getSimpleName() + ": Loaded " + _list.size() + " Characters Raid Points."); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Could not load raid points ", e); + } + } + + private void resetRaidPoints() + { + final Calendar calendar = Calendar.getInstance(); + if (calendar.get(Calendar.DAY_OF_WEEK) != Calendar.MONDAY) + { + return; + } + + // Reward clan reputation points. + final Map rankList = RaidBossPointsManager.getInstance().getRankList(); + for (Clan clan : ClanTable.getInstance().getClans()) + { + for (Entry entry : rankList.entrySet()) + { + if ((entry.getValue() <= 100) && clan.isMember(entry.getKey())) + { + int reputation = 0; + switch (entry.getValue()) + { + case 1: + { + reputation = FeatureConfig.RAID_RANKING_1ST; + break; + } + case 2: + { + reputation = FeatureConfig.RAID_RANKING_2ND; + break; + } + case 3: + { + reputation = FeatureConfig.RAID_RANKING_3RD; + break; + } + case 4: + { + reputation = FeatureConfig.RAID_RANKING_4TH; + break; + } + case 5: + { + reputation = FeatureConfig.RAID_RANKING_5TH; + break; + } + case 6: + { + reputation = FeatureConfig.RAID_RANKING_6TH; + break; + } + case 7: + { + reputation = FeatureConfig.RAID_RANKING_7TH; + break; + } + case 8: + { + reputation = FeatureConfig.RAID_RANKING_8TH; + break; + } + case 9: + { + reputation = FeatureConfig.RAID_RANKING_9TH; + break; + } + case 10: + { + reputation = FeatureConfig.RAID_RANKING_10TH; + break; + } + default: + { + if (entry.getValue() <= 50) + { + reputation = FeatureConfig.RAID_RANKING_UP_TO_50TH; + } + else + { + reputation = FeatureConfig.RAID_RANKING_UP_TO_100TH; + } + break; + } + } + + clan.addReputationScore(reputation); + } + } + } + + cleanUp(); + LOGGER.info(getClass().getSimpleName() + ": Reset task launched."); + } + + public void updatePointsInDB(Player player, int raidId, int points) + { + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement("REPLACE INTO character_raid_points (`charId`,`boss_id`,`points`) VALUES (?,?,?)")) + { + ps.setInt(1, player.getObjectId()); + ps.setInt(2, raidId); + ps.setInt(3, points); + ps.executeUpdate(); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Couldn't update char raid points for player: " + player, e); + } + } + + public void addPoints(Player player, int bossId, int points) + { + final Map tmpPoint = _list.computeIfAbsent(player.getObjectId(), _ -> new HashMap<>()); + updatePointsInDB(player, bossId, tmpPoint.merge(bossId, points, Integer::sum)); + } + + public int getPointsByOwnerId(int ownerId) + { + final Map tmpPoint = _list.get(ownerId); + int totalPoints = 0; + if ((tmpPoint == null) || tmpPoint.isEmpty()) + { + return 0; + } + + for (int points : tmpPoint.values()) + { + totalPoints += points; + } + + return totalPoints; + } + + public Map getList(Player player) + { + return _list.get(player.getObjectId()); + } + + public void cleanUp() + { + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement statement = con.prepareStatement("DELETE from character_raid_points WHERE charId > 0")) + { + statement.executeUpdate(); + _list.clear(); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Couldn't clean raid points", e); + } + } + + public int calculateRanking(int playerObjId) + { + return getRankList().getOrDefault(playerObjId, 0); + } + + public Map getRankList() + { + final Map tmpPoints = new HashMap<>(); + for (int ownerId : _list.keySet()) + { + final int totalPoints = getPointsByOwnerId(ownerId); + if (totalPoints != 0) + { + tmpPoints.put(ownerId, totalPoints); + } + } + + final List> list = new ArrayList<>(tmpPoints.entrySet()); + list.sort(Comparator.comparingInt(Entry::getValue).reversed()); + int ranking = 1; + final Map tmpRanking = new HashMap<>(); + for (Entry entry : list) + { + tmpRanking.put(entry.getKey(), ranking++); + } + + return tmpRanking; + } + + public static RaidBossPointsManager getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final RaidBossPointsManager INSTANCE = new RaidBossPointsManager(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/RaidBossSpawnManager.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/RaidBossSpawnManager.java new file mode 100644 index 0000000..55a7bb4 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/RaidBossSpawnManager.java @@ -0,0 +1,507 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.l2jmobius.gameserver.managers; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Calendar; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ScheduledFuture; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.commons.database.DatabaseFactory; +import org.l2jmobius.commons.threads.ThreadPool; +import org.l2jmobius.commons.util.Rnd; +import org.l2jmobius.gameserver.config.NpcConfig; +import org.l2jmobius.gameserver.data.SpawnTable; +import org.l2jmobius.gameserver.entity.actor.enums.npc.RaidBossStatus; +import org.l2jmobius.gameserver.entity.actor.instance.RaidBoss; +import org.l2jmobius.gameserver.entity.spawns.Spawn; +import org.l2jmobius.gameserver.util.StatSet; + +/** + * Raid Boss spawn manager. + * @author godson + */ +public class RaidBossSpawnManager +{ + private static final Logger LOGGER = Logger.getLogger(RaidBossSpawnManager.class.getName()); + + protected static final Map _bosses = new ConcurrentHashMap<>(); + protected static final Map _spawns = new ConcurrentHashMap<>(); + protected static final Map _storedInfo = new ConcurrentHashMap<>(); + protected static final Map> _schedules = new ConcurrentHashMap<>(); + + /** + * Instantiates a new raid boss spawn manager. + */ + protected RaidBossSpawnManager() + { + load(); + } + + /** + * Load. + */ + public void load() + { + if (!_spawns.isEmpty()) + { + for (Spawn spawn : _spawns.values()) + { + deleteSpawn(spawn, false); + } + } + + _bosses.clear(); + _spawns.clear(); + _storedInfo.clear(); + _schedules.clear(); + + try (Connection con = DatabaseFactory.getConnection(); + Statement s = con.createStatement(); + ResultSet rs = s.executeQuery("SELECT * FROM raidboss_spawnlist ORDER BY boss_id")) + { + while (rs.next()) + { + final Spawn spawnDat = new Spawn(rs.getInt("boss_id")); + spawnDat.setXYZ(rs.getInt("loc_x"), rs.getInt("loc_y"), rs.getInt("loc_z")); + spawnDat.setAmount(rs.getInt("amount")); + spawnDat.setHeading(rs.getInt("heading")); + spawnDat.setRespawnDelay(rs.getInt("respawn_delay"), rs.getInt("respawn_random")); + addNewSpawn(spawnDat, rs.getLong("respawn_time"), rs.getDouble("currentHP"), rs.getDouble("currentMP"), false); + } + + LOGGER.info(getClass().getSimpleName() + ": Loaded " + _bosses.size() + " Instances"); + LOGGER.info(getClass().getSimpleName() + ": Scheduled " + _schedules.size() + " Instances"); + } + catch (SQLException e) + { + LOGGER.warning(getClass().getSimpleName() + ": Could not load raidboss_spawnlist table"); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Error while initializing RaidBossSpawnManager: " + e.getMessage(), e); + } + } + + private static class SpawnSchedule implements Runnable + { + private static final Logger LOGGER = Logger.getLogger(SpawnSchedule.class.getName()); + + private final int bossId; + + /** + * Instantiates a new spawn schedule. + * @param npcId the npc id + */ + public SpawnSchedule(int npcId) + { + bossId = npcId; + } + + @Override + public void run() + { + final RaidBoss raidboss = (RaidBoss) _spawns.get(bossId).doSpawn(); + if (raidboss != null) + { + raidboss.setRaidStatus(RaidBossStatus.ALIVE); + + final StatSet info = new StatSet(); + info.set("currentHP", raidboss.getCurrentHp()); + info.set("currentMP", raidboss.getCurrentMp()); + info.set("respawnTime", 0); + _storedInfo.put(bossId, info); + + LOGGER.info(getClass().getSimpleName() + ": Spawning Raid Boss " + raidboss.getName()); + _bosses.put(bossId, raidboss); + } + + _schedules.remove(bossId); + } + } + + /** + * Update status. + * @param boss the boss + * @param isBossDead the is boss dead + */ + public void updateStatus(RaidBoss boss, boolean isBossDead) + { + final StatSet info = _storedInfo.get(boss.getId()); + if (info == null) + { + return; + } + + if (isBossDead) + { + boss.setRaidStatus(RaidBossStatus.DEAD); + + final int respawnMinDelay = (int) (boss.getSpawn().getRespawnMinDelay() * NpcConfig.RAID_MIN_RESPAWN_MULTIPLIER); + final int respawnMaxDelay = (int) (boss.getSpawn().getRespawnMaxDelay() * NpcConfig.RAID_MAX_RESPAWN_MULTIPLIER); + final int respawnDelay = Rnd.get(respawnMinDelay, respawnMaxDelay); + final long respawnTime = System.currentTimeMillis() + respawnDelay; + info.set("currentHP", boss.getMaxHp()); + info.set("currentMP", boss.getMaxMp()); + info.set("respawnTime", respawnTime); + if (!_schedules.containsKey(boss.getId()) && ((respawnMinDelay > 0) || (respawnMaxDelay > 0))) + { + final Calendar time = Calendar.getInstance(); + time.setTimeInMillis(respawnTime); + LOGGER.info(getClass().getSimpleName() + ": Updated " + boss.getName() + " respawn time to " + time.getTime()); + _schedules.put(boss.getId(), ThreadPool.schedule(new SpawnSchedule(boss.getId()), respawnDelay)); + updateDb(); + } + } + else + { + boss.setRaidStatus(RaidBossStatus.ALIVE); + + info.set("currentHP", boss.getCurrentHp()); + info.set("currentMP", boss.getCurrentMp()); + info.set("respawnTime", 0); + } + + _storedInfo.put(boss.getId(), info); + } + + /** + * Adds the new spawn. + * @param spawnDat the spawn dat + * @param respawnTime the respawn time + * @param currentHP the current hp + * @param currentMP the current mp + * @param storeInDb the store in db + */ + public void addNewSpawn(Spawn spawnDat, long respawnTime, double currentHP, double currentMP, boolean storeInDb) + { + if ((spawnDat == null) || _spawns.containsKey(spawnDat.getId())) + { + return; + } + + final int bossId = spawnDat.getId(); + final long currentTime = System.currentTimeMillis(); + SpawnTable.getInstance().addSpawn(spawnDat); + if ((respawnTime == 0) || (currentTime > respawnTime)) + { + final RaidBoss raidboss = (RaidBoss) spawnDat.doSpawn(); + if (raidboss != null) + { + raidboss.setCurrentHp(currentHP); + raidboss.setCurrentMp(currentMP); + raidboss.setRaidStatus(RaidBossStatus.ALIVE); + + _bosses.put(bossId, raidboss); + + final StatSet info = new StatSet(); + info.set("currentHP", currentHP); + info.set("currentMP", currentMP); + info.set("respawnTime", 0); + _storedInfo.put(bossId, info); + } + } + else + { + _schedules.put(bossId, ThreadPool.schedule(new SpawnSchedule(bossId), respawnTime - currentTime)); + } + + _spawns.put(bossId, spawnDat); + + if (storeInDb) + { + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement("INSERT INTO raidboss_spawnlist (boss_id,amount,loc_x,loc_y,loc_z,heading,respawn_time,currentHp,currentMp) VALUES(?,?,?,?,?,?,?,?,?)")) + { + ps.setInt(1, spawnDat.getId()); + ps.setInt(2, spawnDat.getAmount()); + ps.setInt(3, spawnDat.getX()); + ps.setInt(4, spawnDat.getY()); + ps.setInt(5, spawnDat.getZ()); + ps.setInt(6, spawnDat.getHeading()); + ps.setLong(7, respawnTime); + ps.setDouble(8, currentHP); + ps.setDouble(9, currentMP); + ps.execute(); + } + catch (Exception e) + { + // problem with storing spawn + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Could not store raidboss #" + bossId + " in the DB:" + e.getMessage(), e); + } + } + } + + /** + * Delete spawn. + * @param spawnDat the spawn dat + * @param updateDb the update db + */ + public void deleteSpawn(Spawn spawnDat, boolean updateDb) + { + if (spawnDat == null) + { + return; + } + + final int bossId = spawnDat.getId(); + if (!_spawns.containsKey(bossId)) + { + return; + } + + SpawnTable.getInstance().removeSpawn(spawnDat); + _spawns.remove(bossId); + + if (_bosses.containsKey(bossId)) + { + _bosses.remove(bossId); + } + + if (_schedules.containsKey(bossId)) + { + _schedules.remove(bossId).cancel(true); + } + + if (_storedInfo.containsKey(bossId)) + { + _storedInfo.remove(bossId); + } + + if (updateDb) + { + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement("DELETE FROM raidboss_spawnlist WHERE boss_id=?")) + { + ps.setInt(1, bossId); + ps.execute(); + } + catch (Exception e) + { + // problem with deleting spawn + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Could not remove raidboss #" + bossId + " from DB: " + e.getMessage(), e); + } + } + } + + /** + * Update database. + */ + private void updateDb() + { + try (Connection con = DatabaseFactory.getConnection(); + PreparedStatement ps = con.prepareStatement("UPDATE raidboss_spawnlist SET respawn_time = ?, currentHP = ?, currentMP = ? WHERE boss_id = ?")) + { + for (Entry entry : _storedInfo.entrySet()) + { + final Integer bossId = entry.getKey(); + if (bossId == null) + { + continue; + } + + final RaidBoss boss = _bosses.get(bossId); + if (boss == null) + { + continue; + } + + if (boss.getRaidStatus() == RaidBossStatus.ALIVE) + { + updateStatus(boss, false); + } + + final StatSet info = entry.getValue(); + if (info == null) + { + continue; + } + + try + { + // TODO(Zoey76): Change this to use batch. + ps.setLong(1, info.getLong("respawnTime")); + ps.setDouble(2, boss.isDead() ? boss.getMaxHp() : info.getDouble("currentHP")); + ps.setDouble(3, boss.isDead() ? boss.getMaxMp() : info.getDouble("currentMP")); + ps.setInt(4, bossId); + ps.executeUpdate(); + ps.clearParameters(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Could not update raidboss_spawnlist table " + e.getMessage(), e); + } + } + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": SQL error while updating RaidBoss spawn to database: " + e.getMessage(), e); + } + } + + /** + * Gets the all raid boss status. + * @return the all raid boss status + */ + public String[] getAllRaidBossStatus() + { + final String[] msg = new String[_bosses.size()]; + int index = 0; + for (RaidBoss boss : _bosses.values()) + { + msg[index++] = boss.getName() + ": " + boss.getRaidStatus().name(); + } + + return msg; + } + + /** + * Gets the raid boss status. + * @param bossId the boss id + * @return the raid boss status + */ + public String getRaidBossStatus(int bossId) + { + String msg = "RaidBoss Status..." + System.lineSeparator(); + final RaidBoss boss = _bosses.get(bossId); + if (boss != null) + { + msg += boss.getName() + ": " + boss.getRaidStatus().name(); + } + + return msg; + } + + /** + * Gets the raid boss status id. + * @param bossId the boss id + * @return the raid boss status id + */ + public RaidBossStatus getRaidBossStatusId(int bossId) + { + final RaidBoss boss = _bosses.get(bossId); + if (boss != null) + { + return boss.getRaidStatus(); + } + else if (_schedules.containsKey(bossId)) + { + return RaidBossStatus.DEAD; + } + else + { + return RaidBossStatus.UNDEFINED; + } + } + + /** + * Notify spawn night boss. + * @param raidboss the raidboss + */ + public void notifySpawnNightBoss(RaidBoss raidboss) + { + final StatSet info = new StatSet(); + info.set("currentHP", raidboss.getCurrentHp()); + info.set("currentMP", raidboss.getCurrentMp()); + info.set("respawnTime", 0); + raidboss.setRaidStatus(RaidBossStatus.ALIVE); + + _storedInfo.put(raidboss.getId(), info); + + LOGGER.info(getClass().getSimpleName() + ": Spawning Night Raid Boss " + raidboss.getName()); + _bosses.put(raidboss.getId(), raidboss); + } + + /** + * Checks if the boss is defined. + * @param bossId the boss id + * @return {@code true} if is defined + */ + public boolean isDefined(int bossId) + { + return _spawns.containsKey(bossId); + } + + /** + * Gets the bosses. + * @return the bosses + */ + public Map getBosses() + { + return _bosses; + } + + /** + * Gets the spawns. + * @return the spawns + */ + public Map getSpawns() + { + return _spawns; + } + + /** + * Gets the stored info. + * @return the stored info + */ + public Map getStoredInfo() + { + return _storedInfo; + } + + /** + * Saves and clears the raid bosses status, including all schedules. + */ + public void cleanUp() + { + updateDb(); + + _bosses.clear(); + + for (ScheduledFuture schedule : _schedules.values()) + { + schedule.cancel(true); + } + + _schedules.clear(); + + _storedInfo.clear(); + _spawns.clear(); + } + + /** + * Gets the single instance of RaidBossSpawnManager. + * @return single instance of RaidBossSpawnManager + */ + public static RaidBossSpawnManager getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final RaidBossSpawnManager INSTANCE = new RaidBossSpawnManager(); + } +} diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/RebirthManager.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/RebirthManager.java new file mode 100644 index 0000000..35708e4 --- /dev/null +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/RebirthManager.java @@ -0,0 +1,1409 @@ +/* + * Copyright (c) 2013 L2jMobius + * + * 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. + */ +package org.l2jmobius.gameserver.managers; + +import java.io.File; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.xml.parsers.DocumentBuilderFactory; + +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import org.l2jmobius.commons.database.DatabaseFactory; +import org.l2jmobius.gameserver.config.custom.RebirthConfig; +import org.l2jmobius.gameserver.data.xml.ExperienceData; +import org.l2jmobius.gameserver.data.xml.SkillData; +import org.l2jmobius.gameserver.data.xml.SkillTreeData; +import org.l2jmobius.gameserver.entity.World; +import org.l2jmobius.gameserver.entity.actor.Player; +import org.l2jmobius.gameserver.entity.actor.enums.player.PlayerClass; +import org.l2jmobius.gameserver.entity.item.enums.ItemProcessType; +import org.l2jmobius.gameserver.entity.item.instance.Item; +import org.l2jmobius.gameserver.mechanics.skill.Skill; +import org.l2jmobius.gameserver.network.serverpackets.MagicSkillUse; +import org.l2jmobius.gameserver.network.serverpackets.NpcHtmlMessage; +import org.l2jmobius.gameserver.network.serverpackets.SocialAction; + +/** + * Manages the complete rebirth system lifecycle for players.
        + * Handles rebirth requests, skill selection, persistence, rewards, visual effects and related UI windows. + *
          + *
        • Processes rebirth validation and rebirth execution.
        • + *
        • Loads, grants, resets and persists selected rebirth skills.
        • + *
        • Builds dynamic HTML windows for rebirth menus and skill previews.
        • + *
        • Applies configured announcements, screen messages and visual effects.
        • + *
        + * @author BazookaRpm + */ +public final class RebirthManager +{ + // Logger. + private static final Logger LOGGER = Logger.getLogger(RebirthManager.class.getName()); + + // Constants. + private static final int SKILLS_PER_ROW = 3; + private static final int SKILL_ICON_PADDING_THRESHOLD = 1000; + + private static final String REBIRTH_BUSY_MESSAGE = "A rebirth operation is already in progress."; + private static final String SELECT_REBIRTH_COUNT = "SELECT rebirthCount FROM rebirth_system WHERE charId=?"; + private static final String INSERT_FIRST_REBIRTH = "INSERT INTO rebirth_system (charId, rebirthCount) VALUES (?,1)"; + private static final String UPDATE_REBIRTH_COUNT = "UPDATE rebirth_system SET rebirthCount=? WHERE charId=?"; + private static final String SELECT_SELECTED_SKILLS = "SELECT selectedSkills FROM rebirth_system WHERE charId=?"; + private static final String UPDATE_SELECTED_SKILLS = "UPDATE rebirth_system SET selectedSkills=? WHERE charId=?"; + private static final String CLASS_LIST_FILE = "data/stats/players/classList.xml"; + + private static final Map ROOT_CLASS_IDS = new ConcurrentHashMap<>(); + private static volatile boolean ROOT_CLASS_IDS_LOADED = false; + + // Operation guards. + private static final Set REBIRTH_OPERATIONS_IN_PROGRESS = ConcurrentHashMap.newKeySet(); + private static final Set REBIRTH_SKILL_OPERATIONS_IN_PROGRESS = ConcurrentHashMap.newKeySet(); + + /** + * Creates the rebirth manager singleton instance. + */ + protected RebirthManager() + { + } + + private boolean isStartingClassSkill(Player player, Skill skill) + { + if ((player == null) || (skill == null)) + { + return false; + } + + final int rootClassId = getRootClassId(player.getBaseClass()); + final PlayerClass rootClass = PlayerClass.getPlayerClass(rootClassId); + if (rootClass == null) + { + return false; + } + + return isSkillInTree(skill, SkillTreeData.getInstance().getCompleteClassSkillTree(rootClass)); + } + + private boolean isFishingSkill(Skill skill) + { + return isSkillInTree(skill, SkillTreeData.getInstance().getFishingSkillTree()); + } + + private boolean isNobleSkill(Skill skill) + { + return isSkillInTree(skill, SkillTreeData.getInstance().getNobleSkillTree()); + } + + private boolean isHeroSkill(Skill skill) + { + return isSkillInTree(skill, SkillTreeData.getInstance().getHeroSkillTree()); + } + + private boolean isClanSkill(Skill skill) + { + return isSkillInTree(skill, SkillTreeData.getInstance().getPledgeSkillTree()); + } + + private boolean isProtectedByGroup(Player player, Skill skill) + { + if ((player == null) || (skill == null) || RebirthConfig.REBIRTH_DELETE_PROTECTED_SKILL_GROUPS.isEmpty()) + { + return false; + } + + for (String group : RebirthConfig.REBIRTH_DELETE_PROTECTED_SKILL_GROUPS) + { + switch (group) + { + case "STARTING_CLASS": + { + if (isStartingClassSkill(player, skill)) + { + return true; + } + break; + } + case "FISHING": + { + if (isFishingSkill(skill)) + { + return true; + } + break; + } + case "NOBLE": + { + if (isNobleSkill(skill)) + { + return true; + } + break; + } + case "HERO": + { + if (isHeroSkill(skill)) + { + return true; + } + break; + } + case "CLAN": + { + if (isClanSkill(skill)) + { + return true; + } + break; + } + } + } + return false; + } + + private boolean isSkillInTree(Skill skill, Map tree) + { + if ((skill == null) || (tree == null) || tree.isEmpty()) + { + return false; + } + + final long skillHashCode = SkillData.getSkillHashCode(skill.getId(), skill.getLevel()); + return tree.containsKey(Integer.valueOf((int) skillHashCode)) || tree.containsKey(Long.valueOf(skillHashCode)); + } + + private int getRootClassId(int classId) + { + ensureRootClassIdsLoaded(); + + final Integer rootClassId = ROOT_CLASS_IDS.get(Integer.valueOf(classId)); + if (rootClassId != null) + { + return rootClassId.intValue(); + } + + final PlayerClass playerClass = PlayerClass.getPlayerClass(classId); + if (playerClass != null) + { + return playerClass.getRootClass().getId(); + } + return classId; + } + + private void ensureRootClassIdsLoaded() + { + if (ROOT_CLASS_IDS_LOADED) + { + return; + } + + synchronized (ROOT_CLASS_IDS) + { + if (ROOT_CLASS_IDS_LOADED) + { + return; + } + + loadRootClassIds(); + ROOT_CLASS_IDS_LOADED = true; + } + } + + private void loadRootClassIds() + { + final File file = new File(CLASS_LIST_FILE); + if (!file.exists()) + { + LOGGER.warning("Rebirth class list file not found: " + CLASS_LIST_FILE); + return; + } + + final Map parentClassIds = new HashMap<>(); + try + { + final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + final NodeList classNodes = factory.newDocumentBuilder().parse(file).getElementsByTagName("class"); + for (int i = 0; i < classNodes.getLength(); i++) + { + final Node classNode = classNodes.item(i); + final int classId = parseIntAttribute(classNode, "classId", -1); + if (classId < 0) + { + continue; + } + + parentClassIds.put(Integer.valueOf(classId), Integer.valueOf(parseIntAttribute(classNode, "parentClassId", -1))); + } + + for (Integer classId : parentClassIds.keySet()) + { + ROOT_CLASS_IDS.put(classId, Integer.valueOf(resolveRootClassId(classId.intValue(), parentClassIds))); + } + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "Failed to parse rebirth class list file: " + CLASS_LIST_FILE, e); + } + } + + private int resolveRootClassId(int classId, Map parentClassIds) + { + int currentClassId = classId; + final Set visited = new HashSet<>(); + while (visited.add(Integer.valueOf(currentClassId))) + { + final Integer parentClassId = parentClassIds.get(Integer.valueOf(currentClassId)); + if ((parentClassId == null) || (parentClassId.intValue() < 0)) + { + return currentClassId; + } + currentClassId = parentClassId.intValue(); + } + return classId; + } + + private int parseIntAttribute(Node node, String attributeName, int defaultValue) + { + if ((node == null) || (node.getAttributes() == null) || (node.getAttributes().getNamedItem(attributeName) == null)) + { + return defaultValue; + } + + try + { + return Integer.parseInt(node.getAttributes().getNamedItem(attributeName).getNodeValue()); + } + catch (NumberFormatException e) + { + LOGGER.log(Level.WARNING, "Failed to parse rebirth class attribute: attribute=" + attributeName + " value=" + node.getAttributes().getNamedItem(attributeName).getNodeValue(), e); + return defaultValue; + } + } + + /** + * Displays the rebirth skill selection window. + * @param player + * @param objectId + */ + public void displaySkillSelectionWindow(Player player, int objectId) + { + final int rebirthCount = getRebirthLevel(player); + if (rebirthCount <= 0) + { + player.sendMessage("You must perform at least one Rebirth before selecting skills."); + return; + } + + final List skillDefinitions = getSkillPool(player); + final Map skillDefinitionMap = createSkillDefinitionMap(skillDefinitions, player); + if (skillDefinitionMap.isEmpty()) + { + player.sendMessage("There are no skills configured for your class. Contact an administrator."); + return; + } + + final List selectedSkills = loadValidatedSelectedSkills(player, skillDefinitionMap); + final Set selectedSkillSet = createSkillSet(selectedSkills); + final int allowedSkills = Math.min(rebirthCount, RebirthConfig.REBIRTH_MAX_SELECTED_SKILLS); + final NpcHtmlMessage html = new NpcHtmlMessage(objectId); + final StringBuilder htmlBuilder = new StringBuilder(); + + htmlBuilder.append("
        "); + htmlBuilder.append("Select your Rebirth Skills"); + htmlBuilder.append("
        Maximum allowed: ").append(allowedSkills).append(" skill(s)"); + htmlBuilder.append("Tokens available: ").append(getItemCount(player, RebirthConfig.REBIRTH_REWARD_ITEM_ID)).append("

        "); + htmlBuilder.append(""); + + int column = 0; + for (Entry entry : skillDefinitionMap.entrySet()) + { + final String definition = entry.getValue(); + final int skillLevel = getSkillLevel(definition); + if (skillLevel <= 0) + { + LOGGER.warning("Skipping invalid rebirth skill definition in selection window: playerId=" + player.getObjectId() + " playerName=" + player.getName() + " definition=" + definition); + continue; + } + + final int skillId = entry.getKey().intValue(); + final Skill skill = SkillData.getInstance().getSkill(skillId, skillLevel); + if (skill == null) + { + LOGGER.warning("Configured rebirth skill was not found in selection window: playerId=" + player.getObjectId() + " playerName=" + player.getName() + " skillId=" + skillId + " skillLevel=" + skillLevel + " definition=" + definition); + continue; + } + + final int itemCost = getSkillCost(definition); + if (column == SKILLS_PER_ROW) + { + htmlBuilder.append(""); + column = 0; + } + + final String icon = (skillId < SKILL_ICON_PADDING_THRESHOLD) ? ("0" + skillId) : String.valueOf(skillId); + htmlBuilder.append(""); + column++; + } + + htmlBuilder.append("
        "); + htmlBuilder.append(""); + htmlBuilder.append("").append(skill.getName()).append(""); + htmlBuilder.append("Lv. ").append(skill.getLevel()).append(""); + htmlBuilder.append("Cost: ").append(itemCost).append(""); + + if (selectedSkillSet.contains(Integer.valueOf(skillId))) + { + htmlBuilder.append("Owned"); + } + else if (selectedSkills.size() >= allowedSkills) + { + htmlBuilder.append("Limit reached"); + } + else + { + htmlBuilder.append("

        "); + htmlBuilder.append("
        "); + + html.setHtml(htmlBuilder.toString()); + player.sendPacket(html); + } + + /** + * Displays the rebirth skill preview window for a specific skill. + * @param player + * @param objectId + * @param skillId + */ + public void displaySkillPreviewWindow(Player player, int objectId, int skillId) + { + final List skillDefinitions = getSkillPool(player); + final Map skillDefinitionMap = createSkillDefinitionMap(skillDefinitions, player); + final List savedSkills = loadValidatedSelectedSkills(player, skillDefinitionMap); + final Set savedSkillSet = createSkillSet(savedSkills); + final int rebirthCount = getRebirthLevel(player); + displaySkillPreviewWindow(player, objectId, skillId, skillDefinitionMap, savedSkills, savedSkillSet, rebirthCount); + } + + /** + * Displays the rebirth main window. + * @param player + * @param objectId + */ + public void displayMainWindow(Player player, int objectId) + { + final Map skillDefinitionMap = createSkillDefinitionMap(getSkillPool(player), player); + final List selectedSkills = loadValidatedSelectedSkills(player, skillDefinitionMap); + final int currentRebirthCount = getRebirthLevel(player); + displayMainWindow(player, objectId, selectedSkills, currentRebirthCount, skillDefinitionMap); + } + + /** + * Acquires and persists a rebirth skill for the player. + * @param player + * @param objectId + * @param skillId + */ + public void acquireRebirthSkill(Player player, int objectId, int skillId) + { + final int playerId = player.getObjectId(); + if (!REBIRTH_SKILL_OPERATIONS_IN_PROGRESS.add(Integer.valueOf(playerId))) + { + player.sendMessage(REBIRTH_BUSY_MESSAGE); + return; + } + + try + { + final Map skillDefinitionMap = createSkillDefinitionMap(getSkillPool(player), player); + final List savedSkills = loadValidatedSelectedSkills(player, skillDefinitionMap); + final int rebirthCount = getRebirthLevel(player); + if (rebirthCount <= 0) + { + player.sendMessage("You must perform at least one Rebirth before selecting skills."); + displayMainWindow(player, objectId, savedSkills, rebirthCount, skillDefinitionMap); + return; + } + + final Set savedSkillSet = createSkillSet(savedSkills); + if (savedSkillSet.contains(Integer.valueOf(skillId))) + { + player.sendMessage("You already own this rebirth skill."); + displayMainWindow(player, objectId, savedSkills, rebirthCount, skillDefinitionMap); + return; + } + + final int allowedSkills = Math.min(rebirthCount, RebirthConfig.REBIRTH_MAX_SELECTED_SKILLS); + if (savedSkills.size() >= allowedSkills) + { + player.sendMessage("You have reached the maximum number of skills allowed. (" + allowedSkills + ")."); + displayMainWindow(player, objectId, savedSkills, rebirthCount, skillDefinitionMap); + return; + } + + final String definition = getSkillDefinition(skillDefinitionMap, skillId); + if (definition == null) + { + player.sendMessage("This skill is not configured for your class."); + displayMainWindow(player, objectId, savedSkills, rebirthCount, skillDefinitionMap); + return; + } + + final int itemCost = getSkillCost(definition); + if (!consumeItem(player, RebirthConfig.REBIRTH_REWARD_ITEM_ID, itemCost, ItemProcessType.DESTROY, "rebirth token")) + { + displaySkillPreviewWindow(player, objectId, skillId, skillDefinitionMap, savedSkills, savedSkillSet, rebirthCount); + return; + } + + savedSkills.add(Integer.valueOf(skillId)); + saveSelectedSkills(playerId, savedSkills); + grantRebirthSkills(player, skillDefinitionMap, savedSkills); + playRebirthSelectFx(player); + player.sendMessage("Rebirth skill purchased successfully."); + displayMainWindow(player, objectId, savedSkills, rebirthCount, skillDefinitionMap); + } + finally + { + REBIRTH_SKILL_OPERATIONS_IN_PROGRESS.remove(Integer.valueOf(playerId)); + } + } + + /** + * Resets a previously acquired rebirth skill. + * @param player + * @param objectId + * @param skillId + */ + public void resetRebirthSkill(Player player, int objectId, int skillId) + { + final int playerId = player.getObjectId(); + if (!REBIRTH_SKILL_OPERATIONS_IN_PROGRESS.add(Integer.valueOf(playerId))) + { + player.sendMessage(REBIRTH_BUSY_MESSAGE); + return; + } + + try + { + final Map skillDefinitionMap = createSkillDefinitionMap(getSkillPool(player), player); + final List savedSkills = loadValidatedSelectedSkills(player, skillDefinitionMap); + final Set savedSkillSet = createSkillSet(savedSkills); + final int rebirthCount = getRebirthLevel(player); + if (!savedSkillSet.contains(Integer.valueOf(skillId))) + { + player.sendMessage("You do not own this rebirth skill."); + displayMainWindow(player, objectId, savedSkills, rebirthCount, skillDefinitionMap); + return; + } + + final String definition = getSkillDefinition(skillDefinitionMap, skillId); + if (definition == null) + { + player.sendMessage("The configured skill data is invalid."); + displayMainWindow(player, objectId, savedSkills, rebirthCount, skillDefinitionMap); + return; + } + + final int skillLevel = getSkillLevel(definition); + final int itemCost = getSkillCost(definition); + final int refundAmount = getRefundAmount(itemCost); + + savedSkills.remove(Integer.valueOf(skillId)); + saveSelectedSkills(playerId, savedSkills); + + final Skill skill = SkillData.getInstance().getSkill(skillId, skillLevel); + if (skill != null) + { + player.removeSkill(skill, true); + } + else + { + LOGGER.warning("Configured rebirth skill could not be removed because it was not found: playerId=" + playerId + " playerName=" + player.getName() + " skillId=" + skillId + " skillLevel=" + skillLevel + " definition=" + definition); + } + player.sendSkillList(); + + if (refundAmount > 0) + { + player.addItem(ItemProcessType.REFUND, RebirthConfig.REBIRTH_REWARD_ITEM_ID, refundAmount, player, true); + player.sendMessage("Refunded " + refundAmount + " rebirth token(s)."); + } + else + { + player.sendMessage("Skill removed. Refund mode " + RebirthConfig.REBIRTH_SKILL_REFUND_MODE + " returned no tokens."); + } + + displayMainWindow(player, objectId, savedSkills, rebirthCount, skillDefinitionMap); + } + finally + { + REBIRTH_SKILL_OPERATIONS_IN_PROGRESS.remove(Integer.valueOf(playerId)); + } + } + + /** + * Builds the acquired rebirth skills HTML block. + * @param player + * @param selectedSkills + * @param skillDefinitionMap + * @param showResetButton + * @return The acquired skills HTML. + */ + private String buildAcquiredSkillsHtml(Player player, List selectedSkills, Map skillDefinitionMap, boolean showResetButton) + { + if (selectedSkills.isEmpty()) + { + return "
        You don't have any skills selected yet.
        "; + } + + final StringBuilder htmlBuilder = new StringBuilder(); + htmlBuilder.append(""); + int column = 0; + for (int skillId : selectedSkills) + { + final String definition = getSkillDefinition(skillDefinitionMap, skillId); + if (definition == null) + { + LOGGER.warning("Selected rebirth skill definition was not found while building acquired skills HTML: playerId=" + player.getObjectId() + " playerName=" + player.getName() + " skillId=" + skillId); + continue; + } + + final int skillLevel = getSkillLevel(definition); + final Skill skill = SkillData.getInstance().getSkill(skillId, skillLevel); + if (skill == null) + { + LOGGER.warning("Selected rebirth skill was not found while building acquired skills HTML: playerId=" + player.getObjectId() + " playerName=" + player.getName() + " skillId=" + skillId + " skillLevel=" + skillLevel + " definition=" + definition); + continue; + } + + final int itemCost = getSkillCost(definition); + if (column == SKILLS_PER_ROW) + { + htmlBuilder.append(""); + column = 0; + } + + final String icon = (skillId < SKILL_ICON_PADDING_THRESHOLD) ? ("0" + skillId) : String.valueOf(skillId); + htmlBuilder.append(""); + column++; + } + + htmlBuilder.append("
        "); + htmlBuilder.append(""); + htmlBuilder.append("").append(skill.getName()).append(""); + htmlBuilder.append("Lv. ").append(skill.getLevel()).append(""); + htmlBuilder.append("Cost: ").append(itemCost).append(""); + + if (showResetButton) + { + htmlBuilder.append("
        "); + return htmlBuilder.toString(); + } + + /** + * Returns the configured skill pool for the player's class type. + * @param player + * @return The configured skill pool. + */ + private List getSkillPool(Player player) + { + return player.getPlayerClass().isMage() ? RebirthConfig.REBIRTH_MAGE_SKILLS : RebirthConfig.REBIRTH_FIGHTER_SKILLS; + } + + /** + * Creates a skill definition lookup map indexed by skill id. + * @param skillDefinitions + * @param player + * @return The indexed skill definition map. + */ + private Map createSkillDefinitionMap(List skillDefinitions, Player player) + { + final Map skillDefinitionMap = new HashMap<>(); + for (String definition : skillDefinitions) + { + final int skillId = getSkillId(definition); + if (skillId <= 0) + { + LOGGER.warning("Skipping invalid rebirth skill definition while building index: playerId=" + player.getObjectId() + " playerName=" + player.getName() + " definition=" + definition); + continue; + } + + final String previous = skillDefinitionMap.put(Integer.valueOf(skillId), definition); + if (previous != null) + { + LOGGER.warning("Duplicate rebirth skill definition detected while building index: playerId=" + player.getObjectId() + " playerName=" + player.getName() + " skillId=" + skillId + " previousDefinition=" + previous + " newDefinition=" + definition); + } + } + return skillDefinitionMap; + } + + /** + * Creates a skill membership set from the persisted selected skill list. + * @param skills + * @return The indexed skill set. + */ + private Set createSkillSet(List skills) + { + return new HashSet<>(skills); + } + + /** + * Loads and sanitizes selected skills against current configuration. + * @param player + * @param skillDefinitionMap + * @return The validated selected skills list. + */ + private List loadValidatedSelectedSkills(Player player, Map skillDefinitionMap) + { + final int playerId = player.getObjectId(); + final List storedSkills = getSelectedSkills(playerId); + if (storedSkills.isEmpty()) + { + return storedSkills; + } + + final List validSkills = new ArrayList<>(storedSkills.size()); + boolean changed = false; + for (int skillId : storedSkills) + { + if (skillDefinitionMap.containsKey(Integer.valueOf(skillId))) + { + validSkills.add(Integer.valueOf(skillId)); + } + else + { + changed = true; + LOGGER.warning("Removing stale rebirth skill from persisted selection: playerId=" + playerId + " playerName=" + player.getName() + " skillId=" + skillId); + } + } + + if (changed) + { + saveSelectedSkills(playerId, validSkills); + } + return validSkills; + } + + /** + * Returns the configured skill definition for a skill id. + * @param skillDefinitionMap + * @param skillId + * @return The skill definition, or {@code null} if not found. + */ + private String getSkillDefinition(Map skillDefinitionMap, int skillId) + { + return skillDefinitionMap.get(Integer.valueOf(skillId)); + } + + /** + * Extracts the skill id from a configured skill definition. + * @param definition + * @return The configured skill id, or {@code 0} if invalid. + */ + private int getSkillId(String definition) + { + final String[] parts = definition.split(","); + if (parts.length < 2) + { + return 0; + } + + try + { + return Integer.parseInt(parts[0].trim()); + } + catch (NumberFormatException e) + { + LOGGER.log(Level.WARNING, "Failed to parse rebirth skill id from definition: definition=" + definition, e); + return 0; + } + } + + /** + * Extracts the skill level from a configured skill definition. + * @param definition + * @return The configured skill level, or {@code 1} if invalid. + */ + private int getSkillLevel(String definition) + { + final String[] parts = definition.split(","); + if (parts.length < 2) + { + return 1; + } + + try + { + return Integer.parseInt(parts[1].trim()); + } + catch (NumberFormatException e) + { + LOGGER.log(Level.WARNING, "Failed to parse rebirth skill level from definition: definition=" + definition, e); + return 1; + } + } + + /** + * Extracts the skill token cost from a configured skill definition. + * @param definition + * @return The configured skill cost, or {@code 1} if invalid. + */ + private int getSkillCost(String definition) + { + final String[] parts = definition.split(","); + if (parts.length < 3) + { + return 1; + } + + try + { + return Math.max(1, Integer.parseInt(parts[2].trim())); + } + catch (NumberFormatException e) + { + LOGGER.log(Level.WARNING, "Failed to parse rebirth skill cost from definition: definition=" + definition, e); + return 1; + } + } + + /** + * Calculates the token refund amount for a skill reset. + * @param itemCost + * @return The refund amount. + */ + private int getRefundAmount(int itemCost) + { + switch (RebirthConfig.REBIRTH_SKILL_REFUND_MODE) + { + case "FULL": + return itemCost; + case "MID": + return itemCost / 2; + default: + return 0; + } + } + + /** + * Returns the player inventory count for a specific item. + * @param player + * @param itemId + * @return The available item count. + */ + private long getItemCount(Player player, int itemId) + { + final Item item = player.getInventory().getItemByItemId(itemId); + return item != null ? item.getCount() : 0; + } + + /** + * Validates and processes a rebirth request. + * @param player + */ + public void requestRebirth(Player player) + { + final int playerId = player.getObjectId(); + if (!REBIRTH_OPERATIONS_IN_PROGRESS.add(Integer.valueOf(playerId))) + { + player.sendMessage(REBIRTH_BUSY_MESSAGE); + return; + } + + try + { + if (!RebirthConfig.REBIRTH_ALLOW_REBIRTH) + { + player.sendMessage("Rebirth is disabled by configuration."); + return; + } + if (player.getLevel() < RebirthConfig.REBIRTH_MIN_LEVEL) + { + player.sendMessage("You do not meet the minimum level for Rebirth (" + RebirthConfig.REBIRTH_MIN_LEVEL + ")."); + return; + } + if (player.isAlikeDead() || player.isCastingNow() || player.isAttackingNow()) + { + player.sendMessage("You cannot request Rebirth right now."); + return; + } + + final int currentRebirthCount = getRebirthLevel(player); + if (currentRebirthCount >= RebirthConfig.REBIRTH_MAX_COUNT) + { + player.sendMessage("You are currently at your maximum rebirth count."); + return; + } + + if (!consumeItem(player, RebirthConfig.REBIRTH_ITEM_ID, RebirthConfig.REBIRTH_ITEM_AMOUNT, ItemProcessType.DESTROY, "rebirth request")) + { + return; + } + + grantRebirth(player, currentRebirthCount + 1, currentRebirthCount == 0); + } + finally + { + REBIRTH_OPERATIONS_IN_PROGRESS.remove(Integer.valueOf(playerId)); + } + } + + /** + * Applies the rebirth state change and updates the character. + * @param player + * @param newCount + * @param firstBirth + */ + private void grantRebirth(Player player, int newCount, boolean firstBirth) + { + final int playerId = player.getObjectId(); + final String playerName = player.getName(); + + try + { + // Critical persistence step. + final boolean updated = firstBirth ? insertFirst(playerId) : updateCount(playerId, newCount); + if (!updated) + { + player.sendMessage("Rebirth could not be completed due to a database error."); + LOGGER.severe("Aborting rebirth because rebirth count persistence failed: playerId=" + playerId + " playerName=" + playerName + " newCount=" + newCount + " firstBirth=" + firstBirth); + return; + } + + // Critical player mutation step. + final long currentExp = player.getExp(); + final long targetExp = ExperienceData.getInstance().getExpForLevel(1); + if (currentExp > targetExp) + { + player.getStat().setLevel((byte) 1); + player.removeExpAndSp(currentExp - targetExp, 0); + player.setCurrentHpMp(player.getMaxHp(), player.getMaxMp()); + player.setCurrentCp(player.getMaxCp()); + player.broadcastUserInfo(); + } + + player.setPlayerClass(player.getBaseClass()); + if (RebirthConfig.REBIRTH_DELETE_ALL_SKILLS) + { + for (Skill skill : player.getAllSkills()) + { + if (skill == null) + { + continue; + } + + if (RebirthConfig.REBIRTH_SKILL_DELETE_WHITELIST.contains(Integer.valueOf(skill.getId()))) + { + continue; + } + if (isProtectedByGroup(player, skill)) + { + continue; + } + + player.removeSkill(skill, true); + } + } + player.giveAvailableSkills(true, true, true); + if (RebirthConfig.REBIRTH_DELETE_RESTORE_TEMPORARY_SKILLS) + { + player.regiveTemporarySkills(); + } + player.storeMe(); + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "Failed during critical rebirth section: playerId=" + playerId + " playerName=" + playerName + " newCount=" + newCount + " firstBirth=" + firstBirth, e); + player.sendMessage("Rebirth could not be completed due to an internal error."); + return; + } + + try + { + // Post-commit section. + final Map skillDefinitionMap = createSkillDefinitionMap(getSkillPool(player), player); + final List selectedSkills = loadValidatedSelectedSkills(player, skillDefinitionMap); + grantRewardTokens(player); + if (!RebirthConfig.REBIRTH_DELETE_RESTORE_TEMPORARY_SKILLS) + { + grantRebirthSkills(player, skillDefinitionMap, selectedSkills); + } + player.sendSkillList(); + player.broadcastUserInfo(); + player.broadcastStatusUpdate(); + displayCongrats(player, newCount); + player.sendMessage("Rebirth applied successfully. You have been reborn!"); + routePostRebirthUi(player, selectedSkills, newCount, skillDefinitionMap); + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "Failed during post-commit rebirth section: playerId=" + playerId + " playerName=" + playerName + " newCount=" + newCount, e); + } + } + + /** + * Grants configured rebirth reward tokens. + * @param player + */ + private void grantRewardTokens(Player player) + { + if ((RebirthConfig.REBIRTH_REWARD_ITEM_ID <= 0) || (RebirthConfig.REBIRTH_REWARD_ITEM_AMOUNT <= 0)) + { + return; + } + + player.addItem(ItemProcessType.REWARD, RebirthConfig.REBIRTH_REWARD_ITEM_ID, RebirthConfig.REBIRTH_REWARD_ITEM_AMOUNT, player, true); + player.sendMessage("You received " + RebirthConfig.REBIRTH_REWARD_ITEM_AMOUNT + " rebirth token(s)."); + } + + /** + * Displays configured congratulations effects and announcements. + * @param player + * @param rebirthCount + */ + private void displayCongrats(Player player, int rebirthCount) + { + if (RebirthConfig.REBIRTH_FINISH_SOCIAL_ID > 0) + { + player.broadcastPacket(new SocialAction(player.getObjectId(), RebirthConfig.REBIRTH_FINISH_SOCIAL_ID)); + } + if (RebirthConfig.REBIRTH_FINISH_EFFECT_SKILL_ID > 0) + { + player.broadcastPacket(new MagicSkillUse(player, player, RebirthConfig.REBIRTH_FINISH_EFFECT_SKILL_ID, RebirthConfig.REBIRTH_FINISH_EFFECT_SKILL_LVL, 1, 0)); + } + + final String globalAnnouncement = RebirthConfig.REBIRTH_GLOBAL_ANNOUNCEMENT.replace("%player%", player.getName()).replace("%rebirth_count%", String.valueOf(rebirthCount)).replace("%max_rebirth%", String.valueOf(RebirthConfig.REBIRTH_MAX_COUNT)); + + if (RebirthConfig.REBIRTH_GLOBAL_ANNOUNCEMENT_ENABLED) + { + World.broadcastToAllOnlinePlayers(globalAnnouncement, RebirthConfig.REBIRTH_GLOBAL_ANNOUNCEMENT_CRITICAL); + } + + player.sendMessage("Congratulations " + player.getName() + "! You have been Reborn!"); + } + + /** + * Routes the player to the post-rebirth UI flow. + * @param player + * @param selectedSkills + * @param rebirthCount + * @param skillDefinitionMap + */ + private void routePostRebirthUi(Player player, List selectedSkills, int rebirthCount, Map skillDefinitionMap) + { + displayMainWindow(player, 0, selectedSkills, rebirthCount, skillDefinitionMap); + player.sendMessage("Use your rebirth tokens to purchase skills."); + } + + /** + * Plays configured skill selection visual effects. + * @param player + */ + public void playRebirthSelectFx(Player player) + { + if (RebirthConfig.REBIRTH_SELECT_SOCIAL_ID > 0) + { + player.broadcastPacket(new SocialAction(player.getObjectId(), RebirthConfig.REBIRTH_SELECT_SOCIAL_ID)); + } + if (RebirthConfig.REBIRTH_SELECT_EFFECT_SKILL_ID > 0) + { + player.broadcastPacket(new MagicSkillUse(player, player, RebirthConfig.REBIRTH_SELECT_EFFECT_SKILL_ID, RebirthConfig.REBIRTH_SELECT_EFFECT_SKILL_LVL, 1, 0)); + } + } + + /** + * Consumes an item amount from the player inventory. + * @param player + * @param itemId + * @param amount + * @param processType + * @param reason + * @return {@code true} if the item was consumed, {@code false} otherwise. + */ + private boolean consumeItem(Player player, int itemId, long amount, ItemProcessType processType, String reason) + { + if ((player == null) || (itemId <= 0) || (amount <= 0) || (amount > Integer.MAX_VALUE)) + { + LOGGER.warning("Invalid consumeItem request: player=" + player + " itemId=" + itemId + " amount=" + amount + " processType=" + processType + " reason=" + reason); + return false; + } + + final Item item = player.getInventory().getItemByItemId(itemId); + if ((item == null) || (item.getCount() < amount)) + { + final String itemName = ((item != null) && (item.getTemplate() != null)) ? item.getTemplate().getName() : ("Item " + itemId); + player.sendMessage("You need at least " + amount + " [ " + itemName + " ] for " + reason + "."); + return false; + } + + player.getInventory().destroyItem(processType, item, (int) amount, player, null); + return true; + } + + /** + * Grants all selected rebirth skills. + * @param player + */ + public void grantRebirthSkills(Player player) + { + final Map skillDefinitionMap = createSkillDefinitionMap(getSkillPool(player), player); + grantRebirthSkills(player, skillDefinitionMap, loadValidatedSelectedSkills(player, skillDefinitionMap)); + } + + /** + * Grants all selected rebirth skills. + * @param player + * @param skillDefinitionMap + * @param selectedSkills + */ + private void grantRebirthSkills(Player player, Map skillDefinitionMap, List selectedSkills) + { + if (!RebirthConfig.REBIRTH_INHERIT_SKILLS_TO_SUBCLASSES && (player.getClassIndex() > 0)) + { + return; + } + + for (int skillId : selectedSkills) + { + final String definition = getSkillDefinition(skillDefinitionMap, skillId); + if (definition == null) + { + LOGGER.warning("Skipping selected rebirth skill because definition was not found: playerId=" + player.getObjectId() + " playerName=" + player.getName() + " skillId=" + skillId); + continue; + } + + final Skill skill = SkillData.getInstance().getSkill(skillId, getSkillLevel(definition)); + if (skill != null) + { + player.addSkill(skill, true); + } + else + { + LOGGER.warning("Skipping selected rebirth skill because skill data was not found: playerId=" + player.getObjectId() + " playerName=" + player.getName() + " skillId=" + skillId + " definition=" + definition); + } + } + player.sendSkillList(); + } + + /** + * Returns the current player rebirth level. + * @param player + * @return The current rebirth count. + */ + public int getRebirthLevel(Player player) + { + return getCount(player.getObjectId()); + } + + /** + * Loads the persisted rebirth count for a character. + * @param charId + * @return The stored rebirth count. + */ + public int getCount(int charId) + { + int count = 0; + try (Connection connection = DatabaseFactory.getConnection(); + PreparedStatement statement = connection.prepareStatement(SELECT_REBIRTH_COUNT)) + { + statement.setInt(1, charId); + try (ResultSet resultSet = statement.executeQuery()) + { + if (resultSet.next()) + { + count = resultSet.getInt(1); + } + } + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "Failed to load rebirth count: charId=" + charId, e); + } + return count; + } + + /** + * Inserts the first rebirth record for a character. + * @param charId + * @return {@code true} if the insert succeeded, {@code false} otherwise. + */ + public boolean insertFirst(int charId) + { + try (Connection connection = DatabaseFactory.getConnection(); + PreparedStatement statement = connection.prepareStatement(INSERT_FIRST_REBIRTH)) + { + statement.setInt(1, charId); + return statement.executeUpdate() > 0; + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "Failed to insert first rebirth record: charId=" + charId, e); + } + return false; + } + + /** + * Updates the stored rebirth count for a character. + * @param charId + * @param newCount + * @return {@code true} if the update succeeded, {@code false} otherwise. + */ + public boolean updateCount(int charId, int newCount) + { + try (Connection connection = DatabaseFactory.getConnection(); + PreparedStatement statement = connection.prepareStatement(UPDATE_REBIRTH_COUNT)) + { + statement.setInt(1, newCount); + statement.setInt(2, charId); + return statement.executeUpdate() > 0; + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "Failed to update rebirth count: charId=" + charId + " newCount=" + newCount, e); + } + return false; + } + + /** + * Loads the persisted selected rebirth skills for a character. + * @param charId + * @return The stored selected skill ids. + */ + public List getSelectedSkills(int charId) + { + final List skills = new ArrayList<>(); + try (Connection connection = DatabaseFactory.getConnection(); + PreparedStatement statement = connection.prepareStatement(SELECT_SELECTED_SKILLS)) + { + statement.setInt(1, charId); + try (ResultSet resultSet = statement.executeQuery()) + { + if (resultSet.next()) + { + final String data = resultSet.getString(1); + if ((data != null) && !data.isEmpty()) + { + for (String skillValue : data.split(",")) + { + try + { + skills.add(Integer.valueOf(Integer.parseInt(skillValue.trim()))); + } + catch (NumberFormatException e) + { + LOGGER.log(Level.WARNING, "Failed to parse persisted rebirth skill id: charId=" + charId + " rawValue=" + skillValue + " data=" + data, e); + } + } + } + } + } + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "Failed to load selected rebirth skills: charId=" + charId, e); + } + return skills; + } + + /** + * Saves the selected rebirth skills for a character. + * @param charId + * @param skills + */ + public void saveSelectedSkills(int charId, List skills) + { + try (Connection connection = DatabaseFactory.getConnection(); + PreparedStatement statement = connection.prepareStatement(UPDATE_SELECTED_SKILLS)) + { + final StringBuilder skillBuilder = new StringBuilder(); + for (int i = 0; i < skills.size(); i++) + { + if (i > 0) + { + skillBuilder.append(','); + } + skillBuilder.append(skills.get(i)); + } + + statement.setString(1, skillBuilder.toString()); + statement.setInt(2, charId); + statement.executeUpdate(); + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, "Failed to save selected rebirth skills: charId=" + charId + " skills=" + skills, e); + } + } + + /** + * Displays the rebirth skill preview window for a specific skill. + * @param player + * @param objectId + * @param skillId + * @param skillDefinitionMap + * @param savedSkills + * @param savedSkillSet + * @param rebirthCount + */ + private void displaySkillPreviewWindow(Player player, int objectId, int skillId, Map skillDefinitionMap, List savedSkills, Set savedSkillSet, int rebirthCount) + { + final String definition = getSkillDefinition(skillDefinitionMap, skillId); + if (definition == null) + { + player.sendMessage("Invalid skill."); + return; + } + + final int targetLevel = getSkillLevel(definition); + final Skill skill = SkillData.getInstance().getSkill(skillId, targetLevel); + if (skill == null) + { + LOGGER.warning("Configured rebirth skill preview was not found: playerId=" + player.getObjectId() + " playerName=" + player.getName() + " skillId=" + skillId + " targetLevel=" + targetLevel + " definition=" + definition); + player.sendMessage("Invalid skill."); + return; + } + + final int itemCost = getSkillCost(definition); + final int allowedSkills = Math.min(rebirthCount, RebirthConfig.REBIRTH_MAX_SELECTED_SKILLS); + final long availableTokens = getItemCount(player, RebirthConfig.REBIRTH_REWARD_ITEM_ID); + final boolean alreadySelected = savedSkillSet.contains(Integer.valueOf(skillId)); + final boolean limitReached = savedSkills.size() >= allowedSkills; + final boolean hasEnoughTokens = availableTokens >= itemCost; + + final NpcHtmlMessage html = new NpcHtmlMessage(objectId); + final StringBuilder htmlBuilder = new StringBuilder(); + + htmlBuilder.append("
        "); + htmlBuilder.append("
        "); + htmlBuilder.append("Rebirth Manager
        "); + htmlBuilder.append(""); + htmlBuilder.append("
        "); + htmlBuilder.append("Your Rebirths"); + htmlBuilder.append("").append(rebirthCount).append('/').append(RebirthConfig.REBIRTH_MAX_COUNT).append(""); + htmlBuilder.append("Tokens: ").append(availableTokens).append(""); + htmlBuilder.append(""); + final String icon = (skillId < SKILL_ICON_PADDING_THRESHOLD) ? ("0" + skillId) : String.valueOf(skillId); + htmlBuilder.append(""); + htmlBuilder.append("").append(skill.getName()).append(""); + htmlBuilder.append("Lv. ").append(skill.getLevel()).append(""); + htmlBuilder.append("Cost: ").append(itemCost).append(""); + htmlBuilder.append("
        "); + htmlBuilder.append(""); + htmlBuilder.append("This purchase consumes ").append(itemCost).append(" token(s)."); + htmlBuilder.append("Refund: ").append(RebirthConfig.REBIRTH_SKILL_REFUND_MODE).append(""); + htmlBuilder.append("Rebirth Skills Obtained"); + htmlBuilder.append(buildAcquiredSkillsHtml(player, savedSkills, skillDefinitionMap, false)); + htmlBuilder.append(""); + if (!alreadySelected && !limitReached && hasEnoughTokens) + { + htmlBuilder.append("
        "); + + html.setHtml(htmlBuilder.toString()); + player.sendPacket(html); + } + + /** + * Displays the rebirth main window. + * @param player + * @param objectId + * @param selectedSkills + * @param rebirthCount + * @param skillDefinitionMap + */ + private void displayMainWindow(Player player, int objectId, List selectedSkills, int rebirthCount, Map skillDefinitionMap) + { + final long tokenCount = getItemCount(player, RebirthConfig.REBIRTH_REWARD_ITEM_ID); + + final NpcHtmlMessage html = new NpcHtmlMessage(objectId); + html.setFile(player, "data/html/default/70001_dynamic.htm"); + html.replace("%rebirth_count%", rebirthCount + "/" + RebirthConfig.REBIRTH_MAX_COUNT); + html.replace("%rebirth_icons%", buildAcquiredSkillsHtml(player, selectedSkills, skillDefinitionMap, true)); + html.replace("%next_skill_icon%", ""); + html.replace("%next_skill_name%", "Rebirth Tokens"); + html.replace("%next_skill_lvl%", String.valueOf(tokenCount)); + html.replace("%next_skill_desc%", "Refund mode: " + RebirthConfig.REBIRTH_SKILL_REFUND_MODE); + + if (rebirthCount < RebirthConfig.REBIRTH_MAX_COUNT) + { + html.replace("%rebirth_button%", "