Skip to content

Commit

Permalink
More combat log analysis, plus tests (minmatarfleet#712)
Browse files Browse the repository at this point in the history
  • Loading branch information
silvatek authored Sep 8, 2024
1 parent 561c3a5 commit 264109e
Show file tree
Hide file tree
Showing 3 changed files with 242 additions and 35 deletions.
113 changes: 81 additions & 32 deletions backend/combatlog/combatlog.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
from typing import List, Dict


class LogEvent:
raw_log: str
event_time: str
Expand All @@ -6,9 +9,12 @@ class LogEvent:


class DamageEvent:
damage: int
damage: int = 0
direction: str
entity: str
weapon: str
outcome: str
text: str


class LogAnalysis:
Expand All @@ -17,37 +23,39 @@ class LogAnalysis:
damage_taken: int


def parse(text):
events = []
for line in text.splitlines():
line = line.strip()
def parse_line(line: str) -> LogEvent:
line = line.strip()

if line.startswith("["):
pos = line.find("]")
if line.startswith("["):
pos = line.find("]")

event = LogEvent()
event = LogEvent()

event.event_time = line[1 : pos - 1].strip()
text = line[pos + 1 :].strip()
event.event_time = line[1 : pos - 1].strip()
text = line[pos + 1 :].strip()

pos = text.find(")")
if pos == -1:
event.event_type = "unknown"
else:
event.event_type = text[1:pos].strip()
text = text[pos + 1 :]
pos = text.find(")")
if pos == -1:
event.event_type = "unknown"
else:
event.event_type = text[1:pos].strip()
text = text[pos + 1 :]

event.text = strip_html(text)
event.text = strip_html(text)
else:
event = LogEvent()
event.event_time = ""
event.event_type = "unknown"
event.text = line

events.append(event)
return event

# print(event.event_time, "|", event.event_type, "|", event.text)
else:
event = LogEvent()
event.event_time = ""
event.event_type = "unknown"
event.text = line
events.append(event)

def parse(text: str) -> List[LogEvent]:
events = []
for line in text.splitlines():
event = parse_line(line)
events.append(event)

return events

Expand All @@ -66,27 +74,42 @@ def strip_html(text):
return text


def damage_events(events):
def damage_events(events: List[LogEvent]) -> List[DamageEvent]:
dmg_events = []
for event in events:
if event.event_type == "combat":
damage_event = DamageEvent()
pos = event.text.find(" to ")

text = event.text

pos = text.find(" to ")
if pos >= 0:
damage_event.damage = int(event.text[0:pos])
damage_event.damage = int(text[0:pos])
damage_event.direction = "to"
dmg_events.append(damage_event)
text = text[pos + 4 :]

pos = event.text.find(" from ")
pos = text.find(" from ")
if pos >= 0:
damage_event.damage = int(event.text[0:pos])
damage_event.damage = int(text[0:pos])
damage_event.direction = "from"
text = text[pos + 6 :]

parts = text.split("-")
if len(parts) >= 1:
damage_event.entity = parts[0].strip()
if len(parts) >= 2:
damage_event.weapon = parts[1].strip()
if len(parts) >= 3:
damage_event.outcome = parts[2].strip()

if damage_event.damage > 0:
damage_event.text = text
dmg_events.append(damage_event)

return dmg_events


def damage_done(dmg_events):
def total_damage(dmg_events):
total_done = 0
total_taken = 0
for event in dmg_events:
Expand All @@ -97,3 +120,29 @@ def damage_done(dmg_events):
total_taken += event.damage

return (total_done, total_taken)


def enemy_damage(
dmg_events: List[DamageEvent], direction: str
) -> Dict[str, int]:
result = {}

for event in dmg_events:
if event.direction == direction:
if event.entity not in result:
result[event.entity] = 0
result[event.entity] += event.damage

return result


def weapon_damage(dmg_events: List[DamageEvent]) -> Dict[str, int]:
result = {}

for event in dmg_events:
if event.direction == "to":
if event.weapon not in result:
result[event.weapon] = 0
result[event.weapon] += event.damage

return result
22 changes: 19 additions & 3 deletions backend/combatlog/router.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
from ninja import Router
from pydantic import BaseModel

from .combatlog import parse, damage_events, damage_done
from typing import Dict

from .combatlog import (
parse,
damage_events,
total_damage,
enemy_damage,
weapon_damage,
)

router = Router(tags=["combatlog"])

Expand All @@ -10,6 +17,9 @@ class LogAnalysis(BaseModel):
logged_events: int = 0
damage_done: int = 0
damage_taken: int = 0
damage_from_enemies: Dict[str, int] = {}
damage_to_enemies: Dict[str, int] = {}
damage_with_weapons: Dict[str, int] = {}


@router.post(
Expand All @@ -29,7 +39,13 @@ def analyze_logs(request):

analysis = LogAnalysis()
analysis.logged_events = len(events)

dmg_events = damage_events(events)
(analysis.damage_done, analysis.damage_taken) = damage_done(dmg_events)

(analysis.damage_done, analysis.damage_taken) = total_damage(dmg_events)

analysis.damage_from_enemies = enemy_damage(dmg_events, "from")
analysis.damage_to_enemies = enemy_damage(dmg_events, "to")
analysis.damage_with_weapons = weapon_damage(dmg_events)

return analysis
142 changes: 142 additions & 0 deletions backend/combatlog/tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
from django.test import TestCase

from .combatlog import (
LogEvent,
DamageEvent,
parse,
parse_line,
strip_html,
damage_events,
total_damage,
enemy_damage,
)


class ParseCombatLogTest(TestCase):
def test_parse_line_combat(self):
log_line = "[ 2024.09.07 14:58:50 ] (combat) <color=0xff00ffff><b>567</b> <color=0x77ffffff><font size=10>to</font> <b><color=0xffffffff>Angel Cartel Codebug</b><font size=10><color=0x77ffffff> - Inferno Rage Compiler Error - Hits"
event = parse_line(log_line)
self.assertEqual(event.event_time, "2024.09.07 14:58:50")
self.assertEqual(event.event_type, "combat")
self.assertEqual(
event.text,
"567 to Angel Cartel Codebug - Inferno Rage Compiler Error - Hits",
)

def test_parse_line_hint(self):
log_line = (
"[ 2024.09.07 13:59:17 ] (hint) Attempting to join a channel"
)
event = parse_line(log_line)
self.assertEqual(event.event_time, "2024.09.07 13:59:17")
self.assertEqual(event.event_type, "hint")
self.assertEqual(event.text, "Attempting to join a channel")

def test_parse_line_session(self):
log_line = " Session Started: 2024.09.07 13:59:16"
event = parse_line(log_line)
self.assertEqual(event.event_time, "")
self.assertEqual(event.event_type, "unknown")
self.assertEqual(event.text, "Session Started: 2024.09.07 13:59:16")

def test_parse_logs(self):
logs = "ABC\nXYZ"
events = parse(logs)
self.assertEqual(2, len(events))

def test_parse_empty_logs(self):
logs = ""
events = parse(logs)
self.assertEqual(0, len(events))


class StripHtmlTest(TestCase):
def test_strip_html_basic(self):
stripped = strip_html("hello <b>world</b>")
self.assertEqual("hello world", stripped)

def test_strip_html_no_tags(self):
stripped = strip_html("hello world")
self.assertEqual("hello world", stripped)

def test_strip_html_empty(self):
stripped = strip_html("")
self.assertEqual("", stripped)


def log_event(event_time, event_type, event_text):
event = LogEvent()
event.event_time = event_time
event.event_type = event_type
event.text = event_text
return event


def damage_event(
damage: int, direction: str, entity: str, weapon: str, outcome: str
) -> DamageEvent:
event = DamageEvent()
event.damage = damage
event.direction = direction
event.entity = entity
event.weapon = weapon
event.outcome = outcome
return event


class DamageParseTest(TestCase):
def test_find_damage_events(self):
events = []
events.append(log_event("", "combat", "123 from Rat"))
events.append(log_event("", "combat", "345 to Rat"))
events.append(log_event("", "peace", "collaborating"))

dmg_events = damage_events(events)

self.assertEqual(2, len(dmg_events))

def test_total_damage_done(self):
events = []
events.append(log_event("", "combat", "120 to Rat"))
events.append(log_event("", "combat", "140 to Rat"))
events.append(log_event("", "combat", "125 from Rat"))
events.append(log_event("", "combat", "155 from Rat"))

dmg_events = damage_events(events)

self.assertEqual(4, len(dmg_events))

(dmg_done, dmg_taken) = total_damage(dmg_events)

self.assertEqual(260, dmg_done)
self.assertEqual(280, dmg_taken)

def test_damage_parse(self):
events = []
events.append(
log_event("", "combat", "120 from Rat - Sharp Teeth - Hits")
)

dmg_events = damage_events(events)

self.assertEqual(120, dmg_events[0].damage)
self.assertEqual("from", dmg_events[0].direction)
self.assertEqual("Rat", dmg_events[0].entity)
self.assertEqual("Sharp Teeth", dmg_events[0].weapon)
self.assertEqual("Hits", dmg_events[0].outcome)

def test_enemy_damage(self):
events = []
events.append(damage_event(100, "from", "Rat", "Sharp Teeth", "Hits"))
events.append(damage_event(123, "from", "Bat", "Sharp Teeth", "Hits"))
events.append(damage_event(110, "from", "Rat", "Sharp Teeth", "Hits"))
events.append(damage_event(125, "to", "Rat", "Sharp Teeth", "Hits"))

enemies = enemy_damage(events, "from")

self.assertEqual(210, enemies["Rat"])
self.assertEqual(123, enemies["Bat"])

enemies = enemy_damage(events, "to")

self.assertEqual(125, enemies["Rat"])

0 comments on commit 264109e

Please sign in to comment.