Files
sweetgum-server/src/main/java/server/life/MobSkill.java
2024-05-22 08:33:44 +02:00

455 lines
16 KiB
Java

/*
This file is part of the OdinMS Maple Story Server
Copyright (C) 2008 Patrick Huy <patrick.huy@frz.cc>
Matthias Butz <matze@odinms.de>
Jan Christian Meyer <vimes@odinms.de>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation version 3 as published by
the Free Software Foundation. You may not use, modify or distribute
this program under any other version of the GNU Affero General Public
License.
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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package server.life;
import client.Character;
import client.Disease;
import client.status.MonsterStatus;
import constants.id.MapId;
import constants.id.MobId;
import constants.skills.Bishop;
import net.server.services.task.channel.OverallService;
import net.server.services.type.ChannelServices;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import server.maps.MapObject;
import server.maps.MapObjectType;
import server.maps.MapleMap;
import server.maps.Mist;
import tools.Randomizer;
import java.awt.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
/**
* @author Danny (Leifde)
*/
public class MobSkill {
private static final Logger log = LoggerFactory.getLogger(MobSkill.class);
private final MobSkillId id;
private final int mpCon;
private final int spawnEffect;
private final int hp;
private final int x;
private final int y;
private final int count;
private final long duration;
private final long cooltime;
private final float prop;
private final Point lt;
private final Point rb;
private final int limit;
private final List<Integer> toSummon;
private MobSkill(MobSkillType type, int level, int mpCon, int spawnEffect, int hp, int x, int y, int count,
long duration, long cooltime, float prop, Point lt, Point rb, int limit, List<Integer> toSummon) {
this.id = new MobSkillId(type, level);
this.mpCon = mpCon;
this.spawnEffect = spawnEffect;
this.hp = hp;
this.x = x;
this.y = y;
this.count = count;
this.duration = duration;
this.cooltime = cooltime;
this.prop = prop;
this.lt = lt;
this.rb = rb;
this.limit = limit;
this.toSummon = toSummon;
}
static class Builder {
private final MobSkillType type;
private final int level;
private int mpCon;
private int spawnEffect;
private int hp;
private int x;
private int y;
private int count;
private long duration;
private long cooltime;
private float prop;
private Point lt;
private Point rb;
private int limit;
private List<Integer> toSummon;
public Builder(MobSkillType type, int level) {
this.type = type;
this.level = level;
}
public Builder mpCon(int mpCon) {
this.mpCon = mpCon;
return this;
}
public Builder spawnEffect(int spawnEffect) {
this.spawnEffect = spawnEffect;
return this;
}
public Builder hp(int hp) {
this.hp = hp;
return this;
}
public Builder x(int x) {
this.x = x;
return this;
}
public Builder y(int y) {
this.y = y;
return this;
}
public Builder count(int count) {
this.count = count;
return this;
}
public Builder duration(long duration) {
this.duration = duration;
return this;
}
public Builder cooltime(long cooltime) {
this.cooltime = cooltime;
return this;
}
public Builder prop(float prop) {
this.prop = prop;
return this;
}
public Builder lt(Point lt) {
this.lt = lt;
return this;
}
public Builder rb(Point rb) {
this.rb = rb;
return this;
}
public Builder limit(int limit) {
this.limit = limit;
return this;
}
public Builder toSummon(List<Integer> toSummon) {
this.toSummon = Collections.unmodifiableList(toSummon);
return this;
}
public MobSkill build() {
return new MobSkill(type, level, mpCon, spawnEffect, hp, x, y, count, duration, cooltime, prop, lt, rb,
limit, toSummon);
}
}
public void applyDelayedEffect(final Character player, final Monster monster, final boolean skill, int animationTime) {
Runnable toRun = () -> {
if (monster.isAlive()) {
applyEffect(player, monster, skill, null);
}
};
OverallService service = (OverallService) monster.getMap().getChannelServer().getServiceAccess(ChannelServices.OVERALL);
service.registerOverallAction(monster.getMap().getId(), toRun, animationTime);
}
public void applyEffect(Monster monster) {
applyEffect(null, monster, false, Collections.emptyList());
}
// TODO: avoid output argument banishPlayersOutput
public void applyEffect(Character player, Monster monster, boolean skill, List<Character> banishPlayersOutput) {
// See if the MobSkill is successful before doing anything
if (!makeChanceResult()) {
return;
}
Disease disease = null;
Map<MonsterStatus, Integer> stats = new EnumMap<>(MonsterStatus.class);
List<Integer> reflection = new ArrayList<>();
switch (id.type()) {
case ATTACK_UP, ATTACK_UP_M, PAD -> stats.put(MonsterStatus.WEAPON_ATTACK_UP, x);
case MAGIC_ATTACK_UP, MAGIC_ATTACK_UP_M, MAD -> stats.put(MonsterStatus.MAGIC_ATTACK_UP, x);
case DEFENSE_UP, DEFENSE_UP_M, PDR -> stats.put(MonsterStatus.WEAPON_DEFENSE_UP, x);
case MAGIC_DEFENSE_UP, MAGIC_DEFENSE_UP_M, MDR -> stats.put(MonsterStatus.MAGIC_DEFENSE_UP, x);
case HEAL_M -> applyHealEffect(skill, monster);
case SEAL -> disease = Disease.SEAL;
case DARKNESS -> disease = Disease.DARKNESS;
case WEAKNESS -> disease = Disease.WEAKEN;
case STUN -> disease = Disease.STUN;
case CURSE -> disease = Disease.CURSE;
case POISON -> disease = Disease.POISON;
case SLOW -> disease = Disease.SLOW;
case DISPEL -> applyDispelEffect(skill, monster, player);
case SEDUCE -> disease = Disease.SEDUCE;
case BANISH -> applyBanishEffect(skill, monster, player, banishPlayersOutput);
case AREA_POISON -> spawnMonsterMist(monster);
case REVERSE_INPUT -> disease = Disease.CONFUSE;
case UNDEAD -> disease = Disease.ZOMBIFY;
case PHYSICAL_IMMUNE -> {
if (!monster.isBuffed(MonsterStatus.MAGIC_IMMUNITY)) {
stats.put(MonsterStatus.WEAPON_IMMUNITY, x);
}
}
case MAGIC_IMMUNE -> {
if (!monster.isBuffed(MonsterStatus.WEAPON_IMMUNITY)) {
stats.put(MonsterStatus.MAGIC_IMMUNITY, x);
}
}
case PHYSICAL_COUNTER -> {
stats.put(MonsterStatus.WEAPON_REFLECT, 10);
stats.put(MonsterStatus.WEAPON_IMMUNITY, 10);
reflection.add(x);
}
case MAGIC_COUNTER -> {
stats.put(MonsterStatus.MAGIC_REFLECT, 10);
stats.put(MonsterStatus.MAGIC_IMMUNITY, 10);
reflection.add(x);
}
case PHYSICAL_AND_MAGIC_COUNTER -> {
stats.put(MonsterStatus.WEAPON_REFLECT, 10);
stats.put(MonsterStatus.WEAPON_IMMUNITY, 10);
stats.put(MonsterStatus.MAGIC_REFLECT, 10);
stats.put(MonsterStatus.MAGIC_IMMUNITY, 10);
reflection.add(x);
}
case ACC -> stats.put(MonsterStatus.ACC, x);
case EVA -> stats.put(MonsterStatus.AVOID, x);
case SPEED -> stats.put(MonsterStatus.SPEED, x);
case SEAL_SKILL -> stats.put(MonsterStatus.SEAL_SKILL, x);
case SUMMON -> summonMonsters(monster);
}
if (stats.size() > 0) {
applyMonsterBuffs(stats, skill, monster, reflection);
}
if (disease != null) {
applyDisease(disease, skill, monster, player);
}
}
private void applyHealEffect(boolean skill, Monster monster) {
if (lt != null && rb != null && skill) {
List<MapObject> objects = getObjectsInRange(monster, MapObjectType.MONSTER);
final int hps = (getX() / 1000) * (int) (950 + 1050 * Math.random());
for (MapObject mons : objects) {
((Monster) mons).heal(hps, getY());
}
} else {
monster.heal(getX(), getY());
}
}
private void applyDispelEffect(boolean skill, Monster monster, Character player) {
if (lt != null && rb != null && skill) {
getPlayersInRange(monster).forEach(Character::dispel);
} else {
player.dispel();
}
}
private void applyBanishEffect(boolean skill, Monster monster, Character player,
List<Character> banishPlayersOutput) {
if (lt != null && rb != null && skill) {
banishPlayersOutput.addAll(getPlayersInRange(monster));
} else {
banishPlayersOutput.add(player);
}
}
private void spawnMonsterMist(Monster monster) {
Rectangle mistArea = calculateBoundingBox(monster.getPosition());
var mist = new Mist(mistArea, monster, this);
int mistDuration = x * 100;
monster.getMap().spawnMist(mist, mistDuration, false, false, false);
}
private void summonMonsters(Monster monster) {
int skillLimit = this.limit;
MapleMap map = monster.getMap();
if (MapId.isDojo(map.getId())) { // spawns in dojo should be unlimited
skillLimit = Integer.MAX_VALUE;
}
if (map.getSpawnedMonstersOnMap() < 80) {
List<Integer> summons = new ArrayList<>(toSummon);
int summonLimit = monster.countAvailableMobSummons(summons.size(), skillLimit);
if (summonLimit >= 1) {
boolean bossRushMap = MapId.isBossRush(map.getId());
Collections.shuffle(summons);
for (Integer mobId : summons.subList(0, summonLimit)) {
Monster toSpawn = LifeFactory.getMonster(mobId);
if (toSpawn != null) {
if (bossRushMap) {
toSpawn.disableDrops(); // no littering on BRPQ pls
}
toSpawn.setPosition(monster.getPosition());
int ypos, xpos;
xpos = (int) monster.getPosition().getX();
ypos = (int) monster.getPosition().getY();
switch (mobId) {
case MobId.HIGH_DARKSTAR: // Pap bomb high
toSpawn.setFh((int) Math.ceil(Math.random() * 19.0));
ypos = -590;
break;
case MobId.LOW_DARKSTAR: // Pap bomb
xpos = (int) (monster.getPosition().getX() + Randomizer.nextInt(1000) - 500);
if (ypos != -590) {
ypos = (int) monster.getPosition().getY();
}
break;
case MobId.BLOODY_BOOM: //Pianus bomb
if (Math.ceil(Math.random() * 5) == 1) {
ypos = 78;
xpos = Randomizer.nextInt(5) + (Randomizer.nextInt(2) == 1 ? 180 : 0);
} else {
xpos = (int) (monster.getPosition().getX() + Randomizer.nextInt(1000) - 500);
}
break;
}
switch (map.getId()) {
case MapId.ORIGIN_OF_CLOCKTOWER: //Pap map
if (xpos < -890) {
xpos = (int) (Math.ceil(Math.random() * 150) - 890);
} else if (xpos > 230) {
xpos = (int) (230 - Math.ceil(Math.random() * 150));
}
break;
case MapId.CAVE_OF_PIANUS: // Pianus map
if (xpos < -239) {
xpos = (int) (Math.ceil(Math.random() * 150) - 239);
} else if (xpos > 371) {
xpos = (int) (371 - Math.ceil(Math.random() * 150));
}
break;
}
toSpawn.setPosition(new Point(xpos, ypos));
if (toSpawn.getId() == MobId.LOW_DARKSTAR) {
map.spawnFakeMonster(toSpawn);
} else {
map.spawnMonsterWithEffect(toSpawn, spawnEffect, toSpawn.getPosition());
}
monster.addSummonedMob(toSpawn);
}
}
}
}
}
private void applyMonsterBuffs(Map<MonsterStatus, Integer> stats, boolean skill, Monster monster, List<Integer> reflection) {
if (lt != null && rb != null && skill) {
for (MapObject mons : getObjectsInRange(monster, MapObjectType.MONSTER)) {
((Monster) mons).applyMonsterBuff(stats, getX(), getDuration(), this, reflection);
}
} else {
monster.applyMonsterBuff(stats, getX(), getDuration(), this, reflection);
}
}
private void applyDisease(Disease disease, boolean skill, Monster monster, Character player) {
if (lt != null && rb != null && skill) {
int i = 0;
for (Character character : getPlayersInRange(monster)) {
if (!character.hasActiveBuff(Bishop.HOLY_SHIELD)) {
if (disease.equals(Disease.SEDUCE)) {
if (i < count) {
character.giveDebuff(Disease.SEDUCE, this);
i++;
}
} else {
character.giveDebuff(disease, this);
}
}
}
} else {
player.giveDebuff(disease, this);
}
}
private List<Character> getPlayersInRange(Monster monster) {
return monster.getMap().getPlayersInRange(calculateBoundingBox(monster.getPosition()));
}
public MobSkillId getId() {
return id;
}
public MobSkillType getType() {
return id.type();
}
public int getMpCon() {
return mpCon;
}
public int getHP() {
return hp;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public long getDuration() {
return duration;
}
public long getCoolTime() {
return cooltime;
}
public boolean makeChanceResult() {
return prop == 1.0 || Math.random() < prop;
}
private Rectangle calculateBoundingBox(Point posFrom) {
Point mylt = new Point(lt.x + posFrom.x, lt.y + posFrom.y);
Point myrb = new Point(rb.x + posFrom.x, rb.y + posFrom.y);
Rectangle bounds = new Rectangle(mylt.x, mylt.y, myrb.x - mylt.x, myrb.y - mylt.y);
return bounds;
}
private List<MapObject> getObjectsInRange(Monster monster, MapObjectType objectType) {
return monster.getMap().getMapObjectsInBox(calculateBoundingBox(monster.getPosition()), Collections.singletonList(objectType));
}
}