package javastory.channel.server; import java.awt.Point; import java.awt.Rectangle; import java.io.Serializable; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.ScheduledFuture; import javastory.channel.ChannelCharacter; import javastory.channel.ChannelServer; import javastory.channel.client.ActivePlayerStats; import javastory.channel.client.BuffStat; import javastory.channel.client.Disease; import javastory.channel.client.ISkill; import javastory.channel.client.MonsterStatus; import javastory.channel.client.MonsterStatusEffect; import javastory.channel.life.Monster; import javastory.channel.maps.Door; import javastory.channel.maps.GameMap; import javastory.channel.maps.GameMapObject; import javastory.channel.maps.GameMapObjectType; import javastory.channel.maps.Mist; import javastory.channel.maps.Summon; import javastory.channel.maps.SummonMovementType; import javastory.game.GameConstants; import javastory.game.Inventory; import javastory.game.Item; import javastory.game.Stat; import javastory.game.StatValue; import javastory.game.data.SkillInfoProvider; import javastory.server.BuffStatValue; import javastory.server.TimerManager; import javastory.tools.Randomizer; import javastory.tools.packets.ChannelPackets; import javastory.world.core.PlayerCooldownValueHolder; import javastory.wz.WzData; import javastory.wz.WzDataTool; import com.google.common.collect.Lists; import com.google.common.collect.Maps; public class StatEffect implements Serializable { private static final long serialVersionUID = 9179541993413738569L; private byte mastery, mhpR, mmpR, mobCount, attackCount, bulletCount; private short hp, mp, watk, matk, wdef, mdef, acc, avoid, hands, speed, jump, mpCon, hpCon, damage, prop; private double hpR, mpR; private int duration, sourceid, moveTo, x, y, z, itemCon, itemConNo, bulletConsume, moneyCon, cooldown, morphId = 0, expinc; private boolean overTime, skill; private List<BuffStatValue> statups; private Map<MonsterStatus, Integer> monsterStatus; private Point lt, rb; // private List<Pair<Integer, Integer>> randomMorph; private List<Disease> cureDebuffs; public static StatEffect loadSkillEffectFromData(final WzData source, final int skillid, final boolean overtime) { return loadFromData(source, skillid, true, overtime); } public static StatEffect loadItemEffectFromData(final WzData source, final int itemid) { return loadFromData(source, itemid, false, false); } private static void addBuffStatPairToListIfNotZero(final List<BuffStatValue> list, final BuffStat buffstat, final Integer val) { if (val.intValue() != 0) { list.add(new BuffStatValue(buffstat, val)); } } private static StatEffect loadFromData(final WzData source, final int sourceid, final boolean skill, final boolean overTime) { final StatEffect ret = new StatEffect(); ret.duration = WzDataTool.getIntConvert("time", source, -1); ret.hp = (short) WzDataTool.getInt("hp", source, 0); ret.hpR = WzDataTool.getInt("hpR", source, 0) / 100.0; ret.mp = (short) WzDataTool.getInt("mp", source, 0); ret.mpR = WzDataTool.getInt("mpR", source, 0) / 100.0; ret.mhpR = (byte) WzDataTool.getInt("mhpR", source, 0); ret.mmpR = (byte) WzDataTool.getInt("mmpR", source, 0); ret.mpCon = (short) WzDataTool.getInt("mpCon", source, 0); ret.hpCon = (short) WzDataTool.getInt("hpCon", source, 0); ret.prop = (short) WzDataTool.getInt("prop", source, 100); ret.cooldown = WzDataTool.getInt("cooltime", source, 0); ret.expinc = WzDataTool.getInt("expinc", source, 0); ret.morphId = WzDataTool.getInt("morph", source, 0); ret.mobCount = (byte) WzDataTool.getInt("mobCount", source, 1); if (skill) { switch (sourceid) { case 1100002: case 1100003: case 1200002: case 1200003: case 1300002: case 1300003: case 3100001: case 3200001: case 11101002: case 13101002: ret.mobCount = 6; break; } } /* final MapleData randMorph = source.getChildByPath("morphRandom"); if (randMorph != null) { for (MapleData data : randMorph.getChildren()) { ret.randomMorph.add(new Pair( MapleDataTool.getInt("morph", data, 0), MapleDataTool.getIntConvert("prop", data, 0))); } }*/ ret.sourceid = sourceid; ret.skill = skill; if (!ret.skill && ret.duration > -1) { ret.overTime = true; } else { ret.duration *= 1000; // items have their times stored in ms, of course ret.overTime = overTime; } final ArrayList<BuffStatValue> statups = Lists.newArrayList(); ret.mastery = (byte) WzDataTool.getInt("mastery", source, 0); ret.watk = (short) WzDataTool.getInt("pad", source, 0); ret.wdef = (short) WzDataTool.getInt("pdd", source, 0); ret.matk = (short) WzDataTool.getInt("mad", source, 0); ret.mdef = (short) WzDataTool.getInt("mdd", source, 0); ret.acc = (short) WzDataTool.getIntConvert("acc", source, 0); ret.avoid = (short) WzDataTool.getInt("eva", source, 0); ret.speed = (short) WzDataTool.getInt("speed", source, 0); ret.jump = (short) WzDataTool.getInt("jump", source, 0); final List<Disease> cure = Lists.newArrayListWithCapacity(5); if (WzDataTool.getInt("poison", source, 0) > 0) { cure.add(Disease.POISON); } if (WzDataTool.getInt("seal", source, 0) > 0) { cure.add(Disease.SEAL); } if (WzDataTool.getInt("darkness", source, 0) > 0) { cure.add(Disease.DARKNESS); } if (WzDataTool.getInt("weakness", source, 0) > 0) { cure.add(Disease.WEAKEN); } if (WzDataTool.getInt("curse", source, 0) > 0) { cure.add(Disease.CURSE); } ret.cureDebuffs = cure; if (ret.overTime && ret.getSummonMovementType() == null) { addBuffStatPairToListIfNotZero(statups, BuffStat.WATK, Integer.valueOf(ret.watk)); addBuffStatPairToListIfNotZero(statups, BuffStat.WDEF, Integer.valueOf(ret.wdef)); addBuffStatPairToListIfNotZero(statups, BuffStat.MATK, Integer.valueOf(ret.matk)); addBuffStatPairToListIfNotZero(statups, BuffStat.MDEF, Integer.valueOf(ret.mdef)); addBuffStatPairToListIfNotZero(statups, BuffStat.ACC, Integer.valueOf(ret.acc)); addBuffStatPairToListIfNotZero(statups, BuffStat.AVOID, Integer.valueOf(ret.avoid)); addBuffStatPairToListIfNotZero(statups, BuffStat.SPEED, Integer.valueOf(ret.speed)); addBuffStatPairToListIfNotZero(statups, BuffStat.JUMP, Integer.valueOf(ret.jump)); addBuffStatPairToListIfNotZero(statups, BuffStat.MAXHP, (int) ret.mhpR); addBuffStatPairToListIfNotZero(statups, BuffStat.MAXMP, (int) ret.mmpR); // addBuffStatPairToListIfNotZero(statups, BuffStat.EXPRATE, Integer.valueOf(2)); // EXP } final WzData ltd = source.getChildByPath("lt"); if (ltd != null) { ret.lt = (Point) ltd.getData(); ret.rb = (Point) source.getChildByPath("rb").getData(); } ret.x = WzDataTool.getInt("x", source, 0); ret.y = WzDataTool.getInt("y", source, 0); ret.z = WzDataTool.getInt("z", source, 0); ret.damage = (short) WzDataTool.getIntConvert("damage", source, 100); ret.attackCount = (byte) WzDataTool.getIntConvert("attackCount", source, 1); ret.bulletCount = (byte) WzDataTool.getIntConvert("bulletCount", source, 1); ret.bulletConsume = WzDataTool.getIntConvert("bulletConsume", source, 0); ret.moneyCon = WzDataTool.getIntConvert("moneyCon", source, 0); ret.itemCon = WzDataTool.getInt("itemCon", source, 0); ret.itemConNo = WzDataTool.getInt("itemConNo", source, 0); ret.moveTo = WzDataTool.getInt("moveTo", source, -1); final Map<MonsterStatus, Integer> monsterStatus = Maps.newEnumMap(MonsterStatus.class); if (skill) { // hack because we can't get from the datafile... switch (sourceid) { case 2001002: // magic guard case 12001001: case 22111001: statups.add(new BuffStatValue(BuffStat.MAGIC_GUARD, ret.x)); break; case 2301003: // invincible statups.add(new BuffStatValue(BuffStat.INVINCIBLE, ret.x)); break; case 9001004: // hide ret.duration = 60 * 120 * 1000; ret.overTime = true; statups.add(new BuffStatValue(BuffStat.DARKSIGHT, ret.x)); break; case 13101006: // Wind Walk case 4001003: // darksight case 14001003: // cygnus ds case 4330001: statups.add(new BuffStatValue(BuffStat.DARKSIGHT, ret.x)); break; case 4211003: // pickpocket statups.add(new BuffStatValue(BuffStat.PICKPOCKET, ret.x)); break; case 4211005: // mesoguard statups.add(new BuffStatValue(BuffStat.MESOGUARD, ret.x)); break; case 4111001: // mesoup statups.add(new BuffStatValue(BuffStat.MESOUP, ret.x)); break; case 4111002: // shadowpartner case 14111000: // cygnus statups.add(new BuffStatValue(BuffStat.SHADOWPARTNER, ret.x)); break; case 11101002: // All Final attack case 13101002: statups.add(new BuffStatValue(BuffStat.FINALATTACK, 1)); break; case 3101004: // soul arrow case 3201004: case 2311002: // mystic door - hacked buff icon case 13101003: statups.add(new BuffStatValue(BuffStat.SOULARROW, ret.x)); break; case 1211006: // wk charges case 1211003: case 1211004: case 1211005: case 1211008: case 1211007: case 1221003: case 1221004: case 11111007: case 15101006: case 21111005: statups.add(new BuffStatValue(BuffStat.WK_CHARGE, ret.x)); break; case 12101005: case 22121001: // Elemental Reset statups.add(new BuffStatValue(BuffStat.ELEMENT_RESET, ret.x)); break; case 5110001: // Energy Charge case 15100004: statups.add(new BuffStatValue(BuffStat.ENERGY_CHARGE, 1)); break; case 1101005: // booster case 1101004: case 1201005: case 1201004: case 1301005: case 1301004: case 3101002: case 3201002: case 4101003: case 4201002: case 2111005: // spell booster, do these work the same? case 2211005: case 5101006: case 5201003: case 11101001: case 12101004: case 13101001: case 14101002: case 15101002: case 21001003: // Aran - Pole Arm Booster case 22141002: // Magic Booster case 4301002: statups.add(new BuffStatValue(BuffStat.BOOSTER, ret.x)); break; //case 5121009: //case 15111005: // statups.add(new Pair<MapleBuffStat, Integer>(BuffStat.SPEED_INFUSION, ret.x)); // break; case 4321000: //tornado spin uses same buffstats ret.duration = 1000; statups.add(new BuffStatValue(BuffStat.DASH_SPEED, 100 + ret.x)); statups.add(new BuffStatValue(BuffStat.DASH_JUMP, ret.y)); //always 0 but its there break; case 5001005: // Dash case 15001003: statups.add(new BuffStatValue(BuffStat.DASH_SPEED, ret.x)); statups.add(new BuffStatValue(BuffStat.DASH_JUMP, ret.y)); break; case 1101007: // pguard case 1201007: statups.add(new BuffStatValue(BuffStat.POWERGUARD, ret.x)); break; case 1301007: // hyper body case 9001008: statups.add(new BuffStatValue(BuffStat.MAXHP, ret.x)); statups.add(new BuffStatValue(BuffStat.MAXMP, ret.y)); break; case 1001: // recovery statups.add(new BuffStatValue(BuffStat.RECOVERY, ret.x)); break; case 1111002: // combo case 11111001: // combo statups.add(new BuffStatValue(BuffStat.COMBO, 1)); break; case 5211006: // Homing Beacon case 5220011: // Bullseye case 22151002: //killer wings ret.duration = 60 * 120000; statups.add(new BuffStatValue(BuffStat.HOMING_BEACON, ret.x)); break; case 1011: // Berserk fury case 10001011: case 20001011: case 20011011: statups.add(new BuffStatValue(BuffStat.BERSERK_FURY, 1)); break; case 1010: case 10001010:// Invincible Barrier case 20001010: case 20011010: statups.add(new BuffStatValue(BuffStat.DIVINE_BODY, 1)); break; case 1311006: //dragon roar ret.hpR = -ret.x / 100.0; statups.add(new BuffStatValue(BuffStat.DRAGON_ROAR, ret.y)); break; case 1311008: // dragon blood statups.add(new BuffStatValue(BuffStat.DRAGONBLOOD, ret.x)); break; case 4341007: statups.add(new BuffStatValue(BuffStat.THORNS, ret.x << 8 | ret.y)); break; case 4341002: ret.duration = 60 * 1000; ret.overTime = true; ret.hpR = -ret.x / 100.0; statups.add(new BuffStatValue(BuffStat.FINAL_CUT, ret.y)); break; case 4331002: statups.add(new BuffStatValue(BuffStat.MIRROR_IMAGE, ret.x)); break; case 4331003: ret.duration = 60 * 1000; ret.overTime = true; statups.add(new BuffStatValue(BuffStat.OWL_SPIRIT, ret.y)); break; case 1121000: // maple warrior, all classes case 1221000: case 1321000: case 2121000: case 2221000: case 2321000: case 3121000: case 3221000: case 4121000: case 4221000: case 5121000: case 5221000: case 21121000: // Aran - Maple Warrior case 22171000: case 4341000: statups.add(new BuffStatValue(BuffStat.MAPLE_WARRIOR, ret.x)); break; case 3121002: // sharp eyes bow master case 3221002: // sharp eyes marksmen statups.add(new BuffStatValue(BuffStat.SHARP_EYES, ret.x << 8 | ret.y)); break; case 21101003: // Body Pressure statups.add(new BuffStatValue(BuffStat.BODY_PRESSURE, ret.x)); break; case 21000000: // Aran Combo statups.add(new BuffStatValue(BuffStat.ARAN_COMBO, 100)); break; case 21100005: // Combo Drain statups.add(new BuffStatValue(BuffStat.COMBO_DRAIN, ret.x)); break; case 21111001: // Smart Knockback statups.add(new BuffStatValue(BuffStat.SMART_KNOCKBACK, ret.x)); break; case 4001002: // disorder case 14001002: // cygnus disorder monsterStatus.put(MonsterStatus.WATK, ret.x); monsterStatus.put(MonsterStatus.WDEF, ret.y); break; case 5221009: // Mind Control monsterStatus.put(MonsterStatus.HYPNOTIZE, 1); break; case 1201006: // threaten monsterStatus.put(MonsterStatus.WATK, ret.x); monsterStatus.put(MonsterStatus.WDEF, ret.y); break; case 1211002: // charged blow case 1111008: // shout case 4211002: // assaulter case 3101005: // arrow bomb case 1111005: // coma: sword case 1111006: // coma: axe case 4221007: // boomerang step case 5101002: // Backspin Blow case 5101003: // Double Uppercut case 5121004: // Demolition case 5121005: // Snatch case 5121007: // Barrage case 5201004: // pirate blank shot case 4121008: // Ninja Storm case 22151001: case 4201004: //steal, new monsterStatus.put(MonsterStatus.STUN, 1); break; case 4321002: monsterStatus.put(MonsterStatus.DARKNESS, 1); break; case 4221003: case 4121003: monsterStatus.put(MonsterStatus.SHOWDOWN, ret.x); monsterStatus.put(MonsterStatus.MDEF, ret.x); monsterStatus.put(MonsterStatus.WDEF, ret.x); break; case 2201004: // cold beam case 2211002: // ice strike case 3211003: // blizzard case 2211006: // il elemental compo case 2221007: // Blizzard case 5211005: // Ice Splitter case 2121006: // Paralyze case 21120006: // Tempest case 22121000: monsterStatus.put(MonsterStatus.FREEZE, 1); ret.duration *= 2; // freezing skills are a little strange break; case 2101003: // fp slow case 2201003: // il slow case 12101001: case 22141003: // Slow monsterStatus.put(MonsterStatus.SPEED, ret.x); break; case 2101005: // poison breath case 2111006: // fp elemental compo case 2121003: // ice demon case 2221003: // fire demon case 3111003: //inferno, new case 22161002: //phantom imprint monsterStatus.put(MonsterStatus.POISON, 1); break; case 4121004: // Ninja ambush case 4221004: monsterStatus.put(MonsterStatus.NINJA_AMBUSH, (int) ret.damage); break; case 2311005: monsterStatus.put(MonsterStatus.DOOM, 1); break; /*case 4341006: statups.add(new Pair<MapleBuffStat, Integer>(BuffStat.MIRROR_TARGET, 1)); break;*/ case 3111002: // puppet ranger case 3211002: // puppet sniper case 13111004: // puppet cygnus case 5211001: // Pirate octopus summon case 5220002: // wrath of the octopi statups.add(new BuffStatValue(BuffStat.PUPPET, 1)); break; case 3211005: // golden eagle case 3111005: // golden hawk statups.add(new BuffStatValue(BuffStat.SUMMON, 1)); monsterStatus.put(MonsterStatus.STUN, Integer.valueOf(1)); break; case 3221005: // frostprey case 2121005: // elquines statups.add(new BuffStatValue(BuffStat.SUMMON, 1)); monsterStatus.put(MonsterStatus.FREEZE, Integer.valueOf(1)); break; case 2311006: // summon dragon case 3121006: // phoenix case 2221005: // ifrit case 2321003: // bahamut case 1321007: // Beholder case 5211002: // Pirate bird summon case 11001004: case 12001004: case 12111004: // Itrit case 13001004: case 14001005: statups.add(new BuffStatValue(BuffStat.SUMMON, 1)); break; case 2311003: // hs case 9001002: // GM hs statups.add(new BuffStatValue(BuffStat.HOLY_SYMBOL, ret.x)); break; case 2211004: // il seal case 2111004: // fp seal case 12111002: // cygnus seal monsterStatus.put(MonsterStatus.SEAL, 1); break; case 4111003: // shadow web case 14111001: monsterStatus.put(MonsterStatus.SHADOW_WEB, 1); break; case 4121006: // spirit claw statups.add(new BuffStatValue(BuffStat.SPIRIT_CLAW, 0)); break; case 2121004: case 2221004: case 2321004: // Infinity statups.add(new BuffStatValue(BuffStat.INFINITY, ret.x)); break; case 1121002: case 1221002: case 1321002: // Stance case 21121003: // Aran - Freezing Posture statups.add(new BuffStatValue(BuffStat.STANCE, ret.prop)); break; case 1005: // Echo of Hero case 10001005: // Cygnus Echo case 20001005: // Aran statups.add(new BuffStatValue(BuffStat.ECHO_OF_HERO, ret.x)); break; case 1026: // Soaring case 10001026: // Soaring case 20001026: // Soaring case 20011026: // Soaring ret.duration = 60 * 120 * 1000; //because it seems to dispel asap. ret.overTime = true; statups.add(new BuffStatValue(BuffStat.SOARING, 1)); break; case 2121002: // mana reflection case 2221002: case 2321002: statups.add(new BuffStatValue(BuffStat.MANA_REFLECTION, 1)); break; case 2321005: // holy shield statups.add(new BuffStatValue(BuffStat.HOLY_SHIELD, ret.x)); break; case 3121007: // Hamstring statups.add(new BuffStatValue(BuffStat.HAMSTRING, ret.x)); monsterStatus.put(MonsterStatus.SPEED, ret.x); break; case 3221006: // Blind statups.add(new BuffStatValue(BuffStat.BLIND, ret.x)); monsterStatus.put(MonsterStatus.ACC, ret.x); break; default: break; } } if (ret.morphId > 0 || ret.isPirateMorph()) { statups.add(new BuffStatValue(BuffStat.MORPH, ret.getMorph())); } if (ret.isMonsterRiding()) { statups.add(new BuffStatValue(BuffStat.MONSTER_RIDING, 1)); } ret.monsterStatus = monsterStatus; statups.trimToSize(); ret.statups = statups; return ret; } /** * @param applyto * @param obj * @param attack * damage done by the skill */ public final void applyPassive(final ChannelCharacter applyto, final GameMapObject obj) { if (this.makeChanceResult()) { switch (this.sourceid) { // MP eater case 2100000: case 2200000: case 2300000: if (obj == null || obj.getType() != GameMapObjectType.MONSTER) { return; } final Monster mob = (Monster) obj; // x is absorb percentage if (!mob.getStats().isBoss()) { final int absorbMp = Math.min((int) (mob.getMobMaxMp() * (this.getX() / 100.0)), mob.getMp()); if (absorbMp > 0) { mob.setMp(mob.getMp() - absorbMp); applyto.getStats().setMp(applyto.getStats().getMp() + absorbMp); applyto.getClient().write(ChannelPackets.showOwnBuffEffect(this.sourceid, 1)); applyto.getMap().broadcastMessage(applyto, ChannelPackets.showBuffeffect(applyto.getId(), this.sourceid, 1), false); } } break; } } } public final boolean applyTo(final ChannelCharacter chr) { return this.applyTo(chr, chr, true, null); } public final boolean applyTo(final ChannelCharacter chr, final Point pos) { return this.applyTo(chr, chr, true, pos); } private boolean applyTo(final ChannelCharacter applyfrom, final ChannelCharacter applyto, final boolean primary, final Point pos) { /* if (sourceid == 4341006 && applyfrom.getBuffedValue(BuffStat.MIRROR_IMAGE) == null) { return false; } */ int hpchange = this.calcHPChange(applyfrom, primary); int mpchange = this.calcMPChange(applyfrom, primary); final ActivePlayerStats stat = applyto.getStats(); if (primary) { if (this.itemConNo != 0) { InventoryManipulator.removeById(applyto.getClient(), applyto.getInventoryForItem(this.itemCon), this.itemCon, this.itemConNo, false, true); } } else if (!primary && this.isResurrection()) { hpchange = stat.getMaxHp(); // TODO fix death bug, player doesnt spawn on other screen applyto.setStance(0); } if (this.isDispel() && this.makeChanceResult()) { applyto.dispelDebuffs(); } else if (this.isHeroWill()) { applyto.dispelDebuff(Disease.SEDUCE); } else if (this.cureDebuffs.size() > 0) { for (final Disease debuff : this.cureDebuffs) { applyfrom.dispelDebuff(debuff); } } else if (this.isMPRecovery()) { final int toDecreaseHP = stat.getMaxHp() / 100 * 10; if (stat.getHp() > toDecreaseHP) { hpchange += -toDecreaseHP; // -10% of max HP } else { hpchange = stat.getHp() == 1 ? 0 : stat.getHp() - 1; } mpchange += toDecreaseHP / 100 * this.getY(); } final List<StatValue> hpmpupdate = Lists.newArrayListWithCapacity(2); if (hpchange != 0) { if (hpchange < 0 && -hpchange > stat.getHp() && !applyto.hasDisease(Disease.ZOMBIFY)) { return false; } stat.setHp(stat.getHp() + hpchange); } if (mpchange != 0) { if (mpchange < 0 && -mpchange > stat.getMp()) { return false; } stat.setMp(stat.getMp() + mpchange); hpmpupdate.add(new StatValue(Stat.MP, Integer.valueOf(stat.getMp()))); } hpmpupdate.add(new StatValue(Stat.HP, Integer.valueOf(stat.getHp()))); applyto.getClient().write(ChannelPackets.updatePlayerStats(hpmpupdate, true, applyto.getJobId())); if (this.expinc != 0) { applyto.gainExp(this.expinc, true, true, false); applyto.getClient().write(ChannelPackets.showSpecialEffect(19)); } else if (GameConstants.isMonsterCard(this.sourceid)) { applyto.getMonsterBook().addCard(applyto.getClient(), this.sourceid); } else if (this.isSpiritClaw()) { final Inventory use = applyto.getUseInventory(); Item item; for (int i = 0; i < use.getSlotLimit(); i++) { // impose order... item = use.getItem((byte) i); if (item != null) { if (GameConstants.isThrowingStar(item.getItemId()) && item.getQuantity() >= 200) { InventoryManipulator.removeById(applyto.getClient(), applyto.getUseInventory(), item.getItemId(), 200, false, true); break; } } } } if (this.overTime) { this.applyBuffEffect(applyfrom, applyto, primary); } if (primary) { if (this.overTime || this.isHeal()) { this.applyBuff(applyfrom); } if (this.isMonsterBuff()) { this.applyMonsterBuff(applyfrom); } } final SummonMovementType summonMovementType = this.getSummonMovementType(); if (summonMovementType != null && pos != null) { final Summon tosummon = new Summon(applyfrom, this.sourceid, pos, summonMovementType); if (!tosummon.isPuppet()) { applyfrom.getCheatTracker().resetSummonAttack(); } applyfrom.getMap().spawnSummon(tosummon); applyfrom.getSummons().put(this.sourceid, tosummon); tosummon.addHP((short) this.x); if (this.isBeholder()) { tosummon.addHP((short) 1); } /*if (sourceid == 4341006) { applyfrom.cancelEffectFromBuffStat(BuffStat.MIRROR_IMAGE); }*/ } else if (this.isMagicDoor()) { // Magic Door final Door door = new Door(applyto, new Point(applyto.getPosition())); // Current Map door applyto.getMap().spawnDoor(door); applyto.addDoor(door); final Door townDoor = new Door(door); // Town door applyto.addDoor(townDoor); door.getTown().spawnDoor(townDoor); if (applyto.hasParty()) { // update town doors applyto.silentPartyUpdate(); } applyto.disableDoor(); } else if (this.isMist()) { final Rectangle bounds = this.calculateBoundingBox(pos != null ? pos : applyfrom.getPosition(), applyfrom.isFacingLeft()); final Mist mist = new Mist(bounds, applyfrom, this); applyfrom.getMap().spawnMist(mist, this.getDuration(), this.isMistPoison(), false); } else if (this.isTimeLeap()) { // Time Leap for (final PlayerCooldownValueHolder i : applyto.getAllCooldowns()) { if (i.SkillId != 5121010) { applyto.removeCooldown(i.SkillId); applyto.getClient().write(ChannelPackets.skillCooldown(i.SkillId, 0)); } } } return true; } public final boolean applyReturnScroll(final ChannelCharacter applyto) { if (this.moveTo != -1) { if (applyto.getMap().getReturnMapId() != applyto.getMapId()) { GameMap target; if (this.moveTo == 999999999) { target = applyto.getMap().getReturnMap(); } else { target = ChannelServer.getMapFactory().getMap(this.moveTo); if (target.getId() / 10000000 != 60 && applyto.getMapId() / 10000000 != 61) { if (target.getId() / 10000000 != 21 && applyto.getMapId() / 10000000 != 20) { if (target.getId() / 10000000 != applyto.getMapId() / 10000000) { return false; } } } } applyto.changeMap(target, target.getPortal(0)); return true; } } return false; } private void applyBuff(final ChannelCharacter applyfrom) { if (this.isPartyBuff() && (applyfrom.hasParty() || this.isGmBuff())) { final Rectangle bounds = this.calculateBoundingBox(applyfrom.getPosition(), applyfrom.isFacingLeft()); final List<GameMapObject> affecteds = applyfrom.getMap().getMapObjectsInRect(bounds, Arrays.asList(GameMapObjectType.PLAYER)); for (final GameMapObject affectedmo : affecteds) { final ChannelCharacter affected = (ChannelCharacter) affectedmo; if (affected != applyfrom && (this.isGmBuff() || applyfrom.getPartyMembership().getPartyId() == affected.getPartyMembership().getPartyId())) { if (this.isResurrection() && !affected.isAlive() || !this.isResurrection() && affected.isAlive()) { this.applyTo(applyfrom, affected, false, null); affected.getClient().write(ChannelPackets.showOwnBuffEffect(this.sourceid, 2)); affected.getMap().broadcastMessage(affected, ChannelPackets.showBuffeffect(affected.getId(), this.sourceid, 2), false); } if (this.isTimeLeap()) { for (final PlayerCooldownValueHolder i : affected.getAllCooldowns()) { if (i.SkillId != 5121010) { affected.removeCooldown(i.SkillId); affected.getClient().write(ChannelPackets.skillCooldown(i.SkillId, 0)); } } } } } } } private void applyMonsterBuff(final ChannelCharacter applyfrom) { final Rectangle bounds = this.calculateBoundingBox(applyfrom.getPosition(), applyfrom.isFacingLeft()); final List<GameMapObject> affected = applyfrom.getMap().getMapObjectsInRect(bounds, Arrays.asList(GameMapObjectType.MONSTER)); int i = 0; for (final GameMapObject mo : affected) { if (this.makeChanceResult()) { ((Monster) mo).applyStatus(applyfrom, new MonsterStatusEffect(this.getMonsterStati(), SkillInfoProvider.getSkill(this.sourceid), null, false), this.isPoison(), this.getDuration(), false); } i++; if (i >= this.mobCount) { break; } } } private Rectangle calculateBoundingBox(final Point posFrom, final boolean facingLeft) { Point mylt; Point myrb; if (facingLeft) { mylt = new Point(this.lt.x + posFrom.x, this.lt.y + posFrom.y); myrb = new Point(this.rb.x + posFrom.x, this.rb.y + posFrom.y); } else { myrb = new Point(this.lt.x * -1 + posFrom.x, this.rb.y + posFrom.y); mylt = new Point(this.rb.x * -1 + posFrom.x, this.lt.y + posFrom.y); } return new Rectangle(mylt.x, mylt.y, myrb.x - mylt.x, myrb.y - mylt.y); } public final void silentApplyBuff(final ChannelCharacter chr, final long starttime) { final int localDuration = this.alchemistModifyVal(chr, this.duration, false); chr.registerEffect(this, starttime, TimerManager.getInstance().schedule(new CancelEffectAction(chr, this, starttime), starttime + localDuration - System.currentTimeMillis())); final SummonMovementType summonMovementType = this.getSummonMovementType(); if (summonMovementType != null) { final Summon tosummon = new Summon(chr, this.sourceid, chr.getPosition(), summonMovementType); if (!tosummon.isPuppet()) { chr.getCheatTracker().resetSummonAttack(); chr.getMap().spawnSummon(tosummon); chr.getSummons().put(this.sourceid, tosummon); tosummon.addHP((short) this.x); } } } public final void applyComboBuff(final ChannelCharacter applyto, final short combo) { final List<BuffStatValue> stat = Collections.singletonList(new BuffStatValue(BuffStat.ARAN_COMBO, combo)); applyto.getClient().write(ChannelPackets.giveBuff(this.sourceid, 99999, stat, this)); // Hackish timing, todo find out final long starttime = System.currentTimeMillis(); // final CancelEffectAction cancelAction = new CancelEffectAction(applyto, this, starttime); // final ScheduledFuture<?> schedule = TimerManager.getInstance().schedule(cancelAction, ((starttime + 99999) - System.currentTimeMillis())); applyto.registerEffect(this, starttime, null); } public final void applyEnergyBuff(final ChannelCharacter applyto, final boolean infinity) { // final List<Pair<MapleBuffStat, Integer>> stat = Collections.singletonList(new Pair<MapleBuffStat, Integer>(BuffStat.ENERGY_CHARGE, (int) applyto.getEnergyCharge())); applyto.getClient().write(ChannelPackets.giveEnergyChargeTest(0)); final long starttime = System.currentTimeMillis(); if (infinity) { applyto.registerEffect(this, starttime, null); } else { final CancelEffectAction cancelAction = new CancelEffectAction(applyto, this, starttime); final ScheduledFuture<?> schedule = TimerManager.getInstance().schedule(cancelAction, starttime + this.duration - System.currentTimeMillis()); applyto.registerEffect(this, starttime, schedule); } } private void applyBuffEffect(final ChannelCharacter applyfrom, final ChannelCharacter applyto, final boolean primary) { if (!this.isMonsterRiding_()) { applyto.cancelEffect(this, true, -1); } int localDuration = this.duration; if (primary) { localDuration = this.alchemistModifyVal(applyfrom, localDuration, false); applyto.getMap().broadcastMessage(applyto, ChannelPackets.showBuffeffect(applyto.getId(), this.sourceid, 1), false); } boolean normal = true; switch (this.sourceid) { case 5001005: // Dash case 4321000: //tornado spin case 15001003: { applyto.getClient().write(ChannelPackets.givePirate(this.statups, localDuration / 1000, this.sourceid)); applyto.getMap().broadcastMessage(applyto, ChannelPackets.giveForeignPirate(this.statups, localDuration / 1000, applyto.getId(), this.sourceid), false); normal = false; break; } case 5211006: // Homing Beacon case 22151002: //killer wings case 5220011: {// Bullseye if (applyto.getLinkedMonsterId() > 0) { applyto.getClient().write(ChannelPackets.cancelHoming()); applyto.getClient().write(ChannelPackets.giveHoming(this.sourceid, applyto.getLinkedMonsterId())); } else { return; } normal = false; break; } case 1004: case 10001004: case 5221006: case 20001004: { final int mountid = parseMountInfo(applyto, this.sourceid); if (mountid != 0) { final List<BuffStatValue> stat = Collections.singletonList(new BuffStatValue(BuffStat.MONSTER_RIDING, 0)); applyto.getClient().write(ChannelPackets.giveMount(mountid, this.sourceid, stat)); applyto.getMap().broadcastMessage(applyto, ChannelPackets.showMonsterRiding(applyto.getId(), stat, mountid, this.sourceid), false); normal = false; } break; } case 15100004: case 5110001: { // Energy Charge // final List<Pair<MapleBuffStat, Integer>> stat = Collections.singletonList(new Pair<MapleBuffStat, Integer>(BuffStat.ENERGY_CHARGE, applyto.getEnergyCharge())); // applyto.getClient().write(MaplePacketCreator.giveEnergyCharge(stat, (skill ? sourceid : -sourceid), localDuration)); applyto.getClient().write(ChannelPackets.giveEnergyChargeTest(0)); normal = false; break; } case 5121009: // Speed Infusion case 15111005: applyto.getClient().write(ChannelPackets.giveInfusion(this.statups, this.sourceid, localDuration)); applyto.getMap().broadcastMessage(applyto, ChannelPackets.giveForeignInfusion(applyto.getId(), this.x, localDuration), false); normal = false; break; case 13101006: case 4330001: case 4001003: case 14001003: { // Dark Sight final List<BuffStatValue> stat = Collections.singletonList(new BuffStatValue(BuffStat.DARKSIGHT, 0)); applyto.getMap().broadcastMessage(applyto, ChannelPackets.giveForeignBuff(applyto.getId(), stat, this), false); break; } case 4341002: { // Final Cut final List<BuffStatValue> stat = Collections.singletonList(new BuffStatValue(BuffStat.FINAL_CUT, this.y)); applyto.getClient().write(ChannelPackets.giveBuff(this.sourceid, localDuration, stat, this)); normal = false; break; } case 4331003: { // Owl Spirit final List<BuffStatValue> stat = Collections.singletonList(new BuffStatValue(BuffStat.OWL_SPIRIT, this.y)); applyto.getClient().write(ChannelPackets.giveBuff(this.sourceid, localDuration, stat, this)); normal = false; break; } case 4331002: { // Mirror Image final List<BuffStatValue> stat = Collections.singletonList(new BuffStatValue(BuffStat.MIRROR_IMAGE, 0)); applyto.getMap().broadcastMessage(applyto, ChannelPackets.giveForeignBuff(applyto.getId(), stat, this), false); break; } case 1111002: case 11111001: { // Combo final List<BuffStatValue> stat = Collections.singletonList(new BuffStatValue(BuffStat.COMBO, 1)); applyto.getMap().broadcastMessage(applyto, ChannelPackets.giveForeignBuff(applyto.getId(), stat, this), false); break; } case 3101004: case 3201004: case 13101003: { // Soul Arrow final List<BuffStatValue> stat = Collections.singletonList(new BuffStatValue(BuffStat.SOULARROW, 0)); applyto.getMap().broadcastMessage(applyto, ChannelPackets.giveForeignBuff(applyto.getId(), stat, this), false); break; } case 4111002: case 14111000: { // Shadow Partne final List<BuffStatValue> stat = Collections.singletonList(new BuffStatValue(BuffStat.SHADOWPARTNER, 0)); applyto.getMap().broadcastMessage(applyto, ChannelPackets.giveForeignBuff(applyto.getId(), stat, this), false); break; } case 1121010: // Enrage applyto.handleOrbconsume(); break; default: if (this.isMorph() || this.isPirateMorph()) { final List<BuffStatValue> stat = Collections.singletonList(new BuffStatValue(BuffStat.MORPH, Integer.valueOf(this.getMorph(applyto)))); applyto.getMap().broadcastMessage(applyto, ChannelPackets.giveForeignBuff(applyto.getId(), stat, this), false); } else if (this.isMonsterRiding()) { final int mountid = parseMountInfo(applyto, this.sourceid); if (mountid != 0) { final List<BuffStatValue> stat = Collections.singletonList(new BuffStatValue(BuffStat.MONSTER_RIDING, 0)); applyto.getClient().write(ChannelPackets.cancelBuff(null)); applyto.getClient().write(ChannelPackets.giveMount(mountid, this.sourceid, stat)); applyto.getMap().broadcastMessage(applyto, ChannelPackets.showMonsterRiding(applyto.getId(), stat, mountid, this.sourceid), false); } else { return; } normal = false; } else if (this.isSoaring()) { final List<BuffStatValue> stat = Collections.singletonList(new BuffStatValue(BuffStat.SOARING, 1)); applyto.getMap().broadcastMessage(applyto, ChannelPackets.giveForeignBuff(applyto.getId(), stat, this), false); applyto.getClient().write(ChannelPackets.giveBuff(this.sourceid, localDuration, stat, this)); normal = false; } break; } // Broadcast effect to self if (normal && this.statups.size() > 0) { applyto.getClient().write(ChannelPackets.giveBuff(this.skill ? this.sourceid : -this.sourceid, localDuration, this.statups, this)); } final long starttime = System.currentTimeMillis(); final CancelEffectAction cancelAction = new CancelEffectAction(applyto, this, starttime); final ScheduledFuture<?> schedule = TimerManager.getInstance().schedule(cancelAction, starttime + localDuration - System.currentTimeMillis()); applyto.registerEffect(this, starttime, schedule); } public static int parseMountInfo(final ChannelCharacter player, final int skillid) { switch (skillid) { case 1004: // Monster riding case 10001004: case 20001004: case 20011004: final Item item = player.getEquippedItemsInventory().getItem((byte) -22); if (item != null) { return item.getItemId(); } return 0; case 5221006: // Battleship return 1932000; } return 0; } private int calcHPChange(final ChannelCharacter applyfrom, final boolean primary) { int hpchange = 0; if (this.hp != 0) { if (!this.skill) { if (primary) { hpchange += this.alchemistModifyVal(applyfrom, this.hp, true); } else { hpchange += this.hp; } if (applyfrom.hasDisease(Disease.ZOMBIFY)) { hpchange /= 2; } } else { // assumption: this is heal hpchange += makeHealHP(this.hp / 100.0, applyfrom.getStats().getTotalMagic(), 3, 5); if (applyfrom.hasDisease(Disease.ZOMBIFY)) { hpchange = -hpchange; } } } if (this.hpR != 0) { hpchange += (int) (applyfrom.getStats().getCurrentMaxHp() * this.hpR); } // actually receivers probably never get any hp when it's not heal but whatever if (primary) { if (this.hpCon != 0) { hpchange -= this.hpCon; } } switch (this.sourceid) { case 4211001: // Chakra // final PlayerStats stat = applyfrom.getStats(); // int v42 = getY() + 100; // int v38 = Randomizer.rand(100, 200) % 0x64 + 100; // hpchange = (int) ((v38 * stat.getLuk() * 0.033 + stat.getDex()) * v42 * 0.002); hpchange += makeHealHP(this.getY() / 100.0, applyfrom.getStats().getTotalLuk(), 2.3, 3.5); break; } return hpchange; } private static int makeHealHP(final double rate, final double stat, final double lowerfactor, final double upperfactor) { return (int) (Math.random() * ((int) (stat * upperfactor * rate) - (int) (stat * lowerfactor * rate) + 1) + (int) (stat * lowerfactor * rate)); } private static int getElementalAmp(final int job) { switch (job) { case 211: case 212: return 2110001; case 221: case 222: return 2210001; case 1211: case 1212: return 12110001; case 2215: case 2216: case 2217: case 2218: return 22150000; } return -1; } private int calcMPChange(final ChannelCharacter applyfrom, final boolean primary) { int mpchange = 0; if (this.mp != 0) { if (primary) { mpchange += this.alchemistModifyVal(applyfrom, this.mp, true); } else { mpchange += this.mp; } } if (this.mpR != 0) { mpchange += (int) (applyfrom.getStats().getCurrentMaxMp() * this.mpR); } if (primary) { if (this.mpCon != 0) { double mod = 1.0; final int ElemSkillId = getElementalAmp(applyfrom.getJobId()); if (ElemSkillId != -1) { final ISkill amp = SkillInfoProvider.getSkill(ElemSkillId); final int ampLevel = applyfrom.getCurrentSkillLevel(amp); if (ampLevel > 0) { final StatEffect ampStat = amp.getEffect(ampLevel); mod = ampStat.getX() / 100.0; } } if (applyfrom.getBuffedValue(BuffStat.INFINITY) != null) { mpchange = 0; } else { mpchange -= this.mpCon * mod; } } } return mpchange; } private int alchemistModifyVal(final ChannelCharacter chr, final int val, final boolean withX) { if (!this.skill) { final StatEffect alchemistEffect = this.getAlchemistEffect(chr); if (alchemistEffect != null) { return (int) (val * ((withX ? alchemistEffect.getX() : alchemistEffect.getY()) / 100.0)); } } return val; } private StatEffect getAlchemistEffect(final ChannelCharacter chr) { ISkill al; switch (chr.getJobId()) { case 411: case 412: al = SkillInfoProvider.getSkill(4110000); if (chr.getCurrentSkillLevel(al) == 0) { return null; } return al.getEffect(chr.getCurrentSkillLevel(al)); case 1411: case 1412: al = SkillInfoProvider.getSkill(14110003); if (chr.getCurrentSkillLevel(al) == 0) { return null; } return al.getEffect(chr.getCurrentSkillLevel(al)); } return null; } public final void setSourceId(final int newid) { this.sourceid = newid; } private boolean isGmBuff() { switch (this.sourceid) { case 1005: // echo of hero acts like a gm buff case 10001005: // cygnus Echo case 20001005: // Echo case 20011005: case 9001000: // GM dispel case 9001001: // GM haste case 9001002: // GM Holy Symbol case 9001003: // GM Bless case 9001005: // GM resurrection case 9001008: // GM Hyper body return true; default: return false; } } private boolean isMonsterBuff() { switch (this.sourceid) { case 1201006: // threaten case 2101003: // fp slow case 2201003: // il slow case 12101001: // cygnus slow case 2211004: // il seal case 2111004: // fp seal case 12111002: // cygnus seal case 2311005: // doom case 4111003: // shadow web case 14111001: // cygnus web case 4121004: // Ninja ambush case 4221004: // Ninja ambush case 22151001: case 22141003: case 22121000: case 22161002: case 4321002: return this.skill; } return false; } public final boolean isMonsterRiding_() { return this.skill && (this.sourceid == 1004 || this.sourceid == 10001004 || this.sourceid == 20001004 || this.sourceid == 20011004); } public final boolean isMonsterRiding() { return this.skill && this.isMonsterRiding_(); } private boolean isPartyBuff() { if (this.lt == null || this.rb == null) { return false; } switch (this.sourceid) { case 1211003: case 1211004: case 1211005: case 1211006: case 1211007: case 1211008: case 1221003: case 1221004: case 11111007: case 12101005: return false; } return true; } public final boolean isHeal() { return this.sourceid == 2301002 || this.sourceid == 9101000; } public final boolean isResurrection() { return this.sourceid == 9001005 || this.sourceid == 2321006; } public final boolean isTimeLeap() { return this.sourceid == 5121010; } public final short getHp() { return this.hp; } public final short getMp() { return this.mp; } public final byte getMastery() { return this.mastery; } public final short getWatk() { return this.watk; } public final short getMatk() { return this.matk; } public final short getWdef() { return this.wdef; } public final short getMdef() { return this.mdef; } public final short getAcc() { return this.acc; } public final short getAvoid() { return this.avoid; } public final short getHands() { return this.hands; } public final short getSpeed() { return this.speed; } public final short getJump() { return this.jump; } public final int getDuration() { return this.duration; } public final boolean isOverTime() { return this.overTime; } public final List<BuffStatValue> getStatups() { return this.statups; } public final boolean sameSource(final StatEffect effect) { return this.sourceid == effect.sourceid && this.skill == effect.skill; } public final int getX() { return this.x; } public final int getY() { return this.y; } public final int getZ() { return this.z; } public final short getDamage() { return this.damage; } public final byte getAttackCount() { return this.attackCount; } public final byte getBulletCount() { return this.bulletCount; } public final int getBulletConsume() { return this.bulletConsume; } public final byte getMobCount() { return this.mobCount; } public final int getMoneyCon() { return this.moneyCon; } public final int getCooldown() { return this.cooldown; } public final Map<MonsterStatus, Integer> getMonsterStati() { return this.monsterStatus; } public final boolean isHide() { return this.skill && this.sourceid == 9001004; } public final boolean isDragonBlood() { return this.skill && this.sourceid == 1311008; } public final boolean isBerserk() { return this.skill && this.sourceid == 1320006; } public final boolean isBeholder() { return this.skill && this.sourceid == 1321007; } public final boolean isMPRecovery() { return this.skill && this.sourceid == 5101005; } public final boolean isMagicDoor() { return this.skill && this.sourceid == 2311002; } public final boolean isMesoGuard() { return this.skill && this.sourceid == 4211005; } public final boolean isCharge() { switch (this.sourceid) { case 1211003: case 1211008: case 11111007: case 12101005: case 15101006: case 21111005: return this.skill; } return false; } public final boolean isMistPoison() { switch (this.sourceid) { case 2111003: case 12111005: // Flame gear case 14111006: // Poison bomb return true; } return false; } public final boolean isPoison() { switch (this.sourceid) { case 2111003: case 2101005: case 2111006: case 2121003: case 2221003: case 12111005: // Flame gear case 3111003: //inferno, new case 22161002: //phantom imprint return this.skill; } return false; } private boolean isMist() { return this.skill && (this.sourceid == 2111003 || this.sourceid == 4221006 || this.sourceid == 12111005 || this.sourceid == 14111006); // poison mist, smokescreen and flame gear } private boolean isSpiritClaw() { return this.skill && this.sourceid == 4121006; } private boolean isDispel() { return this.skill && (this.sourceid == 2311001 || this.sourceid == 9001000); } private boolean isHeroWill() { switch (this.sourceid) { case 1121011: case 1221012: case 1321010: case 2121008: case 2221008: case 2321009: case 3121009: case 3221008: case 4121009: case 4221008: case 5121008: case 5221010: case 21121008: case 22171004: case 4341008: return this.skill; } return false; } public final boolean isAranCombo() { return this.sourceid == 21000000; } public final boolean isPirateMorph() { switch (this.sourceid) { case 15111002: case 5111005: case 5121003: return this.skill; } return false; } public final boolean isMorph() { return this.morphId > 0; } public final int getMorph() { return this.morphId; } public final int getMorph(final ChannelCharacter chr) { final byte genderByte = chr.getGender().asNumber(); switch (this.morphId) { case 1000: case 1100: return this.morphId + genderByte; case 1003: return this.morphId + genderByte * 100; } return this.morphId; } public final SummonMovementType getSummonMovementType() { if (!this.skill) { return null; } switch (this.sourceid) { case 3211002: // puppet sniper case 3111002: // puppet ranger case 13111004: // puppet cygnus case 5211001: // octopus - pirate case 5220002: // advanced octopus - pirate case 4341006: return SummonMovementType.STATIONARY; case 3211005: // golden eagle case 3111005: // golden hawk case 2311006: // summon dragon case 3221005: // frostprey case 3121006: // phoenix case 5211002: // bird - pirate return SummonMovementType.CIRCLE_FOLLOW; case 1321007: // beholder case 2121005: // elquines case 2221005: // ifrit case 2321003: // bahamut case 12111004: // Ifrit case 11001004: // soul case 12001004: // flame case 13001004: // storm case 14001005: // darkness case 15001004: return SummonMovementType.FOLLOW; } return null; } public final boolean isSoaring() { switch (this.sourceid) { case 1026: // Soaring case 10001026: // Soaring case 20001026: // Soaring case 20011026: // Soaring return this.skill; } return false; } public final boolean isSkill() { return this.skill; } public final int getSourceId() { return this.sourceid; } /** * * @return true if the effect should happen based on it's probablity, false * otherwise */ public final boolean makeChanceResult() { return this.prop == 100 || Randomizer.nextInt(99) < this.prop; } public final short getProb() { return this.prop; } public static class CancelEffectAction implements Runnable { private final StatEffect effect; private final WeakReference<ChannelCharacter> target; private final long startTime; public CancelEffectAction(final ChannelCharacter target, final StatEffect effect, final long startTime) { this.effect = effect; this.target = new WeakReference<>(target); this.startTime = startTime; } @Override public void run() { final ChannelCharacter realTarget = this.target.get(); if (realTarget != null) { realTarget.cancelEffect(this.effect, false, this.startTime); } } } }