l2j_mobius/trunk/java/com/l2jmobius/gameserver/model/Fishing.java
2016-06-12 01:34:09 +00:00

482 lines
14 KiB
Java

/*
* 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/>.
*/
package com.l2jmobius.gameserver.model;
import java.util.concurrent.ScheduledFuture;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.l2jmobius.Config;
import com.l2jmobius.commons.util.Rnd;
import com.l2jmobius.gameserver.GeoData;
import com.l2jmobius.gameserver.ThreadPoolManager;
import com.l2jmobius.gameserver.data.xml.impl.FishingData;
import com.l2jmobius.gameserver.enums.ShotType;
import com.l2jmobius.gameserver.instancemanager.ZoneManager;
import com.l2jmobius.gameserver.model.actor.instance.L2PcInstance;
import com.l2jmobius.gameserver.model.events.EventDispatcher;
import com.l2jmobius.gameserver.model.events.impl.character.player.OnPlayerFishing;
import com.l2jmobius.gameserver.model.interfaces.ILocational;
import com.l2jmobius.gameserver.model.itemcontainer.Inventory;
import com.l2jmobius.gameserver.model.items.instance.L2ItemInstance;
import com.l2jmobius.gameserver.model.items.type.WeaponType;
import com.l2jmobius.gameserver.model.zone.L2ZoneType;
import com.l2jmobius.gameserver.model.zone.ZoneId;
import com.l2jmobius.gameserver.model.zone.type.L2FishingZone;
import com.l2jmobius.gameserver.model.zone.type.L2WaterZone;
import com.l2jmobius.gameserver.network.SystemMessageId;
import com.l2jmobius.gameserver.network.serverpackets.ActionFailed;
import com.l2jmobius.gameserver.network.serverpackets.PlaySound;
import com.l2jmobius.gameserver.network.serverpackets.SystemMessage;
import com.l2jmobius.gameserver.network.serverpackets.fishing.ExFishingEnd;
import com.l2jmobius.gameserver.network.serverpackets.fishing.ExFishingEnd.FishingEndReason;
import com.l2jmobius.gameserver.network.serverpackets.fishing.ExFishingEnd.FishingEndType;
import com.l2jmobius.gameserver.network.serverpackets.fishing.ExFishingStart;
import com.l2jmobius.gameserver.network.serverpackets.fishing.ExUserInfoFishing;
import com.l2jmobius.gameserver.util.Util;
/**
* @author bit
*/
public class Fishing
{
protected static final Logger LOGGER = Logger.getLogger(Fishing.class.getName());
private volatile ILocational _baitLocation = new Location(0, 0, 0);
private final L2PcInstance _player;
private ScheduledFuture<?> _reelInTask;
private ScheduledFuture<?> _startFishingTask;
private boolean _isFishing = false;
public Fishing(L2PcInstance player)
{
_player = player;
}
public synchronized boolean isFishing()
{
return _isFishing;
}
public boolean isAtValidLocation()
{
// TODO: implement checking direction
return _player.isInsideZone(ZoneId.FISHING);
}
public boolean canFish()
{
return !_player.isDead() && !_player.isAlikeDead() && !_player.hasBlockActions();
}
private FishingBaitData getCurrentBaitData()
{
final L2ItemInstance bait = _player.getInventory().getPaperdollItem(Inventory.PAPERDOLL_LHAND);
return bait != null ? FishingData.getInstance().getBaitData(bait.getId()) : null;
}
private void cancelTasks()
{
if (_reelInTask != null)
{
_reelInTask.cancel(false);
_reelInTask = null;
}
if (_startFishingTask != null)
{
_startFishingTask.cancel(false);
_startFishingTask = null;
}
}
public synchronized void startFishing()
{
if (isFishing())
{
return;
}
_isFishing = true;
castLine();
}
private void castLine()
{
if (!Config.ALLOWFISHING && !_player.canOverrideCond(PcCondOverride.ZONE_CONDITIONS))
{
_player.sendMessage("Fishing is disabled.");
_player.sendPacket(ActionFailed.STATIC_PACKET);
stopFishing(FishingEndType.ERROR);
return;
}
cancelTasks();
if (!canFish())
{
if (isFishing())
{
_player.sendPacket(SystemMessageId.YOUR_ATTEMPT_AT_FISHING_HAS_BEEN_CANCELLED);
}
stopFishing(FishingEndType.ERROR);
return;
}
final int minPlayerLevel = FishingData.getInstance().getMinPlayerLevel();
if (_player.getLevel() < minPlayerLevel)
{
if (minPlayerLevel == 85)
{
_player.sendPacket(SystemMessageId.FISHING_IS_AVAILABLE_TO_CHARACTERS_LV_85_OR_ABOVE);
}
else
// In case of custom fishing level requirement set in config.
{
_player.sendPacket(SystemMessageId.YOU_DO_NOT_MEET_THE_FISHING_LEVEL_REQUIREMENTS);
}
_player.sendPacket(ActionFailed.STATIC_PACKET);
stopFishing(FishingEndType.ERROR);
return;
}
final L2ItemInstance rod = _player.getActiveWeaponInstance();
if ((rod == null) || (rod.getItemType() != WeaponType.FISHINGROD))
{
_player.sendPacket(SystemMessageId.YOU_DO_NOT_HAVE_A_FISHING_POLE_EQUIPPED);
_player.sendPacket(ActionFailed.STATIC_PACKET);
stopFishing(FishingEndType.ERROR);
return;
}
final FishingBaitData baitData = getCurrentBaitData();
if (baitData == null)
{
_player.sendPacket(SystemMessageId.YOU_MUST_PUT_BAIT_ON_YOUR_HOOK_BEFORE_YOU_CAN_FISH);
_player.sendPacket(ActionFailed.STATIC_PACKET);
stopFishing(FishingEndType.ERROR);
return;
}
if (_player.isTransformed() || _player.isInBoat())
{
_player.sendPacket(SystemMessageId.YOU_CANNOT_FISH_WHEN_TRANSFORMED_OR_WHILE_RIDING_AS_A_PASSENGER_OF_A_BOAT_IT_S_AGAINST_THE_RULES);
_player.sendPacket(ActionFailed.STATIC_PACKET);
stopFishing(FishingEndType.ERROR);
return;
}
if (_player.isInCraftMode() || _player.isInStoreMode())
{
_player.sendPacket(SystemMessageId.YOU_CANNOT_FISH_WHILE_USING_A_RECIPE_BOOK_PRIVATE_WORKSHOP_OR_PRIVATE_STORE);
_player.sendPacket(ActionFailed.STATIC_PACKET);
stopFishing(FishingEndType.ERROR);
return;
}
if (_player.isInsideZone(ZoneId.WATER) || _player.isInWater())
{
_player.sendPacket(SystemMessageId.YOU_CANNOT_FISH_WHILE_UNDER_WATER);
_player.sendPacket(ActionFailed.STATIC_PACKET);
stopFishing(FishingEndType.ERROR);
return;
}
_baitLocation = calculateBaitLocation();
if (!isAtValidLocation() || (_baitLocation == null))
{
if (isFishing())
{
_player.sendPacket(SystemMessageId.YOUR_ATTEMPT_AT_FISHING_HAS_BEEN_CANCELLED);
_player.sendPacket(ActionFailed.STATIC_PACKET);
}
else
{
_player.sendPacket(SystemMessageId.YOU_CAN_T_FISH_HERE);
_player.sendPacket(ActionFailed.STATIC_PACKET);
}
stopFishing(FishingEndType.ERROR);
return;
}
if (!_player.isChargedShot(ShotType.FISH_SOULSHOTS))
{
_player.rechargeShots(false, false, true);
}
_reelInTask = ThreadPoolManager.getInstance().scheduleGeneral(() ->
{
_player.getFishing().reelInWithReward();
_startFishingTask = ThreadPoolManager.getInstance().scheduleGeneral(() -> _player.getFishing().castLine(), Rnd.get(FishingData.getInstance().getFishingTimeWaitMin(), FishingData.getInstance().getFishingTimeWaitMax()));
}, Rnd.get(FishingData.getInstance().getFishingTimeMin(), FishingData.getInstance().getFishingTimeMax()));
_player.stopMove(null);
_player.broadcastPacket(new ExFishingStart(_player, -1, baitData.getLevel(), _baitLocation));
_player.sendPacket(new ExUserInfoFishing(_player, true, _baitLocation));
_player.sendPacket(new PlaySound("SF_P_01"));
_player.sendPacket(SystemMessageId.YOU_CAST_YOUR_LINE_AND_START_TO_FISH);
}
public void reelInWithReward()
{
// Fish may or may not eat the hook. If it does - it consumes fishing bait and fishing shot.
// Then player may or may not catch the fish. Using fishing shots increases chance to win.
final FishingBaitData baitData = getCurrentBaitData();
if (baitData == null)
{
reelIn(FishingEndReason.LOSE, false);
LOGGER.warning("Player " + _player + " is fishing with unhandled bait: " + _player.getInventory().getPaperdollItem(Inventory.PAPERDOLL_LHAND));
return;
}
double chance = baitData.getChance();
if (_player.isChargedShot(ShotType.FISH_SOULSHOTS))
{
chance *= 1.25; // +25 % chance to win
_player.setChargedShot(ShotType.FISH_SOULSHOTS, false);
}
if (Rnd.get(0, 100) <= chance)
{
reelIn(FishingEndReason.WIN, true);
}
else
{
reelIn(FishingEndReason.LOSE, true);
}
}
private void reelIn(FishingEndReason reason, boolean consumeBait)
{
if (!isFishing())
{
return;
}
cancelTasks();
try
{
final L2ItemInstance bait = _player.getInventory().getPaperdollItem(Inventory.PAPERDOLL_LHAND);
if (consumeBait)
{
if ((bait == null) || !_player.getInventory().updateItemCount(null, bait, -1, _player, null))
{
reason = FishingEndReason.LOSE; // no bait - no reward
return;
}
}
if ((reason == FishingEndReason.WIN) && (bait != null))
{
final FishingBaitData baitData = FishingData.getInstance().getBaitData(bait.getId());
final int numRewards = baitData.getRewards().size();
if (numRewards > 0)
{
// TODO: verify, totally guessed
final FishingData fishingData = FishingData.getInstance();
final int lvlModifier = _player.getLevel() * _player.getLevel();
_player.addExpAndSp(Rnd.get(fishingData.getExpRateMin(), fishingData.getExpRateMax()) * lvlModifier, Rnd.get(fishingData.getSpRateMin(), fishingData.getSpRateMax()) * lvlModifier);
final int fishId = baitData.getRewards().get(Rnd.get(0, numRewards - 1));
_player.getInventory().addItem("Fishing Reward", fishId, 1, _player, null);
final SystemMessage msg = SystemMessage.getSystemMessage(SystemMessageId.YOU_HAVE_EARNED_S1);
msg.addItemName(fishId);
_player.sendPacket(msg);
}
else
{
LOGGER.log(Level.WARNING, "Could not find fishing rewards for bait ", bait.getId());
}
}
else if (reason == FishingEndReason.LOSE)
{
_player.sendPacket(SystemMessageId.THE_BAIT_HAS_BEEN_LOST_BECAUSE_THE_FISH_GOT_AWAY);
}
if (consumeBait)
{
EventDispatcher.getInstance().notifyEventAsync(new OnPlayerFishing(_player, reason), _player);
}
}
finally
{
_player.broadcastPacket(new ExFishingEnd(_player, reason));
_player.sendPacket(new ExUserInfoFishing(_player, false));
}
}
public void stopFishing()
{
stopFishing(FishingEndType.PLAYER_STOP);
}
public synchronized void stopFishing(FishingEndType endType)
{
if (isFishing())
{
reelIn(FishingEndReason.STOP, false);
_isFishing = false;
switch (endType)
{
case PLAYER_STOP:
{
_player.sendPacket(SystemMessageId.YOU_REEL_YOUR_LINE_IN_AND_STOP_FISHING);
break;
}
case PLAYER_CANCEL:
{
_player.sendPacket(SystemMessageId.YOUR_ATTEMPT_AT_FISHING_HAS_BEEN_CANCELLED);
break;
}
}
}
}
public ILocational getBaitLocation()
{
return _baitLocation;
}
private Location calculateBaitLocation()
{
// calculate a position in front of the player with a random distance
final int distMin = FishingData.getInstance().getBaitDistanceMin();
final int distMax = FishingData.getInstance().getBaitDistanceMax();
int distance = Rnd.get(distMin, distMax);
final double angle = Util.convertHeadingToDegree(_player.getHeading());
final double radian = Math.toRadians(angle);
final double sin = Math.sin(radian);
final double cos = Math.cos(radian);
int baitX = (int) (_player.getX() + (cos * distance));
int baitY = (int) (_player.getY() + (sin * distance));
// search for fishing and water zone
L2FishingZone fishingZone = null;
L2WaterZone waterZone = null;
for (L2ZoneType zone : ZoneManager.getInstance().getZones(baitX, baitY))
{
if (zone instanceof L2FishingZone)
{
fishingZone = (L2FishingZone) zone;
}
else if (zone instanceof L2WaterZone)
{
waterZone = (L2WaterZone) zone;
}
if ((fishingZone != null) && (waterZone != null))
{
break;
}
}
int baitZ = computeBaitZ(_player, baitX, baitY, fishingZone, waterZone);
if (baitZ == Integer.MIN_VALUE)
{
for (distance = distMax; distance >= distMin; --distance)
{
baitX = (int) (_player.getX() + (cos * distance));
baitY = (int) (_player.getY() + (sin * distance));
// search for fishing and water zone again
fishingZone = null;
waterZone = null;
for (L2ZoneType zone : ZoneManager.getInstance().getZones(baitX, baitY))
{
if (zone instanceof L2FishingZone)
{
fishingZone = (L2FishingZone) zone;
}
else if (zone instanceof L2WaterZone)
{
waterZone = (L2WaterZone) zone;
}
if ((fishingZone != null) && (waterZone != null))
{
break;
}
}
baitZ = computeBaitZ(_player, baitX, baitY, fishingZone, waterZone);
if (baitZ != Integer.MIN_VALUE)
{
break;
}
}
if (baitZ == Integer.MIN_VALUE)
{
if (_player.isGM())
{
baitZ = _player.getZ();
}
else
{
_player.sendPacket(SystemMessageId.YOU_CAN_T_FISH_HERE);
return null;
}
}
}
return new Location(baitX, baitY, baitZ);
}
/**
* Computes the Z of the bait.
* @param player the player
* @param baitX the bait x
* @param baitY the bait y
* @param fishingZone the fishing zone
* @param waterZone the water zone
* @return the bait z or {@link Integer#MIN_VALUE} when you cannot fish here
*/
private static int computeBaitZ(L2PcInstance player, int baitX, int baitY, L2FishingZone fishingZone, L2WaterZone waterZone)
{
if ((fishingZone == null))
{
return Integer.MIN_VALUE;
}
if ((waterZone == null))
{
return Integer.MIN_VALUE;
}
// always use water zone, fishing zone high z is high in the air...
final int baitZ = waterZone.getWaterZ();
if (!GeoData.getInstance().canSeeTarget(player.getX(), player.getY(), player.getZ(), baitX, baitY, baitZ))
{
return Integer.MIN_VALUE;
}
if (GeoData.getInstance().hasGeo(baitX, baitY))
{
if (GeoData.getInstance().getHeight(baitX, baitY, baitZ) > baitZ)
{
return Integer.MIN_VALUE;
}
if (GeoData.getInstance().getHeight(baitX, baitY, player.getZ()) > baitZ)
{
return Integer.MIN_VALUE;
}
}
return baitZ;
}
}