-> !wants
-> !orgs info
-> special cmd's
-> !assist
-> "afk" for players without active account
-> !loot add <item_ref> <count> => nolonger breaks !account
Changes:
-> grouped !tara, !gaunt, .. into !wb
-> Display the most recent news entry on logon (default: enabled)
-> improved grouping of !items
-> Added the option to authentificate WS connections (Datanet module). This is used in special cases, where the Websocket Server requires the clien tto authentificate itself. (Server sends "#auth", client responds with the auth string)
-> Add main name to relaying (priv <-> org) [default: disabled]
-> Added logon/logoff messages back
-> restricted default access to "dangerous" commands to moderator
-> Added optional logging (Private Channel, Org Channel, Tells, ... disabled by default)

Rewrite of the Tower Module.
-> More verbosity, if enabled in config. by default, GAS and Hot timer only.
-> !hot displays currently hot (and in penalty) sites, and these which go hot in < 60 minutes
-> !attacks filterable by PF and Site
-> display current contract QL's grouped by org: !contracts (requires managed cache)
This commit is contained in:
2021-11-25 14:09:43 +01:00
parent 2d7ecf4883
commit 17c776faec
44 changed files with 1669 additions and 1249 deletions
@@ -0,0 +1,67 @@
import math
from core.aochat.BaseModule import BaseModule
from core.chat_blob import ChatBlob
from core.command_param_types import Int, NamedParameters
from core.db import DB
from core.decorators import instance, command
from core.igncore import IgnCore
from core.text import Text
# noinspection DuplicatedCode
@instance()
class ContractController(BaseModule):
PAGE_SIZE = 40
# noinspection DuplicatedCode
def inject(self, registry):
self.bot: IgnCore = registry.get_instance("bot")
self.db: DB = registry.get_instance("db")
self.text: Text = registry.get_instance("text")
@command(command="contracts",
params=[Int('mininum', is_optional=True), NamedParameters(['page'])],
access_level="member",
description="Shows contracts")
def cotracts(self, _, min_ql, named_params):
if not min_ql:
min_ql = 200
data = self.db.query("SELECT e.*, CAST(SUM(ql)*2 AS INTEGER) AS contracts, COUNT(*) AS sites FROM towers a "
"LEFT JOIN all_orgs e on a.org_id = e.org_id WHERE close_time IS NOT NULL "
"GROUP BY org_name ORDER BY contracts desc", [])
page = int(named_params.page or "1")
offset = (page - 1) * self.PAGE_SIZE
data = [x for x in data if x.contracts and x.contracts > min_ql]
return self.format_pagination(data, offset, page, f"Tower Contracts ({len(data)})",
f"There are no orgs with more than "
f"<highlight>{min_ql}</highlight> contract points.",
f'contracts {min_ql}')
def format_pagination(self, data, offset, page, title, nullmsg, cmd):
selected = data[offset:offset + self.PAGE_SIZE]
count = len(selected)
pages = ""
if page > 1:
pages += "Pages: " + self.text.make_tellcmd(f"«« Page {page - 1:d}", f'{cmd} --page={page - 1}')
if offset + self.PAGE_SIZE < len(data):
pages += f" Page {page}/{math.ceil(len(data) / self.PAGE_SIZE)}"
pages += " " + self.text.make_tellcmd(f"Page {page + 1:d} »»", f'{cmd} --page={page + 1}')
pages += "\n"
if count == 0:
return nullmsg
else:
blob = ""
blob += "" + pages + "\n"
index = offset
for entry in selected:
index += 1
blob += self.row_formatter(entry, index, data)
blob += "\n" + pages
blob += "</font>"
return ChatBlob(title, blob)
def row_formatter(self, entry, index, data):
return f"{self.text.zfill(index, len(data))}. " \
f"<white>{self.text.zfill(entry.contracts, data[0].contracts)}</white> " \
f"<{entry.faction.lower()}>{entry.org_name}</{entry.faction.lower()}>\n"
+166
View File
@@ -0,0 +1,166 @@
import time
from core.aochat.BaseModule import BaseModule
from core.command_alias_service import CommandAliasService
from core.command_param_types import Options, Int, Any, NamedParameters
from core.db import DB
from core.decorators import instance, command, event
from core.dict_object import DictObject
from core.event_service import EventService
from core.igncore import IgnCore
from core.lookup.pork_service import PorkService
from core.public_channel_service import PublicChannelService
from core.text import Text
from core.util import Util
from modules.standard.helpbot.playfield_controller import PlayfieldController
from modules.standard.tower.tower_events import TowerEventController
@instance()
class TowerHotController(BaseModule):
PAGE_SIZE = 30
# noinspection DuplicatedCode
def inject(self, registry):
self.bot: IgnCore = registry.get_instance("bot")
self.db: DB = registry.get_instance("db")
self.util: Util = registry.get_instance("util")
self.text: Text = registry.get_instance("text")
self.event_service: EventService = registry.get_instance("event_service")
self.pork_service: PorkService = registry.get_instance("pork_service")
self.playfield_controller: PlayfieldController = registry.get_instance("playfield_controller")
self.public_channel_service: PublicChannelService = registry.get_instance("public_channel_service")
self.command_alias_service: CommandAliasService = registry.get_instance("command_alias_service")
@event(event_type=TowerEventController.TOWER_ATTACK_EVENT, description="Mark Sites in penalty as in penalty",
is_enabled=False)
def tower_victory_event(self, _, event_data):
if event_data.attacker.org_id:
self.db.exec("UPDATE towers SET penalty_until=? where org_id=?",
[time.time() + 60 * 60, event_data.attacker.org_id])
@command(command="hot",
params=[Options(['tl1', 'tl2', 'tl3', 'tl4', 'tl5', 'tl6', 'tl7']), Any('faction', is_optional=True),
NamedParameters(["page"])],
access_level="member",
description="Shows hot playfields")
def hot_tl(self, _, tl, faction: str, named_params):
if faction:
if faction.startswith("--page="):
named_params = DictObject({'page': faction[7:]})
faction = None
if faction is not None and faction.lower() not in ['omni', 'clan', 'neut', 'neutral']:
return f"Unknown faction: {faction}"
tl = tl[2:]
page = int(named_params.page or "1")
offset = (page - 1) * self.PAGE_SIZE
towers = self.get_hot_sites_tl(int(tl), faction)
return self.text.format_pagination(towers, offset, page, self.formatter, f"Hot Sites TL{tl} ({len(towers)})",
f"There are no hot sites for TL <highlight>{tl}</highlight>.",
f'hot tl{tl} {faction or ""}', self.PAGE_SIZE)
def formatter(self, row, index, data):
d = {}
if index > 1:
d = data[index - 2]
status = ""
if row.status_time <= 3600:
status += f"<red>5%</red> (closes in {self.util.time_to_readable(row.status_time)})"
elif row.status_time <= (3600 * 6):
status += f"<orange>25%</orange> (closes in {self.util.time_to_readable(row.status_time)})"
else:
status += f"<green>75%</green> (opens in {self.util.time_to_readable(row.status_time - (3600 * 6))})"
if row.penalty_until > time.time():
status += f" <red>In Penalty for: {self.util.time_to_readable(row.penalty_until - time.time())}</red>"
blob = ""
if self.get_ct_type(d.get("ql", 0)) < (tl := self.get_ct_type(row.ql)):
blob += f"<notice>TL{tl}</notice><br>"
space = f"{row.short_name} x{row.site_number}"
place = "_" * (7 - len(space))
return blob + "<tab>" + self.text.make_tellcmd(space,
f'lc {row.short_name} {row.site_number}') + \
f"<black>{place}</black> QL {row.min_ql}/<highlight>{row.ql}</highlight>/{row.max_ql} - " \
f"{self.text.get_formatted_faction(row.faction, row.org_name)}, {status}\n"
@command(command="hot",
params=[Int('level', is_optional=True), Any('faction', is_optional=True), NamedParameters(["page"])],
access_level="member",
description="Shows hot playfields by level")
def hot_level(self, _, level, faction, named_params):
if faction:
if faction.startswith("--page="):
named_params = DictObject({'page': faction[7:]})
faction = None
if faction is not None and faction.lower() not in ['omni', 'clan', 'neut', 'neutral']:
return f"Unknown faction: {faction}"
if level:
if level < 0 | level > 220:
return f"Level out of range: {level}"
page = int(named_params.page or "1")
offset = (page - 1) * self.PAGE_SIZE
towers = self.get_hot_sites(level, faction)
level = f"{level}" if level else ""
faction = f"{faction} " if faction else ""
return self.text.format_pagination(towers, offset, page, self.formatter, f"Hot Towersites ({len(towers)})",
f"There are no hot sites.", f'hot {level}{faction}', self.PAGE_SIZE)
def get_hot_sites(self, level=None, faction=None):
where = ""
now = time.time() % 86400
params = [now, now, now]
if level:
where += " AND l.pvp_min <=? and pvp_max >= ? "
params.append(level)
params.append(level)
if faction:
where += " AND c.faction LIKE ? "
params.append(faction.capitalize())
data = self.db.query("SELECT a.*, f.short_name, c.org_name, b.min_ql, b.max_ql, "
"(CASE WHEN (a.close_time-?) < 0 THEN a.close_time-? +86400 ELSE a.close_time-? END) "
" AS status_time FROM tower_sites b "
"LEFT JOIN towers a ON a.pf_id = b.playfield_id AND a.site_number = b.site_number "
"LEFT JOIN level l on a.ql = l.level "
"LEFT JOIN playfields f on a.pf_id = f.id "
f"LEFT JOIN all_orgs c ON a.org_id = c.org_id WHERE close_time IS NOT NULL {where} ORDER BY a.ql",
params)
return [x for x in data if x.status_time - (3600 * 6) < 60 * 60 or x.penalty_until > time.time()]
def get_hot_sites_tl(self, tl=7, faction=None):
min_ql, max_ql = self.util.get_level_range_tl(tl)
where = ""
now = time.time() % 86400
params = [now, now, now]
where += " AND ql between ? and ? "
params.append(min_ql)
params.append(max_ql)
if faction:
where += " AND c.faction LIKE "
params.append("%" + faction.capitalize() + "%")
data = self.db.query("SELECT a.*, f.short_name, c.org_name, b.min_ql, b.max_ql, "
"(CASE WHEN (a.close_time-?) < 0 THEN a.close_time-? +86400 ELSE a.close_time-? END) "
" AS status_time FROM tower_sites b "
"LEFT JOIN towers a ON a.pf_id = b.playfield_id AND a.site_number = b.site_number "
"LEFT JOIN level l on a.ql = l.level "
"LEFT JOIN playfields f on a.pf_id = f.id "
f"LEFT JOIN all_orgs c ON a.org_id = c.org_id WHERE close_time IS NOT NULL {where} ORDER BY a.ql",
params)
return [x for x in data if x.status_time - (3600 * 6) < 60 * 60 or x.penalty_until > time.time()]
def get_ct_type(self, ql):
if ql == 0:
return 0
elif ql < 34:
return 1
elif ql < 82:
return 2
elif ql < 129:
return 3
elif ql < 177:
return 4
elif ql < 201:
return 5
elif ql < 226:
return 6
else:
return 7
+279
View File
@@ -0,0 +1,279 @@
import time
from datetime import datetime
import pytz
from core.chat_blob import ChatBlob
from core.command_alias_service import CommandAliasService
from core.command_param_types import Any, Int, Const
from core.db import DB
from core.decorators import instance, command, event, setting
from core.event_service import EventService
from core.igncore import IgnCore
from core.lookup.pork_service import PorkService
from core.public_channel_service import PublicChannelService
from core.setting_types import BooleanSettingType
from core.text import Text
from core.util import Util
from modules.standard.helpbot.playfield_controller import PlayfieldController
# legacy(0), EU - friendly(1) or US - friendly(2) => timing
from modules.standard.tower.tower_attack_controller import TowerAttackController
from modules.standard.tower.tower_events import TowerEventController
FIXED_TIMES = {1: 4,
2: 20}
@instance()
class LandController:
def inject(self, registry):
self.bot: IgnCore = registry.get_instance("bot")
self.db: DB = registry.get_instance("db")
self.text: Text = registry.get_instance("text")
self.util: Util = registry.get_instance("util")
self.event_service: EventService = registry.get_instance("event_service")
self.pork_service: PorkService = registry.get_instance("pork_service")
self.playfield_controller: PlayfieldController = registry.get_instance("playfield_controller")
self.public_channel_service: PublicChannelService = registry.get_instance("public_channel_service")
self.command_alias_service: CommandAliasService = registry.get_instance("command_alias_service")
self.tac: TowerAttackController = registry.get_instance("tower_attack_controller")
def pre_start(self):
self.command_alias_service.add_alias('towers', "lc")
self.command_alias_service.add_alias('tower', "lc")
self.command_alias_service.add_alias('lca', "lc")
self.db.load_sql_file(self.module_dir + "/" + "tower_sites.sql", pre_optimized=True)
self.db.shared.exec("CREATE TABLE IF NOT EXISTS `towers` ("
"`tower_id` INT(11) NOT NULL,"
"`pf_id` INT(11) NOT NULL,"
"`site_number` INT(11) NOT NULL,"
"`x_coord` INT(11) NOT NULL,"
"`y_coord` INT(11) NOT NULL,"
"`high_id` INT(11) NOT NULL,"
"`ql` INT(11) NOT NULL,"
"`org_id` INT(11),"
"`faction` VARCHAR(11),"
"`planted` INT(11),"
"`close_time` INT(11),"
"`penalty_until` INT(11),"
"PRIMARY KEY (`tower_id`) USING HASH,"
"INDEX `site` (`pf_id`, `site_number`, `org_id`, `close_time`, `penalty_until`) USING HASH,"
"INDEX `tower` (`planted`, `ql`, `x_coord`, `y_coord`) USING HASH) "
"COLLATE='utf8mb4_general_ci' ENGINE=MEMORY;")
self.db.create_view("towers")
@setting(name="lc_cmd_full", value="false", description="Toggle the verbosity of !lc commands")
def lc_full(self) -> BooleanSettingType:
return BooleanSettingType()
@event(event_type=TowerEventController.TOWER_VICTORY_EVENT, description="Purge sites which got wiped from DB",
is_enabled=False)
def tower_victory_event(self, _, event_data):
row = self.tac.get_last_attack(event_data.winner.faction, event_data.winner.org_name, event_data.loser.faction,
event_data.loser.org_name, event_data.location.playfield.id, time.time())
if row:
self.db.exec("DELETE FROM towers where pf_id=? and site_number=?", [row.pf_id, row.site])
@command(command="lc", params=[], access_level="member",
description="See a list of playfields containing land control tower sites")
def lc_list_cmd(self, request):
data = self.db.query(
"SELECT id, long_name, short_name FROM playfields "
"WHERE id IN (SELECT DISTINCT playfield_id FROM tower_sites) ORDER BY short_name")
blob = ""
for row in data:
blob += f"[{row.id:d}] {self.text.make_tellcmd(row.long_name, f'lc {row.short_name}')} <highlight>{row.short_name}</highlight>\n"
return ChatBlob(f"Land Control Playfields ({len(data):d})", blob)
@command(command="lc", params=[Const("org"), Any("search")], access_level="member",
description="See a list of land control tower sites in a particular playfield")
def sites_org_cmd(self, request, _, search):
if search.isdigit():
org_id = int(search)
result = search
else:
orgs = self.find_orgs(search)
num_orgs = len(orgs)
if num_orgs == 0:
char_info = self.pork_service.get_character_info(search)
if char_info:
if not char_info.org_id:
return f"<highlight>{search.capitalize()}</highlight> does not appear to belong to an org."
else:
org_id = char_info.org_id
result = char_info.org_name
else:
return f"Character or org <highlight>{search}</highlight> does not own any sites."
elif num_orgs == 1:
result = orgs[0].org_name
org_id = orgs[0].org_id
else:
blob = ""
for org in orgs:
blob += self.text.make_tellcmd(f"{org.org_name} ({org.org_id})",
f"lc org {org.org_id}") + "\n"
return ChatBlob(f"Orgs matching your search criteria ({num_orgs})", blob)
data = self.get_towers_by_org(org_id)
blob = ""
ql = 0
for x in data:
blob += f"<pagebreak>{self.format_site_info(x, time.time(), len(data))}"
blob += f"<tab>Dist: <highlight>{x.guard}</highlight> Conductors and <highlight>{x.turrets}</highlight> Turrets planted\n\n"
ql += x.ql
blob += f"Stats: QL<highlight>{ql}</highlight>, contracts up to QL<highlight>{ql * 2}</highlight>"
return ChatBlob(f"Sites owned by {result}", blob)
@command(command="lc", params=[Any("playfield"), Int("site_number", is_optional=True)], access_level="member",
description="See a list of land control tower sites in a particular playfield")
def lc_playfield_cmd(self, request, playfield_name, site_number):
playfield = self.playfield_controller.get_playfield_by_name_or_id(playfield_name)
if not playfield:
return f"Could not find playfield <highlight>{playfield_name}</highlight>."
data = self.get_towers(playfield.id, site_number)
blob = ""
t = int(time.time())
if site_number:
data = self.get_towers(playfield.id, site_number)
blob += "<pagebreak>" + self.format_site_info(data, t)
else:
for row in data:
blob += f"<pagebreak>{self.format_site_info(row, t, len(data))}\n"
if site_number:
title = f"Tower Info: {playfield.long_name} x{site_number}"
else:
title = f"Tower Info: {playfield.long_name} ({len(data)})"
return ChatBlob(title, blob)
@command(command="free", params=[],
access_level="member",
description="Shows potentially free towerfields")
def free(self, _, ):
blob = ""
data = self.get_free()
for row in data:
blob += f"<pagebreak>{self.format_site_info(row, time.time(), len(data))}\n"
return ChatBlob(f"FREE Towersites ({len(data)})", blob) if blob else f"No free towersites found."
def format_site_info(self, row, t, count=0):
data = row
if count == 0 and data:
row = data[0]
blob = f"<highlight>{row.short_name} x{row.site_number}</highlight> ({row.site_name})\n"
blob += f"<tab>Level Range: <white>{row.min_ql} - {row.max_ql}</white> "
if row.timing == 0:
blob += f"[<grey>Legacy</grey>]\n"
if row.timing == 1:
blob += f"[<grey><black>0</black>4 UTC</grey>]\n"
if row.timing == 2:
blob += f"[<grey>20 UTC</grey>]\n"
blob += f"<tab>Coordinates: "
blob += self.text.make_chatcmd(f"{row.x_coord:d}x{row.y_coord:d}",
f"/waypoint {row.x_coord:d} {row.y_coord:d} {row.pf_id:d}") + "\n"
if row.get("org_name", None):
current_day_time = t % 86400
if row.timing > 0:
row.close_time = FIXED_TIMES[row.timing] * 3600 + row.planted % 3600
value = datetime.fromtimestamp(row.close_time, tz=pytz.UTC)
current_status_time = row.close_time - current_day_time
if current_status_time < 0:
current_status_time += 86400
status = ""
if current_status_time <= 3600:
status += f"<red>5%</red> (closes in {self.util.time_to_readable(current_status_time)})"
elif current_status_time <= (3600 * 6):
status += f"<orange>25%</orange> (closes in {self.util.time_to_readable(current_status_time)})"
else:
status += f"<green>75%</green> (opens in {self.util.time_to_readable(current_status_time - (3600 * 6))})"
if row.penalty_until > t:
status += f" <red>Penalty</red> (for {self.util.time_to_readable(row.penalty_until - t)})"
blob += f"<tab>CT: QL<highlight>{row.ql}</highlight> ({self.text.get_formatted_faction(row.faction, row.org_name)}) T{self.get_ct_type(row.ql)} - Planted {self.util.time_to_readable(t - row.planted)} ago\n"
blob += f"<tab>Gas: {status}\n"
if self.lc_full().get_value():
towers = ""
cond, turret = 0, 0
if count == 0:
for tower in data:
if tower.name.__contains__("Turret"):
turret += 1
elif tower.name.__contains__("Conductor"):
cond += 1
towers += f" - QL<highlight>{self.text.zfill(tower.ql, 220)}</highlight> {tower.name}\n"
blob += f"<tab>Dist: <highlight>{cond}</highlight> Conductors and <highlight>{turret}</highlight> Turrets\n"
blob += "\n Towers:\n"
blob += towers
else:
if not row.enabled:
blob += "<red>Disabled</red>\n"
else:
blob += "<tab><red>This site is potentially unplanted</red>\n"
return blob
def get_ct_type(self, ql):
if ql < 34:
return 1
elif ql < 82:
return 2
elif ql < 129:
return 3
elif ql < 177:
return 4
elif ql < 201:
return 5
elif ql < 226:
return 6
else:
return 7
def get_free(self):
return self.db.query("""SELECT d.playfield_id AS pf_id,d.site_number, d.site_name, d.min_ql, d.max_ql, d.x_coord, d.y_coord, d.timing, d.enabled, a.tower_id, a.ql, a.close_time, a.penalty_until, a.planted, b.*, c.*, e.* FROM tower_sites d
LEFT JOIN towers a on a.pf_id = d.playfield_id and a.site_number = d.site_number
LEFT JOIN aodb b ON a.high_id = b.highid
LEFT JOIN playfields c on d.playfield_id = c.id
LEFT JOIN all_orgs e on a.org_id = e.org_id
WHERE a.org_id IS NULL AND d.enabled = 1 GROUP BY d.playfield_id, d.site_number """)
def get_towers(self, pf, site=None):
if site:
return self.db.query("""SELECT d.playfield_id AS pf_id,d.site_number, d.site_name, d.min_ql, d.max_ql, d.x_coord, d.y_coord, d.timing, d.enabled, a.tower_id, a.ql, a.close_time, a.penalty_until, a.planted, b.*, c.*, e.* FROM tower_sites d
LEFT JOIN towers a on a.pf_id = d.playfield_id and a.site_number = d.site_number
LEFT JOIN aodb b ON a.high_id = b.highid
LEFT JOIN playfields c on d.playfield_id = c.id
LEFT JOIN all_orgs e on a.org_id = e.org_id
WHERE playfield_id=? AND d.site_number=? ORDER BY close_time IS NULL, ql desc""",
[pf, site])
else:
return self.db.query("""SELECT d.playfield_id AS pf_id, d.site_number, d.site_name, d.min_ql, d.max_ql, d.x_coord, d.y_coord, d.timing, d.enabled, a.tower_id, a.ql, a.close_time, a.penalty_until, a.planted, b.*, c.*, e.* FROM tower_sites d
LEFT JOIN towers a on a.pf_id = d.playfield_id and a.site_number = d.site_number
LEFT JOIN aodb b ON a.high_id = b.highid
LEFT JOIN playfields c on d.playfield_id = c.id
LEFT JOIN all_orgs e on a.org_id = e.org_id
WHERE playfield_id=?
GROUP BY playfield_id, site_number
ORDER BY site_number, ql DESC
""", [pf])
def get_towers_by_org(self, org_id):
return self.db.query(
"SELECT COUNT(CASE WHEN name LIKE '%Turret%' THEN 1 WHEN name LIKE '%SAM Battery%' THEN 1 END) turrets, "
"COUNT(CASE WHEN name LIKE '%Guard%' THEN 1 END) guard, "
"a.*, b.*, c.*, d.site_name, d.min_ql, d.max_ql, d.timing, d.enabled, e.* FROM towers a "
"LEFT JOIN aodb b ON a.high_id = b.highid "
"LEFT JOIN playfields c on a.pf_id = c.id "
"LEFT JOIN tower_sites d on a.pf_id = d.playfield_id and a.site_number = d.site_number "
"LEFT JOIN all_orgs e on a.org_id = e.org_id "
"WHERE a.org_id=? GROUP BY a.pf_id, a.site_number ORDER BY ql, close_time IS NOT NULL", [org_id])
def find_orgs(self, search):
return self.db.query("SELECT DISTINCT a.org_name, a.org_id FROM all_orgs a "
"LEFT JOIN towers b ON a.org_id = b.org_id WHERE org_name <EXTENDED_LIKE=0> ? AND b.org_id IS NOT NULL",
[search], extended_like=True)
@@ -0,0 +1,247 @@
import time
from core.decorators import instance, event
from core.igncore import IgnCore
from core.logger import Logger
from core.text import Text
from modules.standard.helpbot.playfield_controller import PlayfieldController
from modules.standard.tower.tower_events import TowerEventController
@instance()
class TowerAttackController:
def __init__(self):
self.logger = Logger(__name__)
def inject(self, registry):
self.bot: IgnCore = registry.get_instance("bot")
self.db = registry.get_instance("db")
self.text: Text = registry.get_instance("text")
self.tower: TowerEventController = registry.get_instance("tower_controller")
self.playfield_controller: PlayfieldController = registry.get_instance("playfield_controller")
def start(self):
self.db.shared.exec(
"CREATE TABLE IF NOT EXISTS tower_attacker ("
"id INT PRIMARY KEY AUTO_INCREMENT, "
"att_org_name VARCHAR(50) NOT NULL, "
"att_faction VARCHAR(10) NOT NULL, "
"att_char_id INT, att_char_name VARCHAR(20) NOT NULL, "
"att_level INT NOT NULL, "
"att_ai_level INT NOT NULL, "
"att_profession VARCHAR(15) NOT NULL, "
"x_coord INT NOT NULL, "
"y_coord INT NOT NULL, "
"is_victory SMALLINT NOT NULL, "
"tower_battle_id INT NOT NULL, "
"created_at INT NOT NULL)")
self.db.shared.exec(
"CREATE TABLE IF NOT EXISTS tower_battle ("
"id INT PRIMARY KEY AUTO_INCREMENT, "
"playfield_id INT NOT NULL, "
"site_number INT NOT NULL, "
"def_org_name VARCHAR(50) NOT NULL, "
"def_faction VARCHAR(10) NOT NULL, "
"is_finished INT NOT NULL, "
"battle_type VARCHAR(20) NOT NULL, "
"last_updated INT NOT NULL)"
"")
self.db.create_view("tower_battle")
self.db.create_view("tower_attacker")
@event(event_type=TowerEventController.TOWER_ATTACK_EVENT, description="Create logentries for tower attacks",
is_enabled=False)
def tower_attack_event(self, _, event_data):
t = int(time.time())
site_number = self.find_closest_site_number(event_data.location.playfield.id, event_data.location.x_coord,
event_data.location.y_coord)
attacker = event_data.attacker or {}
defender = event_data.defender
battle = self.find_or_create_battle(event_data.location.playfield.id, site_number, defender.org_name,
defender.faction, "attack", t)
self.db.exec(
"INSERT INTO tower_attacker (att_org_name, att_faction, att_char_id, att_char_name, "
"att_level, att_ai_level, att_profession, "
"x_coord, y_coord, is_victory, tower_battle_id, created_at) "
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
[attacker.get("org_name", ""), attacker.get("faction", ""), attacker.get("char_id", 0),
attacker.get("name", ""), attacker.get("level", 0),
attacker.get("ai_level", 0), attacker.get("profession", ""), event_data.location.x_coord,
event_data.location.y_coord, 0, battle.id, t])
@event(event_type=TowerEventController.TOWER_VICTORY_EVENT, description="Record tower victories", is_enabled=False)
def tower_victory_event(self, _, event_data):
t = int(time.time())
if event_data.type == "attack":
row = self.get_last_attack(event_data.winner.faction, event_data.winner.org_name, event_data.loser.faction,
event_data.loser.org_name, event_data.location.playfield.id, t, is_finished=0)
if not row:
site_number = 0
is_finished = 1
self.db.exec(
"INSERT INTO tower_battle (playfield_id, site_number, def_org_name, def_faction, "
"is_finished, battle_type, last_updated) "
"VALUES (?, ?, ?, ?, ?, ?, ?)",
[event_data.location.playfield.id, site_number, event_data.loser.org_name, event_data.loser.faction,
is_finished, event_data.type, t])
battle_id = self.db.last_insert_id()
attacker = event_data.winner or {}
self.db.exec(
"INSERT INTO tower_attacker (att_org_name, att_faction, att_char_id, "
"att_char_name, att_level, att_ai_level, att_profession, "
"x_coord, y_coord, is_victory, tower_battle_id, created_at) "
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
[attacker.get("org_name", ""), attacker.get("faction", ""), attacker.get("char_id", 0),
attacker.get("name", ""), attacker.get("level", 0),
attacker.get("ai_level", 0), attacker.get("profession", ""), 0, 0, 0, battle_id, t])
else:
is_victory = 1
self.db.exec("UPDATE tower_attacker SET is_victory = ? WHERE id = ?", [is_victory, row.attack_id])
is_finished = 1
self.db.exec("UPDATE tower_battle SET is_finished = ?, last_updated = ? WHERE id = ?",
[is_finished, t, row.battle_id])
elif event_data.type == "terminated":
site_number = 0
is_finished = 1
row = self.find_similar_attacks(event_data.loser.faction, event_data.loser.org_name,
event_data.location.playfield.id, t, finished=0)
if row:
self.db.exec("UPDATE tower_battle SET is_finished = ?, last_updated = ? WHERE id = ?",
[is_finished, t, row.battle_id])
else:
self.db.exec(
"INSERT INTO tower_battle (playfield_id, site_number, def_org_name, def_faction, "
"is_finished, battle_type, last_updated) VALUES (?, ?, ?, ?, ?, ?, ?)",
[event_data.location.playfield.id, site_number, event_data.loser.org_name, event_data.loser.faction,
is_finished, event_data.type, t])
else:
raise Exception("Unknown victory event type: '%s'" % event_data.type)
def find_closest_site_number(self, playfield_id, x_coord, y_coord):
# noinspection SqlUnused
sql = """
SELECT
site_number,
((x_distance * x_distance) + (y_distance * y_distance)) radius
FROM
(SELECT
playfield_id,
site_number,
min_ql,
max_ql,
x_coord,
y_coord,
site_name,
(x_coord - ?) as x_distance,
(y_coord - ?) as y_distance
FROM
tower_sites
WHERE
playfield_id = ?) t
ORDER BY
radius
LIMIT 1"""
row = self.db.query_single(sql, [x_coord, y_coord, playfield_id])
if row:
return row.site_number
else:
return 0
def find_or_create_battle(self, playfield_id, site_number, org_name, faction, battle_type, t):
last_updated = t - (8 * 3600)
is_finished = 0
sql = """
SELECT
*
FROM
tower_battle
WHERE
playfield_id = ?
AND site_number = ?
AND is_finished = ?
AND def_org_name = ?
AND def_faction = ?
AND last_updated >= ?
"""
battle = self.db.query_single(sql, [playfield_id, site_number, is_finished, org_name, faction, last_updated])
if battle:
self.db.exec("UPDATE tower_battle SET last_updated = ? WHERE id = ?", [t, battle.id])
return battle
else:
self.db.exec(
"INSERT INTO tower_battle (playfield_id, site_number, def_org_name, def_faction, "
"is_finished, battle_type, last_updated) VALUES (?, ?, ?, ?, ?, ?, ?)",
[playfield_id, site_number, org_name, faction, is_finished, battle_type, t])
return self.db.query_single(sql, [playfield_id, site_number, is_finished, org_name, faction, last_updated])
def get_last_attack(self, att_faction, att_org_name, def_faction, def_org_name, playfield_id, t, is_finished=None):
last_updated = t - (8 * 3600)
sql = f"""
SELECT
b.id AS battle_id,
a.id AS attack_id,
b.playfield_id as pf_id,
b.site_number as site
FROM
tower_battle b
JOIN tower_attacker a ON
a.tower_battle_id = b.id
WHERE
a.att_faction = ?
AND a.att_org_name = ?
AND b.def_faction = ?
AND b.def_org_name = ?
AND b.playfield_id = ?
{'AND b.is_finished = ?' if is_finished is not None else ''}
AND b.last_updated >= ?
ORDER BY
last_updated DESC
LIMIT 1"""
return self.db.query_single(sql,
[att_faction, att_org_name, def_faction, def_org_name, playfield_id, is_finished,
last_updated]
if is_finished is not None else
[att_faction, att_org_name, def_faction, def_org_name, playfield_id, last_updated])
def find_similar_attacks(self, def_faction, def_org_name, playfield_id, t, finished=None):
last_updated = t - (8 * 3600)
sql = f"""
SELECT
b.id AS battle_id,
a.id AS attack_id,
b.playfield_id as pf_id,
b.site_number as site
FROM
tower_battle b
JOIN tower_attacker a ON
a.tower_battle_id = b.id
WHERE
b.def_faction = ?
AND b.def_org_name = ?
AND b.playfield_id = ?
{'AND b.is_finished = ?' if finished else ''}
AND b.last_updated >= ?
ORDER BY
last_updated DESC
"""
return self.db.query_single(sql,
[def_faction, def_org_name, playfield_id, finished,
last_updated] if finished else
[def_faction, def_org_name, playfield_id,
last_updated])
+170
View File
@@ -0,0 +1,170 @@
import time
from core.chat_blob import ChatBlob
from core.command_param_types import Const, Int, NamedParameters, Any
from core.decorators import instance, command
from core.igncore import IgnCore
from core.logger import Logger
from core.text import Text
from core.util import Util
from modules.standard.helpbot.playfield_controller import PlayfieldController
@instance()
class TowerController:
def __init__(self):
self.logger = Logger(__name__)
def inject(self, registry):
self.bot: IgnCore = registry.get_instance("bot")
self.db = registry.get_instance("db")
self.text: Text = registry.get_instance("text")
self.util: Util = registry.get_instance("util")
self.tower: TowerController = registry.get_instance("tower_controller")
self.playfield_controller: PlayfieldController = registry.get_instance("playfield_controller")
self.public_channel_service = registry.get_instance("public_channel_service")
@command(command="attacks", params=[Const("battle"), Int("battle_id")], access_level="member",
description="Show battle info for a specific battle")
def attacks_battle_cmd(self, _, _1, battle_id):
battle = self.db.query_single(
"SELECT b.*, p.short_name FROM tower_battle b "
"LEFT JOIN playfields p ON p.id = b.playfield_id WHERE b.id = ?",
[battle_id])
if not battle:
return "Could not find battle with ID <highlight>%d</highlight>." % battle_id
t = int(time.time())
attackers = self.db.query("SELECT * FROM tower_attacker WHERE tower_battle_id = ? ORDER BY created_at DESC",
[battle_id])
first_activity = attackers[-1].created_at if len(attackers) > 0 else battle.last_updated
blob = ""
blob += self.format_battle_info(battle, t)
blob += f"Duration: <highlight>" \
f"{self.util.time_to_readable(battle.last_updated - first_activity)}</highlight>\n\n"
blob += "<header2>Attackers:</header2>\n"
for row in attackers:
blob += "<tab>" + self.format_attacker(row)
blob += " " + self.format_timestamp(row.created_at, t)
blob += "\n"
return ChatBlob(f"Battle Info {battle_id}", blob)
@command(command="attacks", params=[NamedParameters(["page"])], access_level="member",
description="Show recent tower attacks and victories")
def attacks_cmd(self, _, named_params):
page = int(named_params.page or "1")
page_size = 30
offset = (page - 1) * page_size
data = self.get_recent_attacks(offset, page_size)
t = int(time.time())
return self.display(page, data, time.time())
@command(command="attacks",
params=[Any("playfield"), Any("site_number", is_optional=True), NamedParameters(["page"])],
access_level="member",
description="Show recent tower attacks and victories")
def cmd_attacks_pf_site(self, _, pf, site, named_params):
page = int(named_params.page or "1")
page_size = 30
offset = (page - 1) * page_size
playfield = self.playfield_controller.get_playfield_by_name_or_id(pf)
if not playfield:
return f"Could not find Playfield <highlight>{pf}</highlight>."
pf = playfield.id
data = self.get_recent_attacks_by_lca(offset, page_size, pf, site)
blob = self.display(page, data, time.time())
if site:
blob.page_postfix = f" in {playfield.short_name} on x{site}"
else:
blob.page_postfix = f" in {playfield.short_name}"
return blob
def display(self, page, data, t):
blob = ""
if page > 1:
blob += " " + self.text.make_chatcmd(f"<< Page {page - 1:d}", self.get_chat_command(page - 1))
if len(data) > 0:
blob += " Page " + str(page)
blob += " " + self.text.make_chatcmd(f"Page {page + 1:d} >>", self.get_chat_command(page + 1))
blob += "\n"
current_battle_id = -1
for row in data:
if current_battle_id != row.battle_id:
blob += "\n<pagebreak>"
current_battle_id = row.battle_id
blob += self.format_battle_info(row, t)
blob += self.text.make_tellcmd("More Info", f"attacks battle {row.battle_id:d}") + "\n"
blob += "<header2>Attackers:</header2>\n"
blob += "<tab>" + self.format_attacker(row) + "\n"
blob = ChatBlob(f"Tower Attacks", blob)
return blob
def format_attacker(self, row):
level = f"{row.att_level}/<green>{row.att_ai_level}</green>" if row.att_ai_level > 0 else f"{row.att_level}"
org = row.att_org_name + " " if row.att_org_name else ""
victor = " - <notice>Winner!</notice>" if row.is_victory else ""
return f"{row.att_char_name or 'Unknown attacker'} ({level} {row.att_profession})" \
f" {org}({row.att_faction}){victor}"
def format_battle_info(self, row, t):
blob = ""
defeated = " - <notice>Defeated!</notice>" if row.is_finished else ""
blob += f"Site: <highlight>{row.short_name} {row.site_number or '?'}</highlight>\n"
blob += f"Defender: <highlight>{row.def_org_name}</highlight> ({row.def_faction}){defeated}\n"
blob += f"Last Activity: {self.format_timestamp(row.last_updated, t)}\n"
return blob
def format_timestamp(self, t, current_t):
return f"<highlight>{self.util.format_datetime(t)}</highlight> " \
f"({self.util.time_to_readable(current_t - t)} ago)"
def get_chat_command(self, page):
return f"/tell <myname> attacks --page={page}"
def get_recent_attacks(self, offset, page_size):
return self.db.query("SELECT b.*, a.*, "
"COALESCE(a.att_level, 0) AS att_level, "
"COALESCE(a.att_ai_level, 0) AS att_ai_level, "
"p.short_name, "
"b.id AS battle_id "
"FROM tower_battle b "
"LEFT JOIN tower_attacker a ON a.tower_battle_id = b.id "
"LEFT JOIN playfields p ON p.id = b.playfield_id "
"ORDER BY b.last_updated DESC, a.created_at DESC "
"LIMIT ?, ?", [offset, page_size])
def get_recent_attacks_by_lca(self, offset, page_size, playfield, site_number=None):
if not site_number:
return self.db.query("SELECT b.*, a.*, "
"COALESCE(a.att_level, 0) AS att_level, "
"COALESCE(a.att_ai_level, 0) AS att_ai_level, "
"p.short_name, "
"b.id AS battle_id "
"FROM tower_battle b "
"LEFT JOIN tower_attacker a ON a.tower_battle_id = b.id "
"LEFT JOIN playfields p ON p.id = b.playfield_id "
"WHERE p.id =? ORDER BY b.last_updated DESC, a.created_at DESC "
"LIMIT ?, ?", [playfield, offset, page_size])
else:
return self.db.query("SELECT b.*, a.*, "
"COALESCE(a.att_level, 0) AS att_level, "
"COALESCE(a.att_ai_level, 0) AS att_ai_level, "
"p.short_name, "
"b.id AS battle_id "
"FROM tower_battle b "
"LEFT JOIN tower_attacker a ON a.tower_battle_id = b.id "
"LEFT JOIN playfields p ON p.id = b.playfield_id "
"WHERE p.id = ? AND b.site_number = ? ORDER BY b.last_updated DESC, a.created_at DESC "
"LIMIT ?, ?", [playfield, site_number, offset, page_size])
+208
View File
@@ -0,0 +1,208 @@
import re
from core.aochat import server_packets
from core.conn import Conn
from core.db import DB
from core.decorators import instance, event
from core.dict_object import DictObject
from core.event_service import EventService
from core.igncore import IgnCore
from core.logger import Logger
from core.lookup.pork_service import PorkService
from core.public_channel_service import PublicChannelService
from core.text import Text
from modules.standard.helpbot.playfield_controller import PlayfieldController
@instance()
class TowerEventController:
TOWER_ATTACK_EVENT = "tower_attack"
TOWER_VICTORY_EVENT = "tower_victory"
TOWER_BATTLE_OUTCOME_ID = 42949672962
ALL_TOWERS_ID = 42949672960
# The %s organization %s just entered a state of war!
# %s attacked the %s organization %s's tower in %s at location (%d,%d).
ATTACK_1 = [506, 12753364]
ATTACK_2 = re.compile(r"^(.+) just attacked the (clan|neutral|omni) organization (.+)'s tower in (.+) "
r"at location \((\d+), (\d+)\).\n$")
VICTORY_1 = re.compile(r"^Notum Wars Update: Victory to the (Clan|Neutral|Omni)s!!!$")
VICTORY_2 = re.compile(r"^The (Clan|Neutral|Omni) organization (.+) attacked the (Clan|Neutral|Omni) (.+) "
r"at their base in (.+). The attackers won!!$")
VICTORY_3 = [506, 147506468] # 'Notum Wars Update: The %s organization %s lost their base in %s.'
def __init__(self):
self.logger = Logger(__name__)
def inject(self, registry):
self.bot: IgnCore = registry.get_instance("bot")
self.db: DB = registry.get_instance("db")
self.text: Text = registry.get_instance("text")
self.event_service: EventService = registry.get_instance("event_service")
self.pork_service: PorkService = registry.get_instance("pork_service")
self.playfield_controller: PlayfieldController = registry.get_instance("playfield_controller")
self.public_channel_service: PublicChannelService = registry.get_instance("public_channel_service")
def pre_start(self):
self.event_service.register_event_type(self.TOWER_ATTACK_EVENT)
self.event_service.register_event_type(self.TOWER_VICTORY_EVENT)
self.bot.register_packet_handler(server_packets.PublicChannelMessage.id, self.handle_public_channel_message)
self.db.load_sql_file(self.module_dir + "/" + "tower_sites.sql", pre_optimized=True)
self.db.create_view("tower_sites")
@event(event_type="connect", description="Check if All Towers channel is available")
def handle_connect_event(self, _, _1):
if self.public_channel_service.org_id and not self.public_channel_service.get_channel_id("All Towers"):
self.logger.warning("This bot is a member of an org but does not have access to 'All Towers' channel and "
"therefore will not receive tower attack messages")
def handle_public_channel_message(self, conn: Conn, packet: server_packets.PublicChannelMessage):
if conn.id != "main":
return
if packet.channel_id == self.TOWER_BATTLE_OUTCOME_ID:
victory = self.get_victory_event(packet)
if victory:
self.logger.info("tower victory packet: %s" % str(packet))
# lookup playfield
playfield_name = victory.location.playfield.long_name
victory.location.playfield = self.playfield_controller.get_playfield_by_name(playfield_name) or \
DictObject()
victory.location.playfield.long_name = playfield_name
# print("VICTORY", victory)
# VICTORY {'type': 'attack', 'winner': {'faction': 'Clan', 'org_name': 'Komodites'}, 'loser': {'faction': 'Neutral', 'org_name': 'Deez Neuts'}, 'location': {'playfield': {'id': 791, 'long_name': 'Holes in the Wall', 'short_name': 'HITW', 'dungeon': 0}}}
self.event_service.fire_event(self.TOWER_VICTORY_EVENT, victory)
elif packet.channel_id == self.ALL_TOWERS_ID:
attack = self.get_attack_event(packet)
if attack:
self.logger.info("tower attack packet: %s" % str(packet))
# lookup playfield
playfield_name = attack.location.playfield.long_name
attack.location.playfield = self.playfield_controller.get_playfield_by_name(playfield_name) or \
DictObject()
attack.location.playfield.long_name = playfield_name
# lookup attacker
name = attack.attacker.name
faction = attack.attacker.faction
org_name = attack.attacker.org_name
char_info = self.pork_service.get_character_info(name)
attack.attacker = char_info or DictObject()
attack.attacker.name = name
attack.attacker.faction = faction or attack.attacker.get("faction", "Unknown")
attack.attacker.org_name = org_name
# print("ATTK", attack)
self.event_service.fire_event(self.TOWER_ATTACK_EVENT, attack)
def get_attack_event(self, packet: server_packets.PublicChannelMessage):
# The %s organization %s just entered a state of war!
# %s attacked the %s organization %s's tower in %s at location (%d,%d).
ATTACK_1 = [506, 12753364]
if packet.extended_message and \
[packet.extended_message.category_id, packet.extended_message.instance_id] == self.ATTACK_1:
params = packet.extended_message.params
return DictObject({
"attacker": {
"name": params[2],
"faction": params[0].capitalize(),
"org_name": params[1]
},
"defender": {
"faction": params[3].capitalize(),
"org_name": params[4]
},
"location": {
"playfield": {
"long_name": params[5]
},
"x_coord": params[6],
"y_coord": params[7]
}
})
else:
ATTACK_2 = re.compile(r"^(.+) just attacked the (clan|neutral|omni) organization (.+)'s tower in (.+) "
r"at location \((\d+), (\d+)\).\n$")
match = self.ATTACK_2.match(packet.message)
if match:
return DictObject({
"attacker": {
"name": match.group(1),
"faction": "",
"org_name": ""
},
"defender": {
"faction": match.group(2).capitalize(),
"org_name": match.group(3)
},
"location": {
"playfield": {
"long_name": match.group(4)
},
"x_coord": match.group(5),
"y_coord": match.group(6)
}
})
# Unknown attack
self.logger.warning("Unknown tower attack: " + str(packet))
return None
def get_victory_event(self, packet: server_packets.PublicChannelMessage):
# Does not contain any relevant data.
match = self.VICTORY_1.match(packet.message)
if match:
return None
VICTORY_2 = re.compile(r"^The (Clan|Neutral|Omni) organization (.+) attacked the (Clan|Neutral|Omni) (.+) "
r"at their base in (.+). The attackers won!!$")
match = self.VICTORY_2.match(packet.message)
if match:
return DictObject({
"type": "attack",
"winner": {
"faction": match.group(1).capitalize(),
"org_name": match.group(2)
},
"loser": {
"faction": match.group(3).capitalize(),
"org_name": match.group(4)
},
"location": {
"playfield": {
"long_name": match.group(5)
}
}
})
if packet.extended_message and \
[packet.extended_message.category_id, packet.extended_message.instance_id] == self.VICTORY_3:
params = packet.extended_message.params
return DictObject({
"type": "terminated",
"winner": {
"faction": "", # params[0].capitalize(),
"org_name": "", # params[1]
},
"loser": {
"faction": params[0].capitalize(),
"org_name": params[1]
},
"location": {
"playfield": {
"long_name": params[2]
}
}
})
# Unknown victory
self.logger.warning("Unknown tower victory: " + str(packet))
return None
+282
View File
@@ -0,0 +1,282 @@
DROP TABLE IF EXISTS `tower_sites`;
CREATE TABLE IF NOT EXISTS `tower_sites`
(
`playfield_id` int(11) NOT NULL,
`site_number` smallint(6) NOT NULL,
`min_ql` smallint(6) NOT NULL,
`max_ql` smallint(6) NOT NULL,
`x_coord` smallint(6) NOT NULL,
`y_coord` smallint(6) NOT NULL,
`site_name` varchar(50) NOT NULL,
`timing` int(11) NOT NULL,
`enabled` int(11) NOT NULL,
PRIMARY KEY (`playfield_id`, `site_number`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;
INSERT INTO `tower_sites` (`playfield_id`, `site_number`, `min_ql`, `max_ql`, `x_coord`, `y_coord`, `site_name`,
`timing`, `enabled`)
VALUES (505, 1, 60, 90, 2740, 4260, 'Griffon Frontier', 1, 1),
(505, 2, 80, 110, 540, 4180, 'Draught', 1, 1),
(505, 3, 70, 95, 1740, 3460, 'Dreadfire Volcano', 1, 1),
(505, 4, 80, 120, 2780, 3420, 'Northeast Barren Lands', 1, 1),
(505, 5, 60, 90, 580, 3140, 'Western Desert', 1, 1),
(505, 6, 50, 75, 2420, 1900, 'Waylander Mines', 1, 1),
(505, 7, 70, 100, 1860, 1700, 'North of Main Omni Base', 1, 1),
(505, 8, 61, 82, 460, 1380, 'Dome Ore', 1, 1),
(505, 9, 100, 150, 2700, 620, 'Crystal Forge Volcano', 1, 1),
(505, 10, 100, 150, 660, 460, 'SW Low Plateau', 1, 1),
(550, 1, 10, 20, 2660, 2020, 'Sifter Beach', 2, 1),
(550, 2, 20, 30, 1780, 1780, 'Academy Ore', 2, 1),
(550, 3, 15, 25, 1980, 1340, 'Athen Fault', 2, 1),
(550, 4, 10, 20, 2660, 820, 'Grindmoore', 2, 1),
(550, 5, 15, 23, 1380, 380, 'Gladius Grove', 2, 1),
(551, 1, 40, 90, 1700, 3700, 'Styx Magma', 0, 1),
(551, 2, 35, 50, 2220, 3340, 'Carbon Grove', 0, 1),
(551, 3, 26, 50, 980, 3140, 'Between the Craters', 0, 1),
(551, 4, 25, 35, 340, 2420, 'Powdered Dunes', 0, 1),
(551, 5, 32, 45, 2540, 2060, 'Dust Bank', 0, 1),
(551, 6, 20, 30, 580, 1740, 'Charred Groove', 0, 1),
(551, 7, 12, 45, 940, 1540, 'West of Perdition', 0, 1),
(551, 8, 15, 30, 660, 900, 'North of Yuttos', 0, 1),
(560, 1, 100, 170, 1500, 3420, 'Terraform Edge', 1, 1),
(560, 2, 170, 250, 3060, 3020, 'West Spirals', 1, 1),
(560, 3, 170, 250, 3500, 2980, 'East Spirals', 1, 1),
(560, 4, 130, 170, 1220, 2220, 'Middle Mort Desert', 1, 1),
(560, 5, 1, 100, 900, 1460, 'Green Crater', 1, 1),
(560, 6, 110, 160, 3100, 1460, 'Oasis Ore', 1, 1),
(560, 7, 150, 200, 2740, 700, 'South East Craterwall', 1, 1),
(560, 8, 100, 150, 540, 540, 'South West Craterwall', 1, 1),
(560, 9, 160, 210, 2780, 540, 'Stormshelter', 1, 1),
(565, 1, 25, 40, 2940, 2900, 'Rich Desert Ridge', 2, 1),
(565, 2, 30, 45, 1980, 2580, 'East of Meetmedere', 2, 1),
(565, 3, 50, 75, 540, 2020, 'Middle of Western Desert', 2, 1),
(565, 4, 40, 60, 2580, 1940, 'North of Rhino Village', 2, 1),
(565, 5, 40, 60, 2700, 1260, 'South of Rhino Village', 2, 0),
(567, 1, 12, 20, 1220, 1060, 'In the Newland Desert', 1, 1),
(567, 2, 15, 25, 540, 460, 'West of Newland Lake', 1, 1),
(570, 1, 200, 300, 3220, 3020, 'North of Cyborg Hideout', 1, 1),
(570, 2, 191, 250, 3780, 2540, 'Middle of Liberty', 1, 1),
(570, 3, 120, 180, 980, 2060, 'South of Sabulum', 1, 1),
(570, 4, 190, 230, 3940, 2060, 'Cyborg Border', 1, 1),
(570, 5, 200, 300, 2820, 1820, 'Middle of Perpetual Wastelands', 1, 1),
(570, 6, 200, 300, 3740, 1700, 'South of Cyborg Hideout', 1, 1),
(570, 7, 100, 150, 1500, 1340, 'Lower Plateu Zone', 1, 1),
(570, 8, 100, 150, 2100, 1380, 'The Mid Canyon Crossing', 1, 1),
(570, 9, 120, 180, 3020, 1220, 'Plains of dust', 1, 1),
(570, 10, 100, 150, 900, 1060, 'West of Canyon', 1, 1),
(570, 11, 100, 150, 3180, 940, 'The Canyon Mines', 1, 1),
(570, 12, 190, 230, 2300, 780, 'South of Canyon', 1, 1),
(585, 1, 40, 60, 1220, 2740, 'Northern Wastelands', 1, 1),
(585, 2, 11, 16, 2180, 2580, 'West Wastelands', 1, 1),
(585, 3, 40, 55, 1020, 2460, 'Mid Wastelands', 1, 1),
(585, 4, 30, 45, 2140, 1660, 'Giant Green River Bank North', 1, 1),
(585, 5, 11, 16, 1180, 1340, 'West of the Dead Forest', 1, 1),
(585, 6, 30, 45, 2100, 1340, 'Giant Green River Bank South', 1, 1),
(585, 7, 15, 22, 1420, 1020, 'Canyon East', 1, 1),
(585, 8, 25, 35, 820, 780, 'Canyon South', 1, 1),
(585, 9, 35, 50, 900, 460, 'By the River', 1, 1),
(590, 1, 140, 200, 1740, 3100, 'By the Fisher Village', 2, 1),
(590, 2, 140, 200, 2100, 3060, 'Fisher Village Approach', 2, 1),
(590, 3, 100, 170, 2900, 2820, 'North Forest', 2, 1),
(590, 4, 90, 130, 3340, 2700, 'North-east Forest', 2, 1),
(590, 5, 130, 170, 860, 1220, 'North-west of Lava Ditches', 2, 1),
(590, 6, 100, 150, 3100, 980, 'Mid Clutching Forest', 2, 1),
(590, 7, 130, 170, 860, 780, 'South-west of Lava Ditches', 2, 1),
(590, 8, 100, 150, 3180, 620, 'South Clutching Forest', 2, 1),
(595, 1, 100, 150, 1140, 3380, 'Old ruins', 0, 1),
(595, 2, 100, 150, 3180, 2900, 'Plains of defense', 0, 1),
(595, 3, 130, 180, 1740, 2300, 'The haunted forest outskirt', 0, 1),
(595, 4, 130, 180, 900, 2220, 'Forest of Xzawkaz', 0, 1),
(595, 5, 200, 300, 2260, 1860, 'In the Swamp of Horrors', 0, 1),
(595, 6, 130, 180, 1420, 1500, 'Island of Control', 0, 1),
(595, 7, 130, 180, 1340, 1140, 'The swamp of hope', 0, 1),
(595, 8, 200, 300, 2900, 1100, 'South of the Medusa', 0, 1),
(595, 9, 140, 210, 2140, 780, 'Middle of the Foul Forest', 0, 1),
(595, 10, 200, 300, 540, 540, 'Southern Forest of Xzawkaz', 0, 1),
(600, 1, 30, 45, 2420, 2980, 'By the Rivers Edge', 1, 1),
(600, 2, 50, 75, 620, 2900, 'North Forest Road', 1, 1),
(600, 3, 25, 50, 1300, 2660, 'Along the Rivers Edge', 1, 1),
(600, 4, 30, 45, 3740, 2500, 'East Forest', 1, 1),
(600, 5, 25, 50, 3140, 2020, 'Rhino Hills', 1, 1),
(600, 6, 50, 75, 580, 1700, 'West Forest', 1, 1),
(600, 7, 60, 90, 1940, 1620, 'Crossroads', 1, 1),
(600, 8, 60, 90, 1140, 1500, 'Forestdawn', 1, 1),
(600, 9, 50, 75, 3220, 1140, 'East of Crater', 1, 1),
(605, 1, 160, 200, 2940, 2820, 'Forest Waters', 2, 1),
(605, 2, 110, 120, 1100, 2620, 'Muddy Pools', 2, 1),
(605, 3, 100, 150, 1700, 2300, 'West of Wine', 2, 1),
(605, 4, 120, 180, 2940, 2260, 'East of Wine', 2, 1),
(605, 5, 130, 195, 1900, 1740, 'Central Belial Forest', 2, 1),
(605, 6, 130, 190, 2500, 1660, 'River Delta', 2, 1),
(605, 7, 160, 200, 2540, 1220, 'Junction Forest', 2, 1),
(605, 8, 100, 150, 2340, 860, 'Borderline', 2, 1),
(605, 9, 120, 180, 2020, 420, 'Southern belial Mine', 2, 1),
(605, 10, 140, 200, 620, 380, 'Southwest Belial Mining District', 2, 1),
(610, 1, 60, 90, 1380, 2780, 'Tetlies Land control area', 1, 1),
(610, 2, 80, 120, 2900, 2660, 'East of the Great Marsh', 1, 1),
(610, 3, 60, 90, 660, 2460, 'West of outpost 10-3', 1, 1),
(610, 4, 100, 150, 2300, 2020, 'Defense of Geholva', 1, 1),
(610, 5, 106, 143, 2740, 1180, 'South of Forest of Geholva', 1, 1),
(610, 6, 120, 180, 860, 900, 'Avid Crater', 1, 1),
(610, 7, 120, 180, 1540, 900, 'East of Avid Crater', 1, 1),
(610, 8, 100, 150, 2460, 540, 'Bendelham forest Defense', 1, 1),
(615, 1, 60, 100, 1900, 3020, 'North of Lenne', 1, 1),
(615, 2, 100, 150, 860, 2820, 'Little Hawaii Defense', 1, 1),
(615, 3, 90, 120, 2620, 2660, 'Defense of Zoto', 1, 1),
(615, 4, 60, 100, 900, 2100, 'By the Ocean', 1, 1),
(615, 5, 61, 100, 2300, 1180, 'Birm', 1, 0),
(615, 6, 120, 180, 2700, 660, 'SFH Defense', 1, 1),
(615, 7, 100, 150, 1860, 500, 'South in Nightplain', 1, 1),
(620, 1, 150, 200, 2700, 3860, 'Krud the Lost Valley Defense', 2, 1),
(620, 2, 150, 225, 1900, 3180, 'Pranade', 2, 1),
(620, 3, 120, 180, 620, 2980, 'Plains of Jarga Defense', 2, 1),
(620, 4, 200, 300, 2460, 2260, 'Old Plains', 2, 1),
(620, 5, 200, 300, 1540, 1780, 'Middle of Easter Fouls Plains', 2, 1),
(620, 6, 130, 200, 1540, 1140, 'Clefre Defense', 2, 1),
(620, 7, 100, 150, 2020, 860, 'Central Sharewood', 2, 1),
(620, 8, 200, 300, 820, 540, 'Pegradul', 2, 1),
(625, 1, 90, 130, 1460, 1940, 'The Resilient Forest - North', 0, 1),
(625, 2, 90, 120, 1900, 1540, 'The Resilient Forest - East', 0, 1),
(625, 3, 125, 170, 2780, 1380, 'Central Prowler Waste', 0, 1),
(625, 4, 100, 125, 1380, 1180, 'Central Resilient Forest', 0, 1),
(625, 5, 125, 170, 2860, 1020, 'Southern Prowler Waste', 0, 1),
(625, 6, 100, 150, 4020, 980, 'The Barren Hills', 0, 1),
(625, 7, 100, 125, 1740, 860, 'The Resilient Forest - South', 0, 1),
(625, 8, 50, 75, 2460, 540, 'The Silent Woods - East', 0, 1),
(630, 1, 40, 60, 1540, 2660, 'Pleasant Range Offense Hill', 0, 1),
(630, 2, 60, 90, 2380, 2500, 'Central Pleasant Range', 0, 1),
(630, 3, 50, 75, 580, 2420, 'West of 20K', 0, 1),
(630, 4, 30, 70, 3220, 2220, 'Pleasant Range Defense', 0, 1),
(630, 5, 60, 90, 3220, 1980, 'Pleasant River Defense', 0, 1),
(630, 6, 60, 90, 3260, 1500, 'Pleasant River Offense', 0, 1),
(630, 7, 40, 60, 2260, 1140, 'Central Pleasant Plains', 0, 1),
(630, 8, 30, 70, 3020, 1020, 'East Pleasant Plains', 0, 1),
(630, 9, 30, 45, 740, 460, 'West of Versailles Tower', 0, 1),
(635, 1, 55, 70, 700, 2420, 'Northern River Bank', 2, 0),
(635, 2, 60, 90, 1780, 2460, 'Hawker Trench', 2, 1),
(635, 3, 70, 105, 1460, 1740, 'Klapam Forest Defense', 2, 1),
(635, 4, 55, 70, 2020, 1740, 'Klompfot Defense', 2, 1),
(635, 5, 70, 105, 1900, 1220, 'South of Trench', 2, 1),
(635, 6, 80, 120, 1140, 940, 'Nile Hills', 2, 1),
(635, 7, 55, 70, 1780, 700, 'Aprils Rock Offense', 2, 1),
(635, 8, 80, 150, 820, 420, 'Southern Lower River Bank', 2, 1),
(635, 9, 80, 150, 1700, 340, 'Aprils Rock Defense', 2, 1),
(646, 1, 10, 15, 460, 1300, 'Great W. Forest Vein', 1, 1),
(646, 2, 10, 15, 2940, 980, 'The Hidden Notum Canal', 1, 1),
(646, 3, 20, 30, 3220, 620, 'Mountain Areas', 1, 1),
(646, 4, 10, 15, 580, 580, 'Great W. Forest Dorsal', 1, 1),
(646, 5, 10, 15, 1500, 460, 'Western Mountain Areas', 1, 1),
(647, 1, 90, 135, 1100, 3100, 'The Mineral Mine', 1, 1),
(647, 2, 20, 30, 2900, 2940, 'NE Desert Aperient', 1, 1),
(647, 3, 37, 64, 1900, 2700, 'SurroundingTemple of Three Winds', 1, 1),
(647, 4, 25, 40, 2220, 1900, 'Piercing Thundertube', 1, 1),
(647, 5, 30, 45, 2820, 1940, 'Central Striking Ant', 1, 1),
(647, 6, 25, 40, 620, 1660, 'Tir Prairie', 1, 1),
(647, 7, 25, 40, 1180, 1700, 'Crater Swamp', 1, 1),
(650, 1, 50, 75, 540, 2820, 'West Pass', 2, 1),
(650, 2, 65, 75, 900, 2300, 'Crowning Shallows', 2, 1),
(650, 3, 100, 150, 1660, 2180, 'Haven Notum Crematorium', 2, 1),
(650, 4, 70, 140, 2020, 1740, 'Stret Vale Deux Drilling Field', 2, 1),
(650, 5, 120, 180, 1340, 1620, 'The Flooded Bottomland', 2, 1),
(650, 6, 75, 90, 1820, 740, 'Stret Woods', 2, 1),
(650, 7, 60, 90, 940, 420, 'Greenslopes', 2, 1),
(655, 1, 30, 45, 420, 2700, 'Skop Notum Mine', 1, 1),
(655, 2, 30, 80, 2820, 2340, 'Klor', 1, 1),
(655, 3, 60, 80, 2820, 1660, 'Harstad', 1, 1),
(655, 4, 40, 90, 540, 1580, 'Ubleo', 1, 1),
(655, 5, 40, 60, 1420, 1580, 'Flubu Notum Mine', 1, 1),
(655, 6, 40, 70, 4340, 900, 'Plago', 1, 1),
(655, 7, 60, 80, 2260, 380, 'jucha', 1, 1),
(655, 8, 70, 105, 4380, 380, 'Mune', 1, 1),
(655, 9, 30, 60, 820, 340, 'Mocnuf Notum Mine', 1, 1),
(665, 1, 80, 150, 940, 4820, 'Central Desert north', 2, 1),
(665, 2, 45, 75, 1260, 3860, 'Notum Disruption Mountain', 2, 1),
(665, 3, 75, 110, 1940, 3860, 'The Notum Plains', 2, 1),
(665, 4, 100, 150, 940, 3380, 'Near Clan Outpost', 2, 1),
(665, 5, 45, 80, 1300, 3060, 'Central Mountains', 2, 1),
(665, 6, 55, 150, 380, 2300, 'Surrounding Evil', 2, 1),
(665, 7, 45, 60, 1260, 2140, 'Notum Mountain', 2, 1),
(665, 8, 55, 100, 2020, 1980, 'Near Omni-Tek Outpost', 2, 1),
(665, 9, 100, 150, 420, 820, 'Shores Notum Vein', 2, 1),
(670, 1, 30, 45, 1100, 4340, 'Yukon Source', 2, 1),
(670, 2, 35, 50, 1460, 2540, 'Frisko', 2, 1),
(670, 3, 30, 45, 2140, 2420, 'Round Hills', 2, 1),
(670, 4, 50, 75, 2140, 1900, 'Dense Drewen', 2, 1),
(670, 5, 35, 50, 1260, 1820, 'Borrowed Hill', 2, 1),
(670, 6, 35, 50, 1340, 1340, 'Narrow Lune', 2, 1),
(670, 7, 10, 15, 2500, 1220, 'Micron Slopes Notum Mine', 2, 1),
(670, 8, 50, 75, 2100, 540, 'High Juniper', 2, 1),
(670, 9, 50, 75, 2300, 460, 'High Juniper Notum Vein', 2, 1),
(685, 1, 35, 50, 2140, 2620, 'Nature Reverve - East', 2, 1),
(685, 2, 35, 50, 1900, 2580, 'Nature Reverve - West', 2, 1),
(685, 3, 50, 75, 1300, 1900, 'Poole - West', 2, 1),
(685, 4, 50, 75, 1580, 1820, 'Poole - East', 2, 1),
(685, 5, 15, 25, 1140, 1100, 'V-Hill', 2, 1),
(685, 6, 20, 30, 1580, 700, 'Lunder Hills - North', 2, 1),
(685, 7, 25, 40, 2740, 460, 'Galway hills', 2, 1),
(685, 8, 20, 30, 1220, 380, 'Lunder Hills', 2, 1),
(685, 9, 25, 40, 2260, 380, 'South-east Woods', 2, 1),
(687, 1, 10, 15, 500, 1900, 'Blossom Valley', 2, 1),
(687, 2, 10, 15, 380, 1300, 'Konty Passage Plains', 2, 1),
(687, 3, 17, 28, 900, 1220, 'Vas\' Pass', 2, 1),
(687, 4, 15, 25, 780, 900, 'Arthur\'s Pass', 2, 1),
(687, 5, 10, 15, 380, 580, 'Kontys Sixth Passage - West', 2, 1),
(687, 6, 10, 15, 620, 540, 'Kontys Sixth Passage - East', 2, 1),
(695, 1, 30, 45, 940, 3260, 'North West Lush Fields', 0, 1),
(695, 2, 20, 30, 2420, 3180, 'North East Lush Fields', 0, 1),
(695, 3, 10, 40, 3460, 2940, 'Stret River Island', 0, 1),
(695, 4, 40, 60, 1260, 2460, 'West of Outpost', 0, 1),
(695, 5, 35, 60, 1740, 2460, 'East of Outpost', 0, 1),
(695, 6, 20, 30, 1780, 1820, 'Central Lush Fields', 0, 1),
(695, 7, 10, 15, 2860, 420, 'South East Lush Fields', 0, 1),
(695, 8, 30, 45, 980, 380, 'South West Lush Fields', 0, 1),
(696, 1, 15, 25, 780, 1420, 'Mutant Domain North', 2, 1),
(696, 2, 20, 30, 500, 860, 'Mutant Domain Central', 2, 1),
(696, 3, 25, 40, 780, 460, 'Mutant Domain South', 2, 1),
(716, 1, 20, 35, 500, 3220, 'Northern Grassland', 0, 1),
(716, 2, 15, 30, 980, 3020, 'Moderate Grassland', 0, 1),
(716, 3, 10, 20, 460, 2180, 'Dungeon Hilltop', 0, 1),
(716, 4, 10, 15, 700, 2180, 'Rocky Upsurge', 0, 1),
(716, 5, 15, 25, 340, 1420, 'Northern Easy Swamps Notum Field', 0, 1),
(716, 6, 15, 26, 460, 820, 'Ocean Inlet', 0, 1),
(717, 1, 30, 45, 1620, 2660, 'Greater Omni Forest Swamps', 1, 1),
(717, 2, 15, 25, 1180, 2460, 'Dragonback Ridge', 1, 1),
(717, 3, 30, 45, 1900, 1820, 'Mountainous Regions', 1, 1),
(717, 4, 20, 35, 1860, 1340, 'Waterfall Swamp', 1, 1),
(717, 5, 10, 15, 1500, 1300, 'Greater Omni Forest South', 1, 1),
(717, 6, 25, 40, 900, 1220, 'Northern Semi-Barren Area', 1, 1),
(717, 7, 10, 25, 1940, 900, 'Ring Mountain Range', 1, 1),
(717, 8, 14, 25, 940, 460, 'Southern Isle', 1, 1),
(760, 1, 60, 90, 1580, 2380, 'Notum Ore in Buttu', 2, 1),
(760, 2, 35, 50, 940, 2020, 'Mountain of Fourtyone', 2, 1),
(760, 3, 35, 50, 1300, 1980, 'Mountain in 4Holes', 2, 1),
(760, 4, 45, 60, 1660, 1740, 'South of Ahenus', 2, 1),
(760, 5, 70, 100, 1820, 1340, 'Ibreri Woods North', 2, 1),
(760, 6, 35, 50, 1460, 1260, 'Mountain of Fourtytwo', 2, 1),
(760, 7, 100, 150, 1740, 1060, 'Ibreri Woods', 2, 1),
(760, 8, 45, 70, 1180, 500, 'Ibreri', 2, 1),
(760, 9, 45, 70, 460, 420, 'Jall Mountain', 2, 1),
(790, 1, 20, 30, 1700, 3100, 'Hells Courtyard', 2, 1),
(790, 2, 15, 25, 2300, 2860, 'Pondus Beach', 2, 1),
(790, 3, 15, 30, 1700, 2780, 'Hound Land', 2, 1),
(790, 4, 20, 40, 1980, 2780, 'Hound Notum Field', 2, 1),
(790, 5, 20, 30, 1700, 1940, 'East Mutie', 2, 1),
(790, 6, 12, 30, 1340, 1220, 'Omni Outpost', 2, 1),
(790, 7, 20, 30, 660, 1180, 'South Mutie', 2, 1),
(790, 8, 30, 60, 2260, 1140, 'The Beach', 2, 1),
(791, 1, 15, 26, 420, 2020, 'Populous Mountain', 2, 1),
(791, 2, 12, 22, 660, 1500, 'Hound Land Mining', 2, 1),
(791, 3, 12, 20, 220, 1060, 'Stret West Notum Ore', 2, 1),
(791, 4, 10, 15, 740, 820, 'Snake Mountain', 2, 1),
(791, 5, 20, 40, 780, 460, 'Southern Empty Wastes and Roads', 2, 1),
(791, 6, 10, 20, 380, 340, 'Transit Valley Ore', 2, 1),
(795, 1, 40, 60, 4220, 1580, 'Illuminati', 1, 1),
(795, 2, 100, 150, 500, 1540, 'Northern Forest of Illuminations', 1, 0),
(795, 3, 25, 50, 3420, 1540, 'Fate Notum Field', 1, 1),
(795, 4, 71, 120, 580, 820, 'Pegrama', 1, 1),
(795, 5, 84, 120, 1220, 700, 'Grazeland Notum Field', 1, 1),
(795, 6, 50, 75, 4020, 620, 'Winterbottom', 1, 1),
(795, 7, 90, 120, 540, 500, 'Southern Forest of Illuminations', 1, 1),
(795, 8, 60, 90, 2900, 500, 'Summer', 1, 0);
@@ -0,0 +1,97 @@
import time
from core.decorators import instance, event
from core.igncore import IgnCore
from core.logger import Logger
from core.message_hub_service import MessageHubService
from core.text import Text, MLStripper
from core.util import Util
from modules.standard.helpbot.playfield_controller import PlayfieldController
from modules.standard.tower.tower_attack_controller import TowerAttackController
from modules.standard.tower.tower_controller import TowerController
from modules.standard.tower.tower_events import TowerEventController
@instance()
class TowerSpamController:
SOURCE = "TowerInfo"
def __init__(self):
self.logger = Logger(__name__)
def inject(self, registry):
self.bot: IgnCore = registry.get_instance("bot")
self.db = registry.get_instance("db")
self.text: Text = registry.get_instance("text")
self.util: Util = registry.get_instance("util")
self.tower: TowerEventController = registry.get_instance("tower_controller")
self.tc: TowerController = registry.get_instance("tower_controller")
self.tac: TowerAttackController = registry.get_instance("tower_attack_controller")
self.playfield_controller: PlayfieldController = registry.get_instance("playfield_controller")
self.msg_hub: MessageHubService = registry.get_instance("message_hub_service")
def pre_start(self):
self.msg_hub.register_message_source(self.SOURCE)
@event(event_type=TowerEventController.TOWER_ATTACK_EVENT, description="NW Warnings, for attacks")
def tower_attack_event(self, _, event_data):
t = int(time.time())
site_number = self.tac.find_closest_site_number(event_data.location.playfield.id, event_data.location.x_coord,
event_data.location.y_coord)
# self.logger.info("ATTK ")
# self.logger.info(event_data)
# self.logger.info(" site: ")
# [11.11.2021 04:35:58] INFO :: modules.standard.tower.tower_spam_controller -> ATTK |
# [11.11.2021 04:35:58] INFO :: modules.standard.tower.tower_spam_controller -> {'attacker': {'char_id': 984112, 'name': 'Flexiblex', 'first_name': '', 'last_name': '', 'leve|
# l': 117, 'breed': 'Opifex', 'gender': 'Female', 'faction': 'Omni', 'profession': 'Meta-Physicist', 'profession_title': 'ArchPriest', 'ai_rank': 'Adept', 'ai_level': 13, 'or|
# g_id': 9655, 'org_name': 'Northern Star', 'org_rank_name': 'Unit Leader', 'org_rank_id': 4, 'dimension': 5, 'head_id': 40240, 'pvp_rating': 1503, 'pvp_title': 'Rookie', 'so|
# urce': 'people.anarchy-online.com', 'last_updated': 1636601503, 'cache_age': 3855}, 'defender': {'faction': 'Omni', 'org_name': 'Weyland Yutani'}, 'location': {'playfield':|
# {'id': 795, 'long_name': 'The Longest Road', 'short_name': 'TLR', 'dungeon': 0}, 'x_coord': 526, 'y_coord': 538}} |
# [11.11.2021 04:35:58] INFO :: modules.standard.tower.tower_spam_controller -> site: |
# [11.11.2021 04:35:58] INFO :: modules.standard.tower.tower_spam_controller -> 7
# self.logger.info(site_number)
self.send_nw(
f"[<cyan>NW</cyan>] {self.text.get_formatted_faction(event_data.attacker.faction, event_data.attacker.org_name)} "
f"[{self.text.get_formatted_faction(event_data.attacker.faction, event_data.attacker.name)} ({event_data.attacker.level}/{event_data.attacker.ai_level})"
f" -> {self.util.get_profession(event_data.attacker.profession)}] "
f"attacked {self.text.get_formatted_faction(event_data.defender.faction, event_data.defender.org_name)} "
f"at {event_data.location.playfield.short_name} x{site_number}")
@event(event_type=TowerEventController.TOWER_VICTORY_EVENT, description="Send NW warnings")
def tower_victory_event(self, _, event_data):
t = int(time.time())
row = None
if event_data.type == "attack":
row = self.tac.get_last_attack(event_data.winner.faction, event_data.winner.org_name,
event_data.loser.faction,
event_data.loser.org_name, event_data.location.playfield.id, t)
# self.logger.info("ATTK_VICT ")
# self.logger.info(event_data)
# self.logger.info("ATTK_VICT ")
# self.logger.info(row)
self.send_nw(
f"[<cyan>NW</cyan>] {self.text.get_formatted_faction(event_data.winner.faction, event_data.winner.org_name)} "
f"won against {self.text.get_formatted_faction(event_data.loser.faction, event_data.loser.org_name)} at {event_data.location.playfield.short_name} x{row.site}")
elif event_data.type == "terminated":
row = self.tac.find_similar_attacks(event_data.loser.faction, event_data.loser.org_name,
event_data.location.playfield.id, t)
# VICTORY {'type': 'terminated', 'winner': {'faction': '', 'org_name': ''}, 'loser': {'faction': 'Omni', 'org_name': 'Do you smell the scared boxes'}, 'location': {'playfield
# ': {'id': 795, 'long_name': 'The Longest Road', 'short_name': 'TLR', 'dungeon': 0}}}
if row:
# self.logger.info("ATTK_TERM ")
# self.logger.info(event_data)
# self.logger.info("TERMINATED ")
# self.logger.info(row)
self.send_nw(f"[<cyan>NW</cyan>] Site terminated in {event_data.location.playfield.long_name}: "
f"{self.text.get_formatted_faction(event_data.loser.faction, event_data.loser.org_name)} at {event_data.location.playfield.short_name} x{row.site}")
else:
raise Exception("Unknown victory event type: '%s'" % event_data.type)
def send_nw(self, message):
stripper = MLStripper()
stripper.feed(message)
clean = stripper.get_data()
self.msg_hub.send_message(self.SOURCE, None, clean, message)