/*******************************************************************************
* Copyright (c) 2014, 2015 Scott Clarke (scott@dawg6.com).
*
* This file is part of Dawg6's Demon Hunter DPS Calculator.
*
* Dawg6's Demon Hunter DPS Calculator 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.
*
* Dawg6's Demon Hunter DPS Calculator 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.dawg6.web.dhcalc.shared.calculator;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.Vector;
public class ActionEvent extends Event {
private Hand hand;
private final boolean bastions;
private final double mainHandAps;
private final double offHandAps;
private final double mainHandInterval;
private final double offHandInterval;
private final boolean hasOffHand;
private final List<SkillAndRune> skills = new Vector<SkillAndRune>();
private SkillAndRune spender = null;
private SkillAndRune generator = null;
private final boolean m4;
private final boolean mortalEnemy;
private final double markedAmount;
private final Rune venRune;
private RoVEvent rov;
private final boolean n2;
private final boolean ue2;
private final boolean kridershot;
private final boolean yangs;
private final boolean huntersWrath;
private final boolean meticulousBolts;
private final boolean odysseys;
private final Rune esRune;
private double esDuration;
private final Map<ActiveSkill, Breakpoint> bpMap = new TreeMap<ActiveSkill, Breakpoint>();
private final double delay;
private final WeaponType mainHand;
private final boolean bots;
private final boolean karleis;
private final double karleisHatred;
private final SpenderLogic spenderLogic;
private final GeneratorLogic generatorLogic;
private SpikeTrapActionEvent spikeTrap;
private final double maxHatred;
private final boolean hasIllWill;
public ActionEvent(CharacterData data) {
this.hand = Hand.MainHand;
this.bastions = data.isBastions();
this.m4 = data.isSentry() && (data.getNumMarauders() >= 4);
this.mortalEnemy = data.isMarked()
&& (data.getMfdRune() == Rune.Mortal_Enemy);
this.n2 = data.getNumNats() >= 2;
this.ue2 = data.getNumUe() >= 2;
this.kridershot = data.isKridershot();
this.yangs = data.isYangs();
this.huntersWrath = data.isHuntersWrath();
this.meticulousBolts = data.isMeticulousBolts();
this.odysseys = data.isOdysseysEnd();
this.mainHand = data.getWeaponType();
this.bots = data.isBotS();
this.venRune = data.getSkills().get(ActiveSkill.Vengeance);
this.karleis = data.isKarleis();
this.spenderLogic = data.getSpenderLogic();
this.generatorLogic = data.getGeneratorLogic();
this.maxHatred = data.getMaxHatred();
this.hasIllWill = data.isIllWill();
this.markedAmount = 4.0 + (data.isHexingPants() ? ((4.0 * data
.getPercentMoving() * .25) - (4.0 * (1.0 - data
.getPercentMoving()) * data.getHexingPantsPercent())) : 0.0);
this.karleisHatred = data.getKarlieshatred()
+ (data.isHexingPants() ? ((data.getKarlieshatred()
* data.getPercentMoving() * .25) - (data
.getKarlieshatred() * (1.0 - data.getPercentMoving()) * data
.getHexingPantsPercent())) : 0.0);
this.mainHandAps = data.getAps();
this.delay = data.getDelay() / 1000.0;
this.mainHandInterval = (1.0 / mainHandAps) + delay;
this.hasOffHand = data.getOffHand_weaponType() != null;
this.offHandAps = data.getOffHand_aps();
if (this.hasOffHand) {
this.offHandInterval = (1.0 / offHandAps) + delay;
} else {
this.offHandInterval = 0.0;
}
if (kridershot && meticulousBolts) {
Rune eaRune = data.getSkills().get(ActiveSkill.EA);
if (eaRune == Rune.Ball_Lightning)
generator = new SkillAndRune(ActiveSkill.EA,
Rune.Ball_Lightning);
}
esRune = data.getSkills().get(ActiveSkill.ES);
if (this.odysseys && (esRune != null)) {
esDuration = (esRune == Rune.Heavy_Burden ? 4.0 : 2.0);
}
for (Map.Entry<ActiveSkill, Rune> e : data.getSkills().entrySet()) {
ActiveSkill skill = e.getKey();
SkillType type = skill.getSkillType();
Rune rune = e.getValue();
if ((type == SkillType.Primary)
|| (type == SkillType.Spender)
|| (type == SkillType.Channeled)
|| ((skill == ActiveSkill.FoK) && (rune == Rune.Knives_Expert))
|| ((skill == ActiveSkill.Vault) && (rune == Rune.Action_Shot) && data.isDanettas() && (spender == null) && data.isBastions())
) {
SkillAndRune skr = new SkillAndRune(skill, rune);
skills.add(skr);
double h = skr.getHatred(data);
// GWT.log("skill = " + skill.name() + " h = " + h);
if ((h > 0)
&& ((generator == null) || (h > generator
.getHatred(data)))) {
if ((generator == null) || !kridershot || !meticulousBolts)
generator = skr;
}
if ((spender == null) && (h < 0)) {
spender = skr;
// GWT.log("Spender = " + spender.getSkill().name());
}
}
}
Collections.sort(skills, new SkillAndRune.HatredSorter(data));
if (data.isSpikeTrap())
spikeTrap = new SpikeTrapActionEvent(data);
else
spikeTrap = null;
}
@Override
public void execute(EventQueue queue, List<Damage> log,
SimulationState state) {
SkillAndRune selected = null;
if (spikeTrap != null) {
int qty = spikeTrap.getQtyAvailable(state);
if (qty > 0) {
selected = spikeTrap.getSkillAndRune();
}
}
double interval = (this.hand == Hand.MainHand) ? mainHandInterval
: offHandInterval;
double t = this.time;
double hatred = state.getHatred();
state.setHand(hand);
if (selected == null) {
if (this.odysseys && (esRune != null)) {
BuffState oeBuff = state.getBuffs().getBuffs()
.get(Buff.OdysseysEnd);
if ((oeBuff == null) || ((t + interval) >= oeBuff.getExpires())) {
selected = new SkillAndRune(ActiveSkill.ES, esRune);
}
}
if (bastions) {
double bwgExpires = state.getBuffs().getBuffs().get(Buff.BwGen)
.getExpires();
double bwsExpires = state.getBuffs().getBuffs()
.get(Buff.BwSpend).getExpires();
if ((t + interval) >= bwgExpires) {
selected = generator;
}
if ((t + interval) >= bwsExpires) {
if (spender != null) {
double h = spender.getHatred(state.getData());
if (h <= hatred) {
selected = spender;
}
}
}
}
if ((selected == null) && kridershot && meticulousBolts
&& (generator != null)
&& (generator.getSkill() == ActiveSkill.EA)
&& (generator.getRune() == Rune.Ball_Lightning)) {
selected = generator;
}
if ((selected == null) && (generator != null) && (spenderLogic == SpenderLogic.FullHatred) && (hatred < maxHatred))
selected = generator;
if ((selected == null) && (generator != null) && ((spender == null) || (spender.getSkill() == ActiveSkill.Vault)))
selected = generator;
if (selected == null) {
for (SkillAndRune skr : skills) {
double h = skr.getHatred(state.getData());
if ((h + hatred) >= 0) {
selected = skr;
break;
}
}
}
}
if (selected != null) {
state.setLastAttack(selected);
String botsLog = null;
if (selected.getSkill() != ActiveSkill.ST) {
if (bots) {
TargetHolder target = state.getTargets().getTarget(
TargetType.Primary);
if (target.isAlive() && (target.getNextBots() <= t)) {
target.setBotsStacks(target.getBotsStacks() + 1);
target.setNextBots(t + 0.3);
botsLog = " BotS(" + target.getBotsStacks() + ")";
}
}
}
Breakpoint.Data bpData = this.getBpData(selected.getSkill(), hand);
double actualInterval = bpData.interval + this.delay;
if (this.yangs && (selected.getSkill() == ActiveSkill.MS)) {
actualInterval = (bpData.interval / 1.5) + this.delay;
}
if (this.huntersWrath
&& (selected.getSkill().getSkillType() == SkillType.Primary)) {
actualInterval = (bpData.interval / 1.3) + this.delay;
}
state.setLastAps(1.0 / actualInterval);
this.time += actualInterval;
double h = selected.getHatred(state.getData());
double actualHatred = state.addHatred(h);
if (this.odysseys && (selected.getSkill() == ActiveSkill.ES)) {
state.getBuffs().set(Buff.OdysseysEnd, t + this.esDuration);
}
if (bastions) {
if (h < 0) {
state.getBuffs().set(Buff.BwSpend, t + 5.0);
} else if (h > 0) {
state.getBuffs().set(Buff.BwGen, t + 5.0);
}
}
List<Damage> dList = new Vector<Damage>();
if (selected.getSkill() == ActiveSkill.ST) {
spikeTrap.execute(queue, log, state);
} else {
dList = DamageFunction.getDamages(
true,
false,
"Player",
new DamageSource(selected.getSkill(), selected
.getRune()), state);
boolean wasSpender = false;
boolean wasGenerator = false;
if (selected.getSkill().getSkillType() == SkillType.Primary)
wasGenerator = true;
else if (selected.getSkill().getSkillType() == SkillType.Spender) {
if (h < 0)
wasSpender = true;
else
wasGenerator = true;
}
for (Damage d : dList) {
if ((botsLog != null) && (d.target == TargetType.Primary)) {
d.note += botsLog;
botsLog = null;
}
if (d.hatred > 0)
d.hatred = actualHatred;
}
applyDamages(state, log, dList);
if (state.hasSpikeTraps()) {
Rune r = state.getData().getSpikeTrapRune();
if (((r == Rune.Custom_Trigger) && wasGenerator)
|| ((r != Rune.Custom_Trigger) && wasSpender)) {
state.detonateTraps(queue, log);
}
}
if (ue2 && (h > 0)) {
double actual = state.addDisc(1.0);
if (actual > 0) {
Damage d = new Damage();
d.time = state.getTime();
d.disc = actual;
d.shooter = "Player";
d.note = "UE2 Disc";
d.currentDisc = state.getDisc();
d.currentHatred = state.getHatred();
log.add(d);
}
}
if (m4
&& (selected.getSkill().getSkillType() == SkillType.Spender)) {
List<Damage> sList = DamageFunction.getDamages(false, true,
"Sentry", new DamageSource(selected.getSkill(),
selected.getRune()), state);
applyDamages(state, log, sList);
state.setLastSpenderTime(t);
dList.addAll(sList);
}
if (mortalEnemy
&& (state.getBuffs().isActive(Buff.MfdPrimary) || state
.getBuffs().isActive(Buff.MfdAdditional))) {
double mh = state.addHatred(markedAmount);
if (mh > 0) {
Damage d = new Damage();
d.shooter = "Player";
d.source = new DamageSource(ActiveSkill.MFD,
Rune.Mortal_Enemy);
d.hatred = mh;
d.time = t;
d.note = "MfD/ME Hatred";
d.currentHatred = state.getHatred();
d.currentDisc = state.getDisc();
log.add(d);
}
}
}
if (state.getBuffs().isActive(Buff.Vengeance)) {
List<Damage> vList = DamageFunction.getDamages(true, false,
"Player", new DamageSource(ActiveSkill.Vengeance,
venRune), state);
applyDamages(state, log, vList);
dList.addAll(vList);
}
if (state.getData().isSashOfKnives()) {
List<Damage> l2 = DamageFunction.getDamages(true, false,
"Sash of Knives", new DamageSource(ActiveSkill.SoK,
Rune.None), state);
applyDamages(state, log, l2);
dList.addAll(l2);
}
if (n2 && (rov != null)) {
if (rov.getTime() > this.time) {
queue.remove(rov);
rov.setTime(Math.max(rov.getTime() - 4.0, this.time));
queue.push(rov);
}
}
Set<TargetType> targetsHit = new TreeSet<TargetType>();
for (Damage d : dList) {
if ((d.target != null) && (d.damage > 0)
&& state.getTargets().getTarget(d.target).isAlive())
targetsHit.add(d.target);
}
if (karleis && (selected.getSkill() == ActiveSkill.IMP)) {
double mh = karleisHatred;
if (mh > 0) {
int mm = 0;
for (TargetType target : targetsHit) {
if (state.getTargets().getTarget(target).isImpaled())
mm++;
}
if ((mm > 0) && state.getData().isHolyPointShot()) {
mm += 2;
}
double mmh = state.addHatred(mh*mm);
if (mmh > 0) {
Damage d = new Damage();
d.shooter = "Player";
d.source = new DamageSource(selected.getSkill(),
selected.getRune());
d.hatred = mmh;
d.time = t;
d.note = "Karlei's Point Hatred (" + mm + " x " + mh +")";
d.currentHatred = state.getHatred();
d.currentDisc = state.getDisc();
log.add(d);
}
}
}
if (selected.getSkill() == ActiveSkill.IMP) {
for (TargetType target : targetsHit) {
state.getTargets().getTarget(target).setImpaled(true);
}
}
// Gem procs
if (!targetsHit.isEmpty()) {
applyDamages(state, log, DamageFunction.getDamages(false,
false, "Player", null, state, targetsHit));
}
if (hasOffHand) {
this.hand = (this.hand == Hand.MainHand) ? Hand.OffHand
: Hand.MainHand;
}
} else {
this.time = queue.nextTime(this.time);
}
queue.push(this);
}
public RoVEvent getRov() {
return rov;
}
public void setRov(RoVEvent rov) {
this.rov = rov;
}
private Breakpoint getBreakpoint(ActiveSkill skill) {
Breakpoint bp = bpMap.get(skill);
if (bp == null) {
int frames = skill.getFrames();
if (frames < 0) {
frames = mainHand.getFrames();
}
bp = new Breakpoint(frames);
bpMap.put(skill, bp);
}
return bp;
}
private Breakpoint.Data getBpData(ActiveSkill skill, Hand hand) {
return getBreakpoint(skill).get(
(hand == Hand.MainHand) ? this.mainHandAps : this.offHandAps);
}
}