diff --git a/code/game/objects/items/weapons/melee/melee_misc.dm b/code/game/objects/items/weapons/melee/melee_misc.dm
index 8c8f6851c40..cebeffae3c4 100644
--- a/code/game/objects/items/weapons/melee/melee_misc.dm
+++ b/code/game/objects/items/weapons/melee/melee_misc.dm
@@ -61,7 +61,7 @@
AddComponent(/datum/component/parry, _stamina_constant = 2, _stamina_coefficient = 0.5, _parryable_attack_types = NON_PROJECTILE_ATTACKS)
RegisterSignal(src, COMSIG_PARENT_QDELETING, PROC_REF(alert_admins_on_destroy))
-/obj/item/melee/saber/attack(mob/living/target, mob/living/user)
+/obj/item/melee/saber/attack__legacy__attackchain(mob/living/target, mob/living/user)
if(user.a_intent != INTENT_HELP || !ishuman(target))
return ..()
if(!COOLDOWN_FINISHED(src, slap_cooldown))
diff --git a/code/game/objects/items/weapons/storage/firstaid.dm b/code/game/objects/items/weapons/storage/firstaid.dm
index 9fcdfeb5839..c6d394ba222 100644
--- a/code/game/objects/items/weapons/storage/firstaid.dm
+++ b/code/game/objects/items/weapons/storage/firstaid.dm
@@ -308,7 +308,7 @@
return
applying_meds = TRUE
for(var/obj/item/reagent_containers/P in contents)
- if(P.attack(M, user))
+ if(P.attack__legacy__attackchain(M, user))
applying_meds = FALSE
else
applying_meds = FALSE
@@ -357,7 +357,7 @@
C.visible_message("[C] [rapid_intake_message]")
if(do_mob(C, C, 100)) // 10 seconds
for(var/obj/item/reagent_containers/pill/P in contents)
- P.attack(C, C)
+ P.attack__legacy__attackchain(C, C)
C.visible_message("[C] [rapid_post_instake_message]")
return
diff --git a/code/modules/mob/living/simple_animal/bot/ed209bot.dm b/code/modules/mob/living/simple_animal/bot/ed209bot.dm
index ec63bdb27f4..7c3d29e71bb 100644
--- a/code/modules/mob/living/simple_animal/bot/ed209bot.dm
+++ b/code/modules/mob/living/simple_animal/bot/ed209bot.dm
@@ -560,7 +560,7 @@
var/threat = C.assess_threat(src)
var/prev_intent = a_intent
a_intent = INTENT_HELP
- baton.attack(C, src)
+ baton.attack__legacy__attackchain(C, src)
a_intent = prev_intent
baton_delayed = TRUE
addtimer(VARSET_CALLBACK(src, baton_delayed, FALSE), BATON_COOLDOWN)
diff --git a/code/modules/mob/living/simple_animal/bot/secbot.dm b/code/modules/mob/living/simple_animal/bot/secbot.dm
index e0c34eb5c94..510874a95a5 100644
--- a/code/modules/mob/living/simple_animal/bot/secbot.dm
+++ b/code/modules/mob/living/simple_animal/bot/secbot.dm
@@ -263,7 +263,7 @@
var/threat = C.assess_threat(src)
var/prev_intent = a_intent
a_intent = harmbaton ? INTENT_HARM : INTENT_HELP
- baton.attack(C, src)
+ baton.attack__legacy__attackchain(C, src)
a_intent = prev_intent
baton_delayed = TRUE
addtimer(VARSET_CALLBACK(src, baton_delayed, FALSE), BATON_COOLDOWN)
diff --git a/code/modules/surgery/organs/organ_internal.dm b/code/modules/surgery/organs/organ_internal.dm
index 2175c825bfe..22e2af8356a 100644
--- a/code/modules/surgery/organs/organ_internal.dm
+++ b/code/modules/surgery/organs/organ_internal.dm
@@ -211,7 +211,7 @@
if(S)
H.drop_item()
H.put_in_active_hand(S)
- S.attack(H, H)
+ S.attack__legacy__attackchain(H, H)
qdel(src)
else
..()
diff --git a/tools/ci/check_legacy_attack_chain.py b/tools/ci/check_legacy_attack_chain.py
index 5804a0235a0..b56a1027aff 100644
--- a/tools/ci/check_legacy_attack_chain.py
+++ b/tools/ci/check_legacy_attack_chain.py
@@ -22,6 +22,7 @@ class AttackChainCall:
var_type: p | None
call_name: str
source_info: any
+ legacy: bool
def make_error_message(self):
return f"{self.proc_decl.type_path}/{self.proc_decl.name}(...) calls {self.call_name}(...) on var {self.var_type}/{self.var_name}"
@@ -66,7 +67,7 @@ class AttackChainCallWalker:
return None
- def add_attack_call(self, var_name, chain_call, source_info):
+ def add_attack_call(self, var_name, chain_call, source_info, legacy):
var_type = self.get_var_type(var_name)
CALLS[var_type].add(
AttackChainCall(
@@ -75,6 +76,7 @@ class AttackChainCallWalker:
self.get_var_type(var_name),
chain_call,
source_info,
+ legacy=legacy,
)
)
@@ -83,15 +85,22 @@ class AttackChainCallWalker:
def visit_Expr(self, node, source_info):
if node.kind == NodeKind.CALL:
+ legacy = False
+ record = False
if "__legacy__attackchain" in node.name.name:
- if node.expr:
- if node.expr.kind == NodeKind.IDENTIFIER:
- self.add_attack_call(
- str(node.expr), node.name.name, source_info
- )
- elif node.expr.kind == NodeKind.CONSTANT:
- if not node.expr.constant.val:
- self.add_attack_call("src", node.name.name, source_info)
+ legacy = True
+ record = True
+ elif node.name.name in NEW_PROCS:
+ legacy = False
+ record = True
+ if record and node.expr:
+ if node.expr.kind == NodeKind.IDENTIFIER:
+ self.add_attack_call(
+ str(node.expr), node.name.name, source_info, legacy
+ )
+ elif node.expr.kind == NodeKind.CONSTANT:
+ if not node.expr.constant.val:
+ self.add_attack_call("src", node.name.name, source_info, legacy)
# Ignored types will never be part of the attack chain.
@@ -117,10 +126,21 @@ IGNORED_TYPES = [
ASSISTED_TYPES = [
p("/atom"),
p("/mob"),
+ p("/mob/living"),
p("/obj"),
p("/obj/item"),
]
+NEW_PROCS = [
+ "activate_self",
+ "after_attack",
+ "attack_by",
+ "attack",
+ "attacked",
+ "interact_with_atom",
+ "item_interaction",
+]
+
if __name__ == "__main__":
print("check_legacy_attack_chain started")
@@ -131,6 +151,7 @@ if __name__ == "__main__":
CALLS = defaultdict(set)
SETTING_CACHE = dict()
LEGACY_PROCS = dict()
+ MODERN_PROCS = dict()
BAD_TREES = dict()
PROCS = dict()
@@ -153,6 +174,7 @@ if __name__ == "__main__":
LEGACY_PROCS[pth] = {
x for x in td.proc_names(modified=True) if "__legacy__attackchain" in x
}
+ MODERN_PROCS[pth] = {x for x in td.proc_names(modified=True) if x in NEW_PROCS}
for proc_decl in td.proc_decls():
walker = AttackChainCallWalker(td, proc_decl)
proc_decl.walk(walker)
@@ -170,11 +192,23 @@ if __name__ == "__main__":
exit_code = 1
print(f"new_attack_chain on {pth} but related type {cursor} is not")
cursor = cursor.parent
- if pth in CALLS:
- exit_code = 1
- print("Call sites requiring migration:")
+ if pth in CALLS and any([x.legacy for x in CALLS[pth]]):
+ print("Legacy sites requiring migration:")
for call in CALLS[pth]:
- print(call.format_error())
+ if call.legacy:
+ print(call.format_error())
+ elif pth not in ASSISTED_TYPES:
+ if MODERN_PROCS[pth]:
+ exit_code = 1
+ print(f"new_attack_chain not on {pth} using new procs:")
+ for proc in sorted(MODERN_PROCS[pth]):
+ print(f"\t{proc}")
+ if pth in CALLS and any([not x.legacy for x in CALLS[pth]]):
+ exit_code = 1
+ print("Unexpected new call sites:")
+ for call in CALLS[pth]:
+ if not call.legacy:
+ print(call.format_error())
end = time.time()
print(f"check_legacy_attack_chain tests completed in {end - start:.2f}s\n")