Skip to content

Commit

Permalink
CD reset Shugo now also resets Potion and DP skill cooldowns <= 5 min
Browse files Browse the repository at this point in the history
  • Loading branch information
neon-dev committed Aug 23, 2024
1 parent 375474e commit 199b229
Showing 1 changed file with 96 additions and 91 deletions.
187 changes: 96 additions & 91 deletions game-server/data/handlers/ai/SkillCooltimeResetAI.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package ai;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

import com.aionemu.gameserver.ai.AIActions;
import com.aionemu.gameserver.ai.AIName;
Expand All @@ -13,16 +16,15 @@
import com.aionemu.gameserver.model.ChatType;
import com.aionemu.gameserver.model.TaskId;
import com.aionemu.gameserver.model.gameobjects.Creature;
import com.aionemu.gameserver.model.gameobjects.Item;
import com.aionemu.gameserver.model.gameobjects.Npc;
import com.aionemu.gameserver.model.gameobjects.player.Player;
import com.aionemu.gameserver.model.items.ItemCooldown;
import com.aionemu.gameserver.model.skill.PlayerSkillEntry;
import com.aionemu.gameserver.model.skill.PlayerSkillList;
import com.aionemu.gameserver.network.aion.serverpackets.SM_ATTACK_STATUS;
import com.aionemu.gameserver.network.aion.serverpackets.SM_MESSAGE;
import com.aionemu.gameserver.network.aion.serverpackets.SM_SKILL_COOLDOWN;
import com.aionemu.gameserver.network.aion.serverpackets.SM_SYSTEM_MESSAGE;
import com.aionemu.gameserver.skillengine.action.Action;
import com.aionemu.gameserver.skillengine.action.DpUseAction;
import com.aionemu.gameserver.model.templates.item.ItemTemplate;
import com.aionemu.gameserver.model.templates.item.ItemUseLimits;
import com.aionemu.gameserver.network.aion.serverpackets.*;
import com.aionemu.gameserver.skillengine.model.SkillTargetSlot;
import com.aionemu.gameserver.skillengine.model.SkillTemplate;
import com.aionemu.gameserver.skillengine.model.TransformType;
import com.aionemu.gameserver.utils.PacketSendUtility;
Expand All @@ -36,29 +38,22 @@
@AIName("customcdreset")
public class SkillCooltimeResetAI extends NpcAI {

private Map<Integer, Long> playersInSight = new ConcurrentHashMap<>();
private static final int PRICE = 50_000;
private static final int MAX_SKILL_COOLDOWN_TIME = 3060; // = 5min 6sec
private static final int MAX_ITEM_COOLDOWN_SECONDS = 300; // excludes items like Fine Bracing Water, Leader's Recovery Scroll, Recovery Crystal etc.

private final Map<Integer, Long> playersInSight = new ConcurrentHashMap<>();

public SkillCooltimeResetAI(Npc owner) {
super(owner);
}

private final int price = 50000; // = 50.000 Kinah
private final int maxCooldownTime = 3060; // = 5min 6sec

@Override
protected void handleSpawned() {
super.handleSpawned();
if (PvpMapService.getInstance().isOnPvPMap(getOwner())) {
getOwner().getController().addTask(TaskId.DESPAWN, ThreadPoolManager.getInstance().schedule(() -> getOwner().getController().delete(), 30000));
ThreadPoolManager.getInstance().schedule(() -> getOwner().getKnownList().forEachPlayer(p -> {
if (p.isDead() || !getOwner().canSee(p) || playersInSight.containsKey(p.getObjectId()))
return;
if (PositionUtil.isInRange(getOwner(), p, 8) && GeoService.getInstance().canSee(getOwner(), p)) {
playersInSight.put(p.getObjectId(), System.currentTimeMillis());
PacketSendUtility.sendPacket(p, new SM_MESSAGE(getOwner(),
String.format("I can heal you and reset your skill cooldowns for %,d Kinah, yang yang.", price), ChatType.NPC));
}
}), 1000);
ThreadPoolManager.getInstance().schedule(() -> getOwner().getKnownList().forEachPlayer(this::tryNotify), 1000);
}
}

Expand All @@ -71,107 +66,117 @@ else if (!PvpMapService.getInstance().isOnPvPMap(player) && player.getController
PacketSendUtility.sendPacket(player, SM_SYSTEM_MESSAGE.STR_SKILL_CANT_CAST_IN_COMBAT_STATE());
else if (player.isTransformed() && player.getTransformModel().getType() == TransformType.AVATAR)
PacketSendUtility.sendPacket(player, SM_SYSTEM_MESSAGE.STR_SKILL_CAN_NOT_ACT_WHILE_IN_ABNORMAL_STATE());
else if (player.getSkillCoolDowns() == null || player.getSkillCoolDowns().isEmpty() || checkCooldowns(player))
else if (collectResettableSkillCooldownIds(player).isEmpty() && collectResettableItemCooldownIds(player).isEmpty())
PacketSendUtility.sendPacket(player, new SM_MESSAGE(getOwner(), "Daeva has no skill cooldowns to reset, yang.", ChatType.NPC));
else
sendRequest(player);
}

@Override
public void handleCreatureMoved(Creature creature) {
if (!(creature instanceof Player))
return;

if (creature.isDead())
return;
if (creature instanceof Player player)
tryNotify(player);
}

if (!getOwner().canSee(creature))
private void tryNotify(Player player) {
if (player.isDead())
return;

if (!getOwner().getPosition().isMapRegionActive())
if (!getOwner().canSee(player))
return;

if (playersInSight.containsKey(creature.getObjectId()))
if (playersInSight.containsKey(player.getObjectId()))
return;

if (PositionUtil.isInRange(getOwner(), creature, 8) && GeoService.getInstance().canSee(getOwner(), creature)) {
playersInSight.put(creature.getObjectId(), System.currentTimeMillis());
PacketSendUtility.sendPacket((Player) creature,
new SM_MESSAGE(getOwner(), String.format("I can heal you and reset your skill cooldowns for %,d Kinah, yang yang.", price), ChatType.NPC));
}
}

private boolean checkCooldowns(Player player) {
PlayerSkillList skillList = player.getSkillList();
for (PlayerSkillEntry skill : skillList.getAllSkills()) {
SkillTemplate st = DataManager.SKILL_DATA.getSkillTemplate(skill.getSkillId());
if (st != null && st.getCooldown() <= maxCooldownTime && (player.getSkillCoolDown(st.getCooldownId()) - System.currentTimeMillis()) > 0) {
return false;
}
if (PositionUtil.isInRange(getOwner(), player, 8) && GeoService.getInstance().canSee(getOwner(), player)) {
playersInSight.put(player.getObjectId(), System.currentTimeMillis());
PacketSendUtility.sendPacket(player,
new SM_MESSAGE(getOwner(), String.format("I can heal you and reset your skill cooldowns for %,d Kinah, yang yang.", PRICE), ChatType.NPC));
}
return true;
}

private void sendRequest(final Player player) {
int distance = 5;
AIActions.addRequest(this, player, 1300765, distance, new AIRequest() {
private void sendRequest(Player player) {
AIActions.addRequest(this, player, 1300765, getObjectTemplate().getTalkDistance(), new AIRequest() {

@Override
public void acceptRequest(Creature requester, Player responder, int requestId) {
if (!PositionUtil.isInRange(requester, responder, distance))
PacketSendUtility.sendPacket(responder, SM_SYSTEM_MESSAGE.STR_WAREHOUSE_TOO_FAR_FROM_NPC());
else if (responder.getInventory().getKinah() < price)
PacketSendUtility.sendPacket(responder, SM_SYSTEM_MESSAGE.STR_MSG_NOT_ENOUGH_KINA(price));
else if (player.getLifeStats().isAboutToDie() || player.isDead())
PacketSendUtility.sendPacket(player, SM_SYSTEM_MESSAGE.STR_CANNOT_USE_IN_DEAD_STATE());
else if (player.getController().isInCombat())
PacketSendUtility.sendPacket(player, SM_SYSTEM_MESSAGE.STR_SKILL_CANT_CAST_IN_COMBAT_STATE());
if (responder.getInventory().getKinah() < PRICE)
PacketSendUtility.sendPacket(responder, SM_SYSTEM_MESSAGE.STR_MSG_NOT_ENOUGH_KINA(PRICE));
else if (responder.getLifeStats().isAboutToDie() || responder.isDead())
PacketSendUtility.sendPacket(responder, SM_SYSTEM_MESSAGE.STR_CANNOT_USE_IN_DEAD_STATE());
else if (responder.getController().isInCombat())
PacketSendUtility.sendPacket(responder, SM_SYSTEM_MESSAGE.STR_SKILL_CANT_CAST_IN_COMBAT_STATE());
else if (responder.isTransformed() && responder.getTransformModel().getType() == TransformType.AVATAR)
PacketSendUtility.sendPacket(responder, SM_SYSTEM_MESSAGE.STR_SKILL_CAN_NOT_ACT_WHILE_IN_ABNORMAL_STATE());
else {
Map<Integer, Long> resetSkillCoolDowns = new HashMap<>();

PlayerSkillList skillList = responder.getSkillList();
for (PlayerSkillEntry skill : skillList.getAllSkills()) {
SkillTemplate st = DataManager.SKILL_DATA.getSkillTemplate(skill.getSkillId());

if (st != null && st.getCooldown() <= maxCooldownTime) {
if (!st.isDeityAvatar()) {
boolean hasDpAction = false;

if (st.getActions() != null) {
for (Action ac : st.getActions().getActions()) {
if (ac instanceof DpUseAction) {
hasDpAction = true;
break;
}
}
}

if (!hasDpAction) {
if ((responder.getSkillCoolDown(st.getCooldownId()) - System.currentTimeMillis()) > 0) {
resetSkillCoolDowns.put(st.getCooldownId(), System.currentTimeMillis());
}
responder.removeSkillCoolDown(st.getCooldownId());
}
}
}
}

if (resetSkillCoolDowns.size() > 0) {
if (responder.getInventory().tryDecreaseKinah(price)) {
Set<Integer> skillCooldownIds = collectResettableSkillCooldownIds(responder);
Set<Integer> itemCooldownIds = collectResettableItemCooldownIds(responder);
if (!skillCooldownIds.isEmpty() || !itemCooldownIds.isEmpty()) {
if (responder.getInventory().tryDecreaseKinah(PRICE)) {
responder.getLifeStats().increaseHp(SM_ATTACK_STATUS.TYPE.HP, responder.getLifeStats().getMaxHp(), getOwner());
responder.getLifeStats().increaseMp(SM_ATTACK_STATUS.TYPE.HEAL_MP, responder.getLifeStats().getMaxMp(), 0, SM_ATTACK_STATUS.LOG.MPHEAL);
PacketSendUtility.sendPacket(responder, new SM_SKILL_COOLDOWN(resetSkillCoolDowns));
if (!skillCooldownIds.isEmpty()) {
skillCooldownIds.forEach(responder::removeSkillCoolDown);
PacketSendUtility.sendPacket(responder, new SM_SKILL_COOLDOWN(
skillCooldownIds.stream().collect(Collectors.toMap(cooldownId -> cooldownId, cooldownId -> System.currentTimeMillis()))));
}
if (!itemCooldownIds.isEmpty()) {
Map<Integer, ItemCooldown> dummyCds = new HashMap<>(); // 4.8 client ignores reuseTime <= currentTime, but sending old cds + useDelay 0 works
itemCooldownIds.forEach(itemCooldownId -> {
dummyCds.put(itemCooldownId, new ItemCooldown(responder.getItemReuseTime(itemCooldownId), 0));
responder.removeItemCoolDown(itemCooldownId);
});
PacketSendUtility.sendPacket(responder, new SM_ITEM_COOLDOWN(dummyCds));
}
if (PvpMapService.getInstance().isOnPvPMap(getOwner())) {
getOwner().getController().delete();
}
} else {
PacketSendUtility.sendPacket(responder, SM_SYSTEM_MESSAGE.STR_MSG_NOT_ENOUGH_KINA(price));
PacketSendUtility.sendPacket(responder, SM_SYSTEM_MESSAGE.STR_MSG_NOT_ENOUGH_KINA(PRICE));
}
}
}
}
});
}

private Set<Integer> collectResettableItemCooldownIds(Player player) {
Set<Integer> itemCooldownIds = player.getItemCoolDowns().entrySet().stream()
.filter(e -> e.getValue().getUseDelay() <= MAX_ITEM_COOLDOWN_SECONDS && e.getValue().getReuseTime() > System.currentTimeMillis())
.map(Map.Entry::getKey)
.collect(Collectors.toSet());
if (!itemCooldownIds.isEmpty())
itemCooldownIds.retainAll(collectBuffItemAndPotionCooldownIds(player));
return itemCooldownIds;
}

private Set<Integer> collectBuffItemAndPotionCooldownIds(Player player) {
Set<Integer> cooldownIds = new HashSet<>();
for (Item item : player.getInventory().getItems()) {
ItemTemplate itemTemplate = item.getItemTemplate();
ItemUseLimits useLimits = itemTemplate.getUseLimits();
if (useLimits == null || useLimits.getDelayId() == 0)
continue;
if (itemTemplate.getActions() == null || itemTemplate.getActions().getSkillUseAction() == null)
continue;
SkillTemplate skillTemplate = DataManager.SKILL_DATA.getSkillTemplate(itemTemplate.getActions().getSkillUseAction().getSkillId());
if (skillTemplate == null || skillTemplate.getTargetSlot() != SkillTargetSlot.BUFF && !skillTemplate.getStack().startsWith("ITEM_POTION_")
&& !skillTemplate.getStack().startsWith("ITEM_ARENA_POTION_"))
continue;
cooldownIds.add(useLimits.getDelayId());
}
return cooldownIds;
}

private Set<Integer> collectResettableSkillCooldownIds(Player player) {
Set<Integer> cooldownIds = new HashSet<>();
for (PlayerSkillEntry skill : player.getSkillList().getAllSkills()) {
SkillTemplate st = DataManager.SKILL_DATA.getSkillTemplate(skill.getSkillId());
if (st == null || st.getCooldown() > MAX_SKILL_COOLDOWN_TIME)
continue;
if (st.isDeityAvatar())
continue;
if (player.getSkillCoolDown(st.getCooldownId()) < System.currentTimeMillis())
continue;
cooldownIds.add(st.getCooldownId());
}
return cooldownIds;
}
}

0 comments on commit 199b229

Please sign in to comment.