Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Merge branch 'master' of github.com:nathanbeckmann/Rift

  • Loading branch information...
commit 34588d00a4484029505af5cfd7bda595ca26b0b3 2 parents 9ba3f47 + 2ca37c3
Nathan Beckmann authored
View
2  graphs/Graphs.m
@@ -2,7 +2,7 @@
<< ./Lib.m
-ext = ".png"
+ext = ".pdf"
plotDataSet[data, "Damage", 2, inputFile <> "-damage", ext]
plotDataSet[data, "Heals", 3, inputFile <> "-heals", ext]
View
8 parser/Action.scala
@@ -13,6 +13,7 @@ class Action (
object Categories {
val CAST_START = 1
+ val INTERRUPT = 2
val HIT = 3
val DOT = 4
val HEAL = 5
@@ -22,7 +23,10 @@ class Action (
val DEBUFF_END = 9
val MISS = 10
val SLAIN = 11
+ val DIED = 12
+ val FALL = 14
val DODGE = 15
+ val PARRY = 16
val CRITICAL_HIT = 23
val IMMUNE = 26
val REGEN = 27 // ??
@@ -43,7 +47,9 @@ class Action (
case _ => false
}
- // if this is a start or end marker
+ // if this is a Combat Begin/End
+ // note: this isn't used for much since we use an inactivity
+ // interval now
def isBookend: Boolean = category == 0
override def toString: String =
View
125 parser/Combat.scala
@@ -1,71 +1,120 @@
-import scala.collection.mutable.{DoubleLinkedList, Map, StringBuilder}
+import scala.collection.mutable.{Buffer, Map, StringBuilder}
import scala.util.Sorting.stableSort
import scala.util.matching.Regex
class Combat extends Grapher {
- def start: Long = actions.head.time.getTime
- def end: Long = actions.last.time.getTime
- def duration: Long = end - start
+
+ var start: Long = 0
+ var end: Long = 0
+ private var last: Long = 0 // the last action, regardless
+ // of type. see comment below
+ // for inCombat
+
+ def duration: Long = (end - start) / 1000
+ private def idle: Long = (last - end) / 1000
- var actions: DoubleLinkedList[Action] = new DoubleLinkedList[Action]()
- var inCombat: Boolean = false
+ val actions: Buffer[Action] = Buffer.empty[Action]
+
+ private var deferredActions: Buffer[Action] = Buffer.empty[Action]
val entities = Map[Id, Entity]()
- def handle(action: Action) {
+ // The timing model for combat is from the first to last damaging
+ // action. Combat is reset whenever no damaging action occurs within
+ // Config.inactivityThreshold seconds
+ def inCombat = (start != 0) && (idle <= Config.inactivityThreshold)
+ def ended = (start != 0) && (idle > Config.inactivityThreshold)
- if (Config.saveActions || action.isBookend)
- actions = actions :+ action
+ private def isCombatAction(action: Action) = action.isDmg
- // No more processing needed for bookends
- if (!action.isBookend) {
-
+ private def isIgnoredAction(action: Action) =
+ (Config.ignoredActions contains action.name) ||
+ action.isBookend ||
+ (action.source.r == Id.Relation.Other && action.target.r == Id.Relation.Other)
+
+ private def updateTimers(action: Action) {
+ if (isCombatAction(action)) {
+ if (start == 0)
+ start = action.time.getTime
+
+ end = action.time.getTime
+ }
+
+ last = action.time.getTime
+ }
+
+ // Deferred processing of an action
+ private def process(action: Action) {
// extending Map to use default didn't work for some reason,
// probably I'm a nub and a moron
- if (!(entities contains action.source))
- entities += action.source -> new Entity(action.source, action.sourceName, this)
- if (!(entities contains action.target))
- entities += action.target -> new Entity(action.target, action.targetName, this)
+ if (!(entities contains action.source))
+ entities += action.source -> new Entity(action.source, action.sourceName, this)
+ if (!(entities contains action.target))
+ entities += action.target -> new Entity(action.target, action.targetName, this)
- val source = entities(action.source)
- val target = entities(action.target)
+ val source = entities(action.source)
+ val target = entities(action.target)
- // Add pet to owner; pets is a set so this won't produce duplicates
- (entities get action.owner) match {
- case None => ()
+ // pets
+ (entities get action.owner) match {
+ case None => ()
case Some(owner) => {
if (owner.id.t != Id.Type.Unknown)
- owner.pets += source
+ owner.pets += source // pets is a set; no duplicates
}
- }
+ }
- if (Config.saveActions)
- source.actions :+ action
+ if (Config.saveActions)
+ source.actions :+ action
- if (action.isDmg) {
- source.damage += action.time -> action.amount
- target.damageTaken += action.time -> action.amount
- }
- else if (action.isHeal) {
- source.heals += action.time -> action.amount
- target.healsTaken += action.time -> action.amount
- }
+ if (action.isDmg) {
+ source.damage += action.time -> action.amount
+ target.damageTaken += action.time -> action.amount
+ }
+ else if (action.isHeal) {
+ source.heals += action.time -> action.amount
+ target.healsTaken += action.time -> action.amount
+ }
+ }
+
+ // Public interface to process a single action
+ def handle(action: Action) {
+ if (Config.saveActions)
+ actions :+ action
+
+ if (isIgnoredAction(action))
+ return
+
+ updateTimers(action)
+
+ // we defer processing of actions until we know that they should
+ // be included in the stats of this combat (see updateTimers)
+ if (inCombat) {
+ deferredActions += action
}
- action.name match {
- case "Combat Begin" => inCombat = true
- case "Combat End" => inCombat = false
- case _ =>
+ if (isCombatAction(action)) {
+ deferredActions.foreach(process)
+ deferredActions.clear()
}
}
+ // Notify this structure that nothing has happened for a while,
+ // possibly end combat
+ //
+ // The milliseconds parameter is in real-time -- not rift time. (For
+ // offline parsing these are not the same.)
+ def notifyIdle(milliseconds: Long) {
+ last += milliseconds
+ }
+
// Pass in a string of with these formatting rules:
// %t - combat time
// %<num>d - <num> top dpsers
// %<num>h - <num> top hpsers
def format(fmt: String): String = {
- val secs = duration / 1000
+ val secs = duration
val mins = secs / 60
val timeStr = mins + ":" + ("%02d" format (secs % 60))
View
6 parser/Config.scala
@@ -5,4 +5,10 @@ object Config {
val combinePets = true
var makeGraphs = false
+ // The number of seconds to wait with no damaging spells before
+ // considering that combat has ended
+ val inactivityThreshold = 5
+
+ val ignoredActions = List("Shocking Cipher", "Deathly Flames")
+
}
View
6 parser/Entity.scala
@@ -1,12 +1,12 @@
import java.util.Date
-import scala.collection.mutable.{DoubleLinkedList, Set, StringBuilder}
+import scala.collection.mutable.{Buffer, Set, StringBuilder}
class Entity(
val id: Id,
val name: String,
val combat: Combat) extends Grapher {
- val actions: DoubleLinkedList[Action] = new DoubleLinkedList[Action]()
+ val actions: Buffer[Action] = Buffer.empty[Action]
val pets: Set[Entity] = Set[Entity]()
var damage: Statistic = new Statistic("damage", this, (ent: Entity) => ent.damage)
@@ -25,7 +25,7 @@ class Entity(
}
private def valPerSecond(thisVal: => Statistic, petVal: Entity => Double) =
- 1000 * thisVal.full / combat.duration +
+ thisVal.full / combat.duration +
(if (Config.combinePets) pets.map(petVal).sum else 0)
def dps: Double = valPerSecond(damage, _.dps)
View
29 parser/Id.scala
@@ -3,6 +3,7 @@ import scala.collection.mutable.Map
class Id(
val t: Id.Type.Type,
+ val r: Id.Relation.Type,
val id: String) {
override def hashCode = id.hashCode
@@ -33,11 +34,29 @@ object Id {
case _ => throw new Error(c)
}
}
- import Type.{Type, Unknown, Player, Nonplayer}
+ import Type.{Unknown, Player, Nonplayer}
- private val nothing = new Id(Unknown, "")
+ object Relation extends Enumeration {
+
+ type Type = Value
+ val Character, Group, Raid, Other = Value
+
+ class Error(val c: Char) extends Throwable
+
+ def apply(s: String): Type = apply(s charAt 0)
+ def apply(c: Char): Type = c match {
+ case 'C' => Character
+ case 'G' => Group
+ case 'R' => Raid
+ case 'O' => Other
+ case _ => throw new Error(c)
+ }
+ }
+ import Relation.{Character, Group, Raid, Other}
+
+ private val nothing = new Id(Unknown, Other, "")
private val ids = Map[String, Id]("T=X#R=X#0" -> nothing)
- private val parseRegex = """T=([XPN])#R=.#(\d+)""".r
+ private val parseRegex = """T=([XPN])#R=([CGRO])#(\d+)""".r
def apply() = nothing
@@ -46,9 +65,9 @@ object Id {
(ids get str) match {
case Some(id) => id
case None => str match {
- case parseRegex(c, id) =>
+ case parseRegex(c, r, id) =>
try {
- val newId = new Id(Type(c), id)
+ val newId = new Id(Type(c), Relation(r), id)
ids += str -> newId
newId
}
View
33 parser/Parser.scala
@@ -39,11 +39,19 @@ class Parser(
}
}
- private def waitForLine(it: Iterator[String]) {
+ private def waitForLine(it: Iterator[String], timeout: Long = 0): Boolean = {
+ val start = new Date()
+
// wait for a line to be available
while (!it.hasNext) {
Thread.sleep(500)
+
+ if (timeout > 0 &&
+ ((new Date()).getTime - start.getTime) > timeout)
+ return false
}
+
+ return true
}
private def parseCombat(lines: Iterator[String]): (Combat, Iterator[String]) = {
@@ -51,23 +59,25 @@ class Parser(
// find a valid entry, if one exists
val buf = lines.buffered
- while (buf.hasNext && !(buf.head contains "Combat Begin"))
- buf.next()
-
if (!buf.hasNext) {
(null, buf)
} else {
val combat = new Combat
+ val timeout = Config.inactivityThreshold * 1000
do {
- waitForLine(buf)
-
- val action = parseAction(buf.next())
- combat.handle(action)
-
- afterActionCallback(combat)
+ if (waitForLine(buf, timeout)) {
+ val action = parseAction(buf.next())
+ combat.handle(action)
+ afterActionCallback(combat)
+ } else {
+ if (combat.inCombat)
+ combat.notifyIdle(timeout)
+ else
+ return (null, buf)
+ }
}
- while(combat.inCombat)
+ while(!combat.ended)
afterCombatCallback(combat)
@@ -105,7 +115,6 @@ class Parser(
{
try {
var lines = new OnlineIterator(logFilename)
-
parseForever(lines)
} catch {
case _: java.io.FileNotFoundException => Thread.sleep(500)
View
4 parser/Simple.scala
@@ -12,7 +12,7 @@ object Simple {
if (c.duration > 0) Clippy.copy(c toString)
- if (Config.makeGraphs) {
+ if (Config.makeGraphs && c.entities.nonEmpty) {
// only make graphs if some entity took more than 1 million damage
val maxdmg = c.entities.values.map(_.damageTaken.full).max
@@ -23,7 +23,7 @@ object Simple {
val file = new File(filename)
- // file.delete()
+ file.delete()
if (file.createNewFile()) {
val writer = new FileWriter(filename)
Please sign in to comment.
Something went wrong with that request. Please try again.