2025-03-09 16:28:03 -04:00
#!/bin/env python3
import sys
import json
# weapon required data
WEAPONS = {
"Laser" : {
"T0" : {
"damage_inflicted" : 42 ,
2025-03-15 11:03:35 -04:00
"group_fire_scales" : [ 1.0 ],
2025-03-09 16:28:03 -04:00
},
"T1" : {
"damage_inflicted" : 420 ,
2025-03-15 11:03:35 -04:00
"group_fire_scales" : [ 1.0 ],
2025-03-09 16:28:03 -04:00
},
"T2" : {
"damage_inflicted" : 4200 ,
2025-03-15 11:03:35 -04:00
"group_fire_scales" : [ 1.0 ],
2025-03-09 16:28:03 -04:00
},
"T3" : {
"damage_inflicted" : 42000 ,
2025-03-15 11:03:35 -04:00
"group_fire_scales" : [ 1.0 ],
2025-03-09 16:28:03 -04:00
},
"T4" : {
"damage_inflicted" : 420000 ,
2025-03-15 11:03:35 -04:00
"group_fire_scales" : [ 1.0 ],
2025-03-09 16:28:03 -04:00
},
"T5" : {
"damage_inflicted" : 4200000 ,
2025-03-15 11:03:35 -04:00
"group_fire_scales" : [ 1.0 ],
2025-03-09 16:28:03 -04:00
},
},
}
2025-03-15 11:03:35 -04:00
STATS_TRANSLATIONS = {
"CPU LOAD" : "strCPU" ,
"CPU" : "strCPU" ,
"MASS" : "strMass" ,
"ARMOR" : "strHealth" ,
"ROBOT RANKING" : "strRobotRanking" ,
"ROBOT RATING" : "strRobotRanking" ,
"MAX LIFT" : "strLiftDS" ,
"LIFT" : "strLiftDS" ,
"MAX SPEED" : "strMaxSpeedDS" ,
"CARRYING CAPACITY" : "strCapacity" ,
"LOAD CAPACITY PER WING" : "strCapacity" ,
"TOP SPEED" : "strMaxSpeedDS" ,
"DAMAGE AT 160M" : "strDamageNearDS" ,
"DAMAGE AT 320M" : "strDamageFarDS" ,
"DAMAGE" : "strDamageDS" ,
"BLAST" : "strBlastRadiusDS" ,
"DAMAGE RATE" : "strWeaponDamageRateDS" ,
"HEAL RATE" : "strHealRate" ,
}
STATS_VALUE_REPLACEMENTS = {
"pFLOP" : "[strPFlops]" ,
"Kg" : "[strKilograms]" ,
"kg" : "[strKilograms]" ,
}
IGNORED_STATS = [
"OVERCLOCK" ,
"OVERCLOCKER" ,
"THRUST" ,
"SHIELD" ,
"LIGHT OUTPUT" ,
]
2025-03-09 16:28:03 -04:00
# different from the enum
'''CATEGORIES = {
0: "NotAFunctionalItem",
1: "Wheel",
2: "Hover",
3: "Wing",
4: "Rudder",
5: "Thruster",
6: "InsectLeg",
7: "MechLeg",
8: "Ski",
9: "TankTrack",
10: "Rotor",
11: "SprinterLeg",
12: "Propeller",
100: "Laser",
200: "Plasma",
250: "Mortar",
300: "Rail",
400: "Nano",
500: "Tesla",
600: "Aeroflak",
650: "Ion",
701: "Seeker",
750: "Chaingun",
800: "ShieldModule",
801: "GhostModule",
802: "BlinkModule",
803: "EmpModule",
804: "WindowmakerModule",
900: "EnergyModule",
}'''
CATEGORIES = [
"NotAFunctionalItem" ,
"Wheel" ,
"Hover" ,
"Wing" ,
"Rudder" ,
"Thruster" ,
"InsectLeg" ,
"MechLeg" ,
"Ski" ,
"TankTrack" ,
"Rotor" ,
"SprinterLeg" ,
"Propeller" ,
"Laser" , #13
"Plasma" ,
"Mortar" ,
"Rail" ,
"Nano" ,
"Tesla" ,
"Aeroflak" ,
"Ion" ,
"Seeker" ,
"Chaingun" ,
"ShieldModule" , #23
"GhostModule" ,
"BlinkModule" ,
"EmpModule" ,
"WindowmakerModule" ,
"EnergyModule" ,
]
2025-03-15 11:03:35 -04:00
CATEGORY_IGNORES = [
"VaporTrail" ,
"Vapor_Trail" ,
"FusionTower" ,
]
ALL_FACES = 63 ;
CATEGORIES_PLACEMENTS = {
"NotAFunctionalItem" : ALL_FACES ,
"Wheel" : 0b00001100 ,
"Hover" : 0b00111100 ,
"Wing" : 0b00111100 ,
"Rudder" : 0b00111100 ,
"Thruster" : ALL_FACES ,
"InsectLeg" : 0b00111100 ,
"MechLeg" : 0b00000011 ,
"Ski" : 0b00000011 ,
"TankTrack" : 0b00111100 ,
"Rotor" : 0b00111100 ,
"SprinterLeg" : 0b00000011 ,
"Propeller" : ALL_FACES ,
"Laser" : None ,
"Plasma" : ALL_FACES ,
"Mortar" : 0b00000011 ,
"Rail" : ALL_FACES ,
"Nano" : ALL_FACES ,
"Tesla" : ALL_FACES ,
"Aeroflak" : ALL_FACES ,
"Ion" : ALL_FACES ,
"Seeker" : ALL_FACES ,
"Chaingun" : ALL_FACES ,
"ShieldModule" : ALL_FACES ,
"GhostModule" : ALL_FACES ,
"BlinkModule" : ALL_FACES ,
"EmpModule" : ALL_FACES ,
"WindowmakerModule" : ALL_FACES ,
"EnergyModule" : ALL_FACES ,
}
def guess_category ( name : str , sprite : str ) -> str :
2025-03-09 16:28:03 -04:00
name = name . lower ()
sprite = sprite . lower ()
for variant in CATEGORIES :
variant_sanitized = variant . lower ()
2025-03-15 11:03:35 -04:00
if ( variant_sanitized in name and not str_contains_any ( name , CATEGORY_IGNORES )) or ( variant_sanitized in sprite and not str_contains_any ( sprite , CATEGORY_IGNORES )):
2025-03-09 16:28:03 -04:00
return variant
return CATEGORIES [ 0 ]
2025-03-15 11:03:35 -04:00
def str_contains_any ( s : str , l : list ) -> bool :
for variant in l :
variant_sanitized = variant . lower ()
if variant_sanitized in s :
print ( s + " contains " + variant )
return True
return False
def guess_type ( category : str ) -> str :
2025-03-09 16:28:03 -04:00
cat_i = CATEGORIES . index ( category )
if cat_i == 0 :
return "NotAFunctionalItem"
elif cat_i >= 23 :
return "Module"
elif cat_i >= 13 :
return "Weapon"
elif cat_i >= 1 :
return "Movement"
else :
return "NotAFunctionalItem"
def guess_tier ( name : str , sprite : str ) -> str :
2025-03-15 11:03:35 -04:00
if guess_category ( name , sprite ) == "NotAFunctionalItem" and "medium" in name . lower (): # Medium cube variants
return "NoTier"
return guess_tier_by_name_str_key ( name ) or guess_tier_by_size ( sprite ) or guess_tier_by_name_str_key ( sprite ) or "NoTier"
2025-03-09 16:28:03 -04:00
def guess_tier_by_size ( sprite : str ) -> str :
sprite = sprite . lower ()
if "tiny" in sprite :
return "T0"
elif "small" in sprite :
return "T1"
elif "medium" in sprite :
return "T2"
elif "large" in sprite :
return "T3"
elif "huge" in sprite :
return "T4"
elif "mega" in sprite :
return "T5"
else :
return None
def guess_tier_by_name_str_key ( name : str ) -> str :
name = name . upper ()
if "T0" in name :
return "T0"
elif "T1" in name :
return "T1"
elif "T2" in name :
return "T2"
elif "T3" in name :
return "T3"
elif "T4" in name :
return "T4"
elif "T5" in name :
return "T5"
else :
return None
def placements_to_int ( placements : dict ) -> int :
return int ( placements [ "1 UInt8 up" ]) | \
int ( placements [ "1 UInt8 down" ]) << 1 | \
int ( placements [ "1 UInt8 left" ]) << 2 | \
int ( placements [ "1 UInt8 right" ]) << 3 | \
int ( placements [ "1 UInt8 back" ]) << 4 | \
int ( placements [ "1 UInt8 front" ]) << 5
2025-03-15 11:03:35 -04:00
def guess_placement ( placements : dict , category : str , name : str ) -> int :
by_category = CATEGORIES_PLACEMENTS [ category ]
if by_category is not None :
return by_category
name = name . lower ()
if "front" in name :
return 0b0011000000
elif category == "Laser" :
return ALL_FACES
return placements_to_int ( placements )
def translate_stat_key ( key : str ) -> str :
return STATS_TRANSLATIONS [ key . upper ()]
def replace_stat_values ( value : str ) -> str :
for replace in STATS_VALUE_REPLACEMENTS . keys ():
value = value . replace ( replace , STATS_VALUE_REPLACEMENTS [ replace ])
return value
VARIANT_STRINGS = [
"frontlaser" ,
"golden" ,
"carbon6" ,
"egglauncher" ,
"cardlife" ,
"seekerfirework" ,
"rudderbat" ,
"wingbat" ,
"legspider" ,
]
def is_variant_guess ( name : str , sprite : str ) -> bool :
name_lower = name . lower ()
for s in VARIANT_STRINGS :
if s in name_lower :
return True
return False
2025-03-09 16:28:03 -04:00
def main ():
print ( sys . argv )
filename_in = sys . argv [ 1 ]
with open ( filename_in ) as f :
cubes_asset = json . load ( f )
name = cubes_asset [ "0 MonoBehaviour Base" ][ "1 string m_Name" ]
print ( f "found name (expected to be empty): ` { name } `" )
cubes = cubes_asset [ "0 MonoBehaviour Base" ][ "0 CubeTypeData cubeTypes" ][ "0 Array Array" ]
print ( f "found { len ( cubes ) } cubes to process" )
cubes_out = {
"cubes" : dict (),
"movement" : dict (),
"lerp_value" : 10.0 ,
}
2025-03-15 11:03:35 -04:00
last_tech_tree_id = 0
tech_tree_index = 0
tech_tree_specials_index = 0
2025-03-09 16:28:03 -04:00
for i in range ( len ( cubes )):
#print(f"processing cube {i}")
cube = cubes [ i ][ "0 CubeTypeData data" ]
name = str ( cube [ "1 string nameStrKey" ])
if name . startswith ( "str" ):
name = name [ 3 :]
if name . endswith ( "Name" ):
name = name [: - 4 ]
new_key = name + " hexCode:" + str ( cube [ "1 string itemCode" ]) + " intCode:" + str ( cube [ "0 unsigned int itemCodeValue" ])
stats = dict ()
for stat_i in range ( 1 , 7 ): # 1 to 6 (inclusive)
stat_key = "1 string stat" + str ( stat_i )
if stat_key in cube :
stat_val = cube [ stat_key ]
#print(f"stat {stat_key} -> {stat_val}")
if len ( stat_val ) == 0 :
continue
if " = " in stat_val :
new_stat_key = str ( cube [ stat_key ]) . split ( " = " )[ 0 ]
new_stat_val = str ( cube [ stat_key ]) . split ( " = " )[ 1 ]
else :
new_stat_key = str ( cube [ stat_key ]) . split ( ": " )[ 0 ]
new_stat_val = str ( cube [ stat_key ]) . split ( ": " )[ 1 ]
stats [ new_stat_key ] = new_stat_val
2025-03-15 11:03:35 -04:00
category = guess_category ( cube [ "1 string nameStrKey" ], cube [ "1 string spriteName" ]);
2025-03-09 16:28:03 -04:00
new_entry = {
"id" : int ( cube [ "0 unsigned int itemCodeValue" ]),
"info" : {
2025-03-15 11:03:35 -04:00
"category" : category ,
"placements" : guess_placement ( cube [ "0 PersistentCubeData cubeData" ][ "0 CubeFaces selectableFaces" ], category , cube [ "1 string nameStrKey" ]),
2025-03-09 16:28:03 -04:00
"stats" : stats , # required
"description" : str ( cube [ "1 string description" ]), # required
"size" : guess_tier ( cube [ "1 string nameStrKey" ], cube [ "1 string spriteName" ]), # required
2025-03-15 11:03:35 -04:00
"type" : guess_type ( category ),
2025-03-09 16:28:03 -04:00
"active" : int ( cube [ "1 UInt8 active" ]) != 0 , # ignored
},
# ignored
"spriteName" : cube [ "1 string spriteName" ],
"nameStrKey" : cube [ "1 string nameStrKey" ],
"mirrorCubeId" : cube [ "0 PersistentCubeData cubeData" ][ "1 string mirrorCubeId" ],
"hexId" : str ( cube [ "1 string itemCode" ]),
}
2025-03-15 11:03:35 -04:00
if "protonium" in name . lower ():
new_entry [ "info" ][ "protonium" ] = True
2025-03-09 16:28:03 -04:00
if "CPU LOAD" in stats :
new_entry [ "info" ][ "cpu" ] = int ( stats [ "CPU LOAD" ] . strip () . split ( " " )[ 0 ] . strip ())
if "ARMOR" in stats :
new_entry [ "info" ][ "health" ] = int ( stats [ "ARMOR" ] . replace ( "," , "" ) . strip ())
2025-03-15 11:03:35 -04:00
translated_stats = dict ()
for ( key , val ) in new_entry [ "info" ][ "stats" ] . items ():
if key not in IGNORED_STATS :
trans_key = translate_stat_key ( key )
translated_stats [ trans_key ] = replace_stat_values ( val )
new_entry [ "info" ][ "stats" ] = translated_stats
2025-03-09 16:28:03 -04:00
if len ( new_entry [ "info" ][ "description" ] . strip ()) == 0 :
new_entry [ "info" ][ "description" ] = name + " (" + str ( cube [ "0 unsigned int itemCodeValue" ]) + "_10|" + str ( cube [ "1 string itemCode" ]) + "_16) without a description"
2025-03-15 11:03:35 -04:00
else :
new_entry [ "info" ][ "description" ] += " (" + str ( cube [ "0 unsigned int itemCodeValue" ]) + "_10|" + str ( cube [ "1 string itemCode" ]) + "_16)"
is_original = not is_variant_guess ( cube [ "1 string nameStrKey" ], cube [ "1 string spriteName" ])
# weapons
if new_entry [ "info" ][ "type" ] == "Weapon" :
if is_original and "module" not in new_entry [ "info" ][ "category" ] . lower (): # ignore variants and modules
print ( name , new_entry [ "info" ][ "category" ], new_entry [ "info" ][ "size" ])
tier_num = int ( new_entry [ "info" ][ "size" ][ 1 ])
new_entry [ "weapon" ] = {
"damage_inflicted" : i ,
"group_fire_scales" : [ 1.0 ],
}
new_entry [ "weapon_upgrade" ] = {
"xp" : tier_num + 1.0 ,
"rating" : tier_num ,
"rank" : tier_num ,
"power" : 0 ,
}
if new_entry [ "info" ][ "category" ] in WEAPONS :
if new_entry [ "info" ][ "size" ] in WEAPONS [ new_entry [ "info" ][ "category" ]]:
new_entry [ "weapon" ] = WEAPONS [ new_entry [ "info" ][ "category" ]][ new_entry [ "info" ][ "size" ]]
new_entry [ "info" ][ "ignore_in_weapon_list" ] = new_entry [ "info" ][ "type" ] != "Weapon"
# tech tree
if new_entry [ "info" ][ "category" ] != "NotAFunctionalItem" and is_original :
if last_tech_tree_id != 0 :
neighbours = [ last_tech_tree_id ]
else :
neighbours = []
last_tech_tree_id = new_entry [ "id" ]
new_entry [ "tree" ] = {
"position_x" : tech_tree_index % 16 ,
"position_y" : tech_tree_index // 16 ,
"tech_points" : i ,
"neighbours" : neighbours ,
"requires" : neighbours ,
}
tech_tree_index += 1
if not is_original :
new_entry [ "tree" ] = {
"position_x" : tech_tree_specials_index % 16 ,
"position_y" : ( tech_tree_specials_index // 16 ) + 8 ,
"tech_points" : i ,
"neighbours" : [],
"requires" : [ 227205318 ], # default cube (MediumCube)
}
tech_tree_specials_index += 1
2025-03-09 16:28:03 -04:00
print ( f "processed cube { i } into { new_entry } " )
cubes_out [ "cubes" ][ new_key ] = new_entry
with open ( "../assets/robocraft/cubes.json" , "w" ) as f :
json . dump ( cubes_out , f , indent = 4 )
print ( f "processed { len ( cubes ) } cubes" )
if __name__ == "__main__" :
main ()