From f45f02501a5026911a4c39b24ec1825a3155bfab Mon Sep 17 00:00:00 2001 From: jerick Date: Mon, 26 Jan 2026 15:51:44 -0500 Subject: [PATCH] Changed status lookup to only queue okay enemies for attack --- README.md | 9 +- __pycache__/config.cpython-311.pyc | Bin 2202 -> 2154 bytes .../bot_assignment.cpython-311.pyc | Bin 14136 -> 15976 bytes services/bot_assignment.py | 86 ++++++++++++++---- 4 files changed, 69 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index e38c101..c753add 100644 --- a/README.md +++ b/README.md @@ -9,13 +9,8 @@ Features: ToDo: -- API Key section - - Have user enter their own API key, pass along to the functions that call APIs -- server side config storage - - have multiple managers logged in to make changes - basic auth - since control of the Discord bot would also be through there technically -- add status description to member cards +- Log output section on webpage, to see who is being assigned to who, when the hit is complete, if it is missed, how many hits a person has done +- Hit Leaderboard? - -For now let's pivot to the Discord Bot functionality. What the bot is going to do is for each battle group it needs to assign a friendly member to an enemy member. That friendly will then get a ping in Discord by pulling the list of Discord users and matching the player id to the user in the Discord server. It will ping them and say "New target for @user , attack (link to enemy profile) in the next 30 seconds!" If the enemies status does not change to "In Hospital" in the next 30 seconds that enemy will be assigned to the next player in the group that has not received a hit yet. We will also need to keep track of how many hits a friendly has completed. That way if a new friendly enters a pool they will get a chance to attack before the ones that have not had a chance. We also need a button on the webpage to start and stop the bot \ No newline at end of file diff --git a/__pycache__/config.cpython-311.pyc b/__pycache__/config.cpython-311.pyc index 79536c3f290bb0f350465cfc33611a068a9a897e..8c658f41a0289cbb2e2e6dd7dff37b43364e307c 100644 GIT binary patch delta 378 zcmbOw_)36pIWI340}yncE6;quGLi2!Bg4dBn#|lOJd@QJHJN!+_$CJdY5o*}$#pTs(p5f?R+`c)R-Dk_M}Ti8=X)K*S+xg&ci+ z{KH*c;+;Jl{rp^g;yqm^FJoKmED3a2ku->q0TFT_LIFf5fe2+Fv6A64kYp$l1`@wG zY;yBcN^?@}iZp>-Mj$R`o!rCj=jD8n-{lIwO9Rgb4hBJq>D-gJXZT;xb-O6wenr5& yf%gL&lBm~30q-jU-VMArL}jK&Pl~>vXu6^BqNwE+QOgSqLX%ZF3RyIOCIbMZMrWJ= delta 427 zcmaDQFiVhcIWI340}zy-EYB2Royhl^k$d7VO=g}H-pOi=n#_DD{F8%#v_OjBz_1#KAs~t`g(;XpQ*`qiMjn=W0n0ESZ-elXR70oo2>*;!r&~gf zK0f~8t}gM;9*%x~u0HXeE;+0~tzQHhgc=zQ6&M+A@w&MMJNt)*xCX_0yGGs;h=|M$ z5Ae5)ip=&Y%quf4yT$9`2~-f|5+CC4?dn(M;TsZY;G30h?3Wd4>=zPRIdq%dzkyW2k3>RM0%J6M&t(g=vqc)T2^Hg==ztK1?OfuSA+-phld)6>KdkGPcCO$ z>?{FvO_3CckOmR5AVMBQD1rzjAhD9+GmvB`5(W~#IBatBQ%ZAE?TR#jTt*-+W}6(# z?&ocLk>BnLzg+{*4N;lt(UYPtD4Jd{_qr(ReMQv!0)x;8HU>e7>D-gJXZT;xwZACf ea7DnOffq%z{Gvd`6@iKd-pOCt3t7~GMgst|dv)9Z diff --git a/services/__pycache__/bot_assignment.cpython-311.pyc b/services/__pycache__/bot_assignment.cpython-311.pyc index c33ff0326a128b6c10b7ce29b861784b1ee00e7f..afce6250a142dcbb37a2b1d97d9e207889dbf9a8 100644 GIT binary patch delta 5273 zcmbVQeQZa`clHvfPZIc3YC#+6Cp&% zuV`hyKtmm70?q3LF@nSy4_OV@SucX5Th=hWNs&Qj=k?J@BFe{6Cu!LFlt!HA1`tXj z)Bv662_Q={Z&uhZ(f%#a^Xwp~>mZZ#rz7!Gybxkk?YH1RJ1X_c>K^PX^B?X;m^uBIJjPC&8|KoOM5aV3VD5Ga*|JQd99E{Vo0yb?T2wRK$xt@ zQ4EITXGRom=6H-lPs4zq2NF;ca@~B_X{15)?_KqlThM=rk~IC)(S7(!SGA8q^nAHU zj(9PRE!Wcwexo*M(;=Uv8`CZhU#xTEkt&;)(~L8u3#RXpn5c%P_EH?PmkMcTF5znS zY$}jMfkaXaL_{$X9gmF0`L!lhqd?$0x+||ujz`Yyge{SiLnT1+IzGWqoKXxQuZPDa zo)Yr9cyfwIMLXY1co`nHIz*=K2U4_OGVI2GV|S!dP<@dqm?)>OKv|4#h0OKkTt2z1 z0dksA%elV$_OkO_mJP{vl5>uVZ}gPQ^Xky0T^Dzy`!dePtg~@$AXi>dplEC5<$){1 zcfD&DylXSwby@Ga8-okpF4@}!^9p*(>7Ls`emT9pd`?8}o5&s38Hbm2#cz8@H`s`h zj;TmY966N`WAPEhN4PVpl_?gf9Ef=|DUP61i9{@M0(IhUU!@~V#E@469VTQCKJIJs zHxp?_5k;c>C>(a)IEi8jafF|ahRpE3z2ghC5u3@IBZ3e+k(l5UqM#TMQqIz#ocjnI zC^2WWju8H$->H}`+Jrs+5Iu9V-~SJ$ehkKKZtQ*r2R+4a9%M$zP;&hAlQ!5(PAwoGlm>Su-l3t^@5@;_^hPL6V4)@lv znzR9I(}iEwI(F$Ly|AT7h*8o2tLZB97QFx0i4RZ`O-VHLnMAo^BGHmYVmPfx z+|XhgGvNznb}MJ$teoviiA0T&4Jt`0*$gG8-TPQElKxRKK@ekkyci<|qAiP(@r}v~ zoNBV;AJY=|OGg(d}^7V*OTJHs@HSC+q>ORzLA7{P%h`RdfJe5WSrK|D&j*#KvVYR6T`SW-UeQ_#_Khh6b!= zsK{*o*Et^qtC+Q%vk==*Yg6QZJEdq(21#5h@35F#h|e96)}#Lo#~VcEstFN5PfmeTpn zcg?;9voB+=&YG*!oeSoWY!2PCl*(nJ*BX8uh63^j4IehEHJ}+wG;4{r8>=IEcJyl%33}*STNwv8k? zcdp}!^V+PVF5Q=PG+wL7INEa#_PicYFjaM!mUkbJJ%=FA4HYz$JEpnToDKIBC{k7F zoraIYtK0H1K6`AusAux*>gG2THLQ7larWwgxu@>4o>x!aWy1?>_>-pYREF)zvORN8 zY9#9I?=zRI7KZ!lB;kyj+Mg^eg^GQEI0SjW!TvgvQlHqI{=_o@Vjvlzb%pl zwMv$>3%XrH<&tTPT@nw=Iv-NYC7Gd@y;@QnY`sKrWoMb^s7uU`v@`?_L>+v-r3LOG z5V$c~t^)tHrFFe#exq_35srXm%~~I_kBX&rCF{yoh&{S1Pf*47IBS!rbS079Yn5yd zje%vLbw!8kQDUm9M`J3G;vrH(R>>gg-vU#-zHE-gdzu%yGPv(}@po%C^-X}L3a}1- zL7v5ocMm_2JWY^Z#o!{qM+Cp}hLcHQGA2giDrRrP!>!G`l`x^?*$4ta49plwL;z3< zQGq)M@Q;>+u7nRLK~^RRxO2u94#Tps1jnPUa5yky3bf!0t<`!ZFc`+STI*9)3O*x5 z6wt0B`{I%cdfvZFX7{nNrC5-qf>QJ zA^`>ZrwNA<4kr>*Zc1l~WM$I>P!ou6T7-D#64Wc-1P})!5b%&M0uR=)C4eoIaGAlj zwhj6YW6J}o18-|v)8}x$GX0b3%Z7AA#@?8ltO>)awcx8y3k*@~uXeR9P*xE12@wm=)XJTlhG`_782GkA@Woz3cf(c!tg@5+(8 zzSad_YsS}>^|i@%l5_Za+xYHJl{=-wH80(@*DTm;GWNQxy)J!v!QLXhx&3g*bq#8A{)S)1e-Q_aZ646v9?ovwtyWbkABkj-953n_7gsmc`U8rp9@KoT zQ0S=~B*?u;J*ykVb^K2I_m+<~Zq;tvthuvUvz@}X)`wEW4JqG@kdM>|_NXyu7pe6C z$!kZG$vEmGRR)Y3kuLzMr773tO{7U|PI>f*e1t)6LR^FpKdumBw8Aq0jV~_$3K|1` ze#3jIGM)1VQ=p*uK&upYD&#%;K8EiL(Dg8B?9c@Z)G|rXTWMYAwY~yH%G*)3yt7v= zA49ikfN=+{jHtz?;wH3I{fX-NvK6;5G7(8Yu!};a%I7Q;#2muSHcuqEQ*piry#nG$WFXwb2Rq~Ycgoa%%?0g*mL|&m_u_#%_H{AKF(YO-`nBZv F{{sXI@F)NP delta 3642 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 E2LrrPLjV8( diff --git a/services/bot_assignment.py b/services/bot_assignment.py index e6de6ab..130a973 100644 --- a/services/bot_assignment.py +++ b/services/bot_assignment.py @@ -102,13 +102,21 @@ class BotAssignmentManager: def get_next_enemy_in_group(self, group_id: str, enemy_ids: list) -> Optional[int]: """ Get the next enemy in the group who needs to be assigned. - Returns None if all enemies are already assigned. + Returns None if all enemies are already assigned or not attackable. """ for eid in enemy_ids: key = f"{group_id}:{eid}" - # If enemy is not currently assigned, return it - if key not in self.active_targets: - return eid + # If enemy is already assigned, skip them + if key in self.active_targets: + continue + + # Check if enemy is attackable (status must be "Okay") + enemy = STATE.enemy.get(eid) + if not enemy or enemy.status.lower() != "okay": + continue + + # This enemy is available for assignment + return eid return None async def assignment_loop(self): @@ -176,18 +184,30 @@ class BotAssignmentManager: print(f"Cannot assign: friendly {friendly_id} or enemy {enemy_id} not found") return + # Only assign if enemy status is "Okay" (not traveling, hospitalized, etc.) + if enemy.status.lower() != "okay": + print(f"Skipping assignment: {enemy.name} status is '{enemy.status}' (must be 'Okay')") + return + # Get Discord user discord_id = self.get_discord_id(friendly_id) if not discord_id: - print(f"No Discord mapping for Torn ID {friendly_id}") + print(f"No Discord mapping for Torn ID {friendly_id} - skipping assignment") + # Record assignment to prevent infinite retries + key = f"{group_id}:{enemy_id}" + self.active_targets[key] = { + "group_id": group_id, + "friendly_id": friendly_id, + "enemy_id": enemy_id, + "discord_id": None, + "assigned_at": datetime.now(), + "reminded": False, + "failed": True + } return - discord_user = await self.bot.fetch_user(discord_id) - if not discord_user: - print(f"Discord user {discord_id} not found") - return - - # Record assignment + # Record assignment BEFORE attempting to send message + # This prevents infinite retries if message sending fails key = f"{group_id}:{enemy_id}" self.active_targets[key] = { "group_id": group_id, @@ -195,22 +215,32 @@ class BotAssignmentManager: "enemy_id": enemy_id, "discord_id": discord_id, "assigned_at": datetime.now(), - "reminded": False + "reminded": False, + "failed": False } - # Send Discord message to channel - attack_link = f"https://www.torn.com/loader.php?sid=attack&user2ID={enemy_id}" - message = f"**New target for {discord_user.mention}!**\n\n[**{enemy.name}** (Level {enemy.level})]({attack_link})\n\nYou have {ASSIGNMENT_TIMEOUT} seconds!" - + # Fetch Discord user and send message try: + discord_user = await self.bot.fetch_user(discord_id) + if not discord_user: + print(f"Discord user {discord_id} not found") + self.active_targets[key]["failed"] = True + return + + # Send Discord message to channel + attack_link = f"https://www.torn.com/loader.php?sid=attack&user2ID={enemy_id}" + message = f"**New target for {discord_user.mention}!**\n\n[**{enemy.name}** (Level {enemy.level})]({attack_link})\n\nYou have {ASSIGNMENT_TIMEOUT} seconds!" + channel = self.bot.get_channel(ALLOWED_CHANNEL_ID) if channel: await channel.send(message) print(f"Assigned {enemy.name} to {friendly.name} (Discord: {discord_user.name})") else: print(f"Assignment channel {ALLOWED_CHANNEL_ID} not found") + self.active_targets[key]["failed"] = True except Exception as e: print(f"Failed to send Discord message to channel: {e}") + self.active_targets[key]["failed"] = True async def monitor_active_targets(self): """Monitor active targets for status changes or timeouts""" @@ -220,12 +250,24 @@ class BotAssignmentManager: for key, data in list(self.active_targets.items()): elapsed = (now - data["assigned_at"]).total_seconds() + # Remove failed assignments after a short delay (don't reassign them) + if data.get("failed", False): + if elapsed >= 30: # Clean up after 30 seconds + print(f"Removing failed assignment: {key}") + del self.active_targets[key] + continue + # Check enemy status enemy_id = data["enemy_id"] enemy = STATE.enemy.get(enemy_id) - if enemy and "hospital" in enemy.status.lower(): - # Enemy is hospitalized - success! + if not enemy: + # Enemy no longer exists, remove assignment + del self.active_targets[key] + continue + + # Check if enemy is hospitalized (success!) + if "hospital" in enemy.status.lower(): friendly_id = data["friendly_id"] if friendly_id in STATE.friendly: # Increment hit count @@ -236,7 +278,13 @@ class BotAssignmentManager: del self.active_targets[key] continue - # Send reminder + # Check if enemy is no longer attackable (traveling, etc.) + if enemy.status.lower() != "okay": + print(f"Target {enemy.name} is now '{enemy.status}' - removing assignment") + del self.active_targets[key] + continue + + # Send reminder (only for successful assignments) if elapsed >= ASSIGNMENT_REMINDER and not data["reminded"]: discord_id = data["discord_id"] try: