add CI check for exploitable recycle costs (#30286)

This commit is contained in:
warriorstar-orion
2025-09-04 07:00:12 +00:00
committed by GitHub
parent 3b7af6c771
commit 7022365e53
11 changed files with 76 additions and 8 deletions
@@ -714,6 +714,7 @@ Congratulations! You are now trained for invasive xenobiology research!"}
desc = "You've never seen this before."
icon = 'icons/obj/abductor.dmi'
origin_tech = "materials=2;biotech=2;abductor=2"
materials = list(MAT_METAL = 2000, MAT_GLASS = 2500)
toolspeed = 0.25
/obj/item/retractor/alien
@@ -721,6 +722,7 @@ Congratulations! You are now trained for invasive xenobiology research!"}
desc = "You're not sure if you want the veil pulled back."
icon = 'icons/obj/abductor.dmi'
origin_tech = "materials=2;biotech=2;abductor=2"
materials = list(MAT_METAL = 2000, MAT_GLASS = 3000)
toolspeed = 0.25
/obj/item/circular_saw/alien
@@ -222,6 +222,7 @@
icon_state = "knife-cheese"
materials = list(MAT_METAL = 4000)
force = 3
materials = list(MAT_METAL = 4000)
/obj/item/kitchen/knife/pizza_cutter
name = "pizza cutter"
@@ -229,6 +230,7 @@
icon_state = "pizza_cutter"
materials = list(MAT_METAL = 10000)
force = 8
materials = list(MAT_METAL = 10000)
/*
* Rolling Pins
@@ -138,6 +138,7 @@
throw_speed = 3
throw_range = 4
w_class = WEIGHT_CLASS_NORMAL
materials = list(MAT_GLASS = 4000, MAT_METAL = 1000)
/obj/item/shield/riot/tele/add_parry_component()
AddComponent(/datum/component/parry, _stamina_constant = 2, _stamina_coefficient = 0.7, _parryable_attack_types = ALL_ATTACK_TYPES, _parry_cooldown = (5 / 3) SECONDS, _requires_activation = TRUE)
@@ -223,6 +223,7 @@
desc = "A retracted hardlight stretcher that can be carried around."
icon_state = "holo_retracted"
w_class = WEIGHT_CLASS_SMALL
materials = list(MAT_METAL = 1000)
origin_tech = "magnets=3;biotech=4;powerstorage=3"
extended = /obj/structure/bed/roller/holo
@@ -254,7 +255,7 @@
/obj/item/roller_holder/interact_with_atom(atom/target, mob/living/user, list/modifiers)
if(!istype(target, /obj/item/roller))
return ..()
if(istype(target, /obj/item/roller/holo) && !carry_holo)
return ITEM_INTERACT_COMPLETE
+1 -1
View File
@@ -27,7 +27,7 @@
desc = "A gas mask with built in welding goggles and face shield. Looks like a skull, clearly designed by a nerd."
icon_state = "weldingmask"
item_state = "weldingmask"
materials = list(MAT_METAL=4000, MAT_GLASS=2000)
materials = list(MAT_METAL = 3000, MAT_GLASS = 1000)
flash_protect = FLASH_PROTECTION_WELDER
tint = FLASH_PROTECTION_WELDER
armor = list(MELEE = 5, BULLET = 0, LASER = 0, ENERGY = 0, BOMB = 0, RAD = 0, FIRE = INFINITY, ACID = 60)
@@ -676,7 +676,7 @@
name = "Goggles"
id = "goggles"
build_type = AUTOLATHE
materials = list(MAT_METAL = 100, MAT_GLASS = 100)
materials = list(MAT_METAL = 100, MAT_GLASS = 250)
build_path = /obj/item/clothing/glasses/goggles
category = list("initial", "Miscellaneous")
@@ -27,7 +27,7 @@
id = "plasmacutter"
req_tech = list("materials" = 3, "plasmatech" = 3, "magnets" = 2)
build_type = PROTOLATHE
materials = list(MAT_METAL = 1500, MAT_GLASS = 500, MAT_PLASMA = 400)
materials = list(MAT_METAL = 2000, MAT_GLASS = 500, MAT_PLASMA = 400)
build_path = /obj/item/gun/energy/plasmacutter
category = list("Mining")
@@ -30,7 +30,7 @@
id = "hyper_cell"
req_tech = list("powerstorage" = 5, "materials" = 5, "engineering" = 5)
build_type = PROTOLATHE | MECHFAB
materials = list(MAT_METAL = 700, MAT_GOLD = 150, MAT_SILVER = 150, MAT_GLASS = 70)
materials = list(MAT_METAL = 700, MAT_GOLD = 150, MAT_SILVER = 150, MAT_GLASS = 400)
construction_time=100
build_path = /obj/item/stock_parts/cell/hyper/empty
category = list("Misc", "Power", "Stock Parts")
@@ -41,7 +41,7 @@
id = "super_cell"
req_tech = list("powerstorage" = 3, "materials" = 3)
build_type = PROTOLATHE | MECHFAB
materials = list(MAT_METAL = 700, MAT_GLASS = 70)
materials = list(MAT_METAL = 700, MAT_GLASS = 300)
construction_time=100
build_path = /obj/item/stock_parts/cell/super/empty
category = list("Misc", "Power", "Stock Parts")
@@ -52,7 +52,7 @@
id = "bluespace_cell"
req_tech = list("powerstorage" = 6, "materials" = 5, "engineering" = 5, "bluespace" = 5)
build_type = PROTOLATHE | MECHFAB
materials = list(MAT_METAL = 800, MAT_GOLD = 120, MAT_GLASS = 160, MAT_DIAMOND = 160, MAT_TITANIUM = 300, MAT_BLUESPACE = 100)
materials = list(MAT_METAL = 800, MAT_GOLD = 120, MAT_GLASS = 600, MAT_DIAMOND = 160, MAT_TITANIUM = 300, MAT_BLUESPACE = 100)
construction_time=100
build_path = /obj/item/stock_parts/cell/bluespace/empty
category = list("Misc", "Power", "Stock Parts")
@@ -256,7 +256,7 @@
id = "techshotshell"
req_tech = list("combat" = 3, "materials" = 3, "powerstorage" = 4, "magnets" = 3)
build_type = PROTOLATHE
materials = list(MAT_METAL = 1000, MAT_GLASS = 200)
materials = list(MAT_METAL = 4000, MAT_GLASS = 200)
build_path = /obj/item/ammo_casing/shotgun/techshell
category = list("Weapons")
+1
View File
@@ -177,6 +177,7 @@
icon_state = "scalpel_laser1_on"
damtype = "fire"
hitsound = 'sound/weapons/sear.ogg'
materials = list(MAT_METAL = 2000, MAT_GLASS = 1000)
/obj/item/scalpel/laser/attack__legacy__attackchain(mob/living/carbon/target, mob/living/user)
if(!cigarette_lighter_act(user, target))
+61
View File
@@ -0,0 +1,61 @@
import sys
import time
from collections import namedtuple, defaultdict
from avulto import DME
RED = "\033[0;31m"
GREEN = "\033[0;32m"
BLUE = "\033[0;34m"
NC = "\033[0m" # No Color
ItemBOM = namedtuple("ItemBOM", ["build_costs", "recycle_costs"])
if __name__ == "__main__":
print("check_material_recycle_costs started")
exit_code = 0
start = time.time()
dme = DME.from_file("paradise.dme")
designs = dme.subtypesof("/datum/design")
costs = {}
for design in designs:
td = dme.types[design]
build_type = td.var_decl("build_type").const_val
build_path = td.var_decl("build_path").const_val
materials = td.var_decl("materials").const_val
if not build_type or not build_path or not build_path.child_of("/obj/item"):
continue
# ammo boxes update their materials dynamically based on contained ammo contents
if build_path.child_of("/obj/item/ammo_box"):
continue
result_type = dme.types[build_path]
material_content = result_type.var_decl("materials").const_val
if result_type not in costs:
costs[build_path] = ItemBOM(defaultdict(set), defaultdict(set))
bom = costs[build_path]
for x in materials.keys():
bom.build_costs[x].add(materials[x])
for x in material_content.keys():
bom.recycle_costs[x].add(material_content[x])
for pth in sorted(costs.keys()):
bom = costs[pth]
for matname, values in bom.build_costs.items():
if matname not in bom.recycle_costs:
continue
recycle_cost = min(bom.recycle_costs[matname])
if recycle_cost > max(values):
exit_code = 1
msg = f"{pth} has {matname} build cost {max(values)} and recycle cost {recycle_cost}"
print(msg)
end = time.time()
print(f"check_material_recycle_costs tests completed in {end - start:.2f}s\n")
sys.exit(exit_code)