pre cw with probe and a bunch script to read game behavior
This commit is contained in:
1 parent
5120219ae2
commit
da8a32f60d
310 files changed
+219693
-872
No files matched your search
@@ -15,11 +15,30 @@ parser.add_argument("--assembly", type=pathlib.Path, required=True)
|
||||
parser.add_argument("--client-version", required=True)
|
||||
parser.add_argument("--output", type=pathlib.Path, required=True)
|
||||
parser.add_argument("--merge", action="store_true", help="Preserve compatible previously captured floors in output")
|
||||
parser.add_argument("--dimension-index", type=int, help="Use captured SavedValues defaults from this RuntimeFloor dimension-list index")
|
||||
parser.add_argument("--omit-start-anchor", action="store_true", help="Keep per-entrance Anchor.json placements; retain the floor anchor only as floor_default_anchor for entries without StartAnchorID")
|
||||
args = parser.parse_args()
|
||||
floors, completed = {}, set()
|
||||
placements = {}
|
||||
placement_completed = set()
|
||||
props_completed = set()
|
||||
dimensions = {}
|
||||
dimension_groups = {}
|
||||
for line in args.log.read_text(encoding="utf-8", errors="replace").splitlines():
|
||||
if "[GAC_SCENE_DIMENSION] " in line:
|
||||
row = line.split("[GAC_SCENE_DIMENSION] ", 1)[1]
|
||||
groups = re.fullmatch(r"GROUPS (\d+) (\d+) (\d+) (.+)", row)
|
||||
if groups:
|
||||
floor, index, identity, raw = groups.groups()
|
||||
dimension_groups.setdefault(floor, {})[int(index)] = (int(identity), json.loads(raw))
|
||||
continue
|
||||
if "[GAC_SCENE_SAVED] " in line:
|
||||
row = line.split("[GAC_SCENE_SAVED] ", 1)[1]
|
||||
dimension = re.fullmatch(r"DIMENSION (\d+) (\d+) (.+)", row)
|
||||
if dimension:
|
||||
floor, index, raw = dimension.groups()
|
||||
dimensions.setdefault(floor, {})[int(index)] = json.loads(raw)
|
||||
continue
|
||||
if "[GAC_SCENE_PLACEMENT] " in line:
|
||||
row = line.split("[GAC_SCENE_PLACEMENT] ", 1)[1]
|
||||
end_placement = re.fullmatch(r"END (\d+)", row)
|
||||
@@ -37,6 +56,25 @@ for line in args.log.read_text(encoding="utf-8", errors="replace").splitlines():
|
||||
else:
|
||||
target["monsters"].setdefault(first_id, {})[inst_id] = value
|
||||
continue
|
||||
if "[GAC_SCENE_PROPS] " in line:
|
||||
row = line.split("[GAC_SCENE_PROPS] ", 1)[1]
|
||||
end_props = re.fullmatch(r"END (\d+)", row)
|
||||
prop = re.fullmatch(r"PROP (\d+) (\d+) (\d+) (\d+) (\d+) pos=([^,]+),([^,]+),([^ ]+) rot=(.+)", row)
|
||||
if end_props:
|
||||
props_completed.add(end_props[1])
|
||||
elif prop:
|
||||
floor, group_id, inst_id, prop_id, state, x, y, z, rot = prop.groups()
|
||||
if floor in floors:
|
||||
value = {
|
||||
"pos": {"x": round(float(x) * 1000), "y": round(float(y) * 1000), "z": round(float(z) * 1000)},
|
||||
"rot": {"x": 0, "y": round(float(rot) * 1000), "z": 0},
|
||||
"groupId": int(group_id),
|
||||
"instId": int(inst_id),
|
||||
"propState": int(state),
|
||||
"propId": int(prop_id),
|
||||
}
|
||||
floors[floor].setdefault("props", {}).setdefault(group_id, []).append(value)
|
||||
continue
|
||||
if "[GAC_SCENE_META]" not in line:
|
||||
continue
|
||||
row = line.split("[GAC_SCENE_META] ", 1)[1]
|
||||
@@ -45,7 +83,7 @@ for line in args.log.read_text(encoding="utf-8", errors="replace").splitlines():
|
||||
end = re.fullmatch(r"END (\d+)", row)
|
||||
if start:
|
||||
plane, floor, start_group, anchor, count = start.groups()
|
||||
floors[floor] = {"plane_id": int(plane), "group_count": int(count), "groups": {}}
|
||||
floors[floor] = {"plane_id": int(plane), "group_count": int(count), "groups": {}, "props": {}}
|
||||
completed.discard(floor)
|
||||
elif group:
|
||||
floor, group_id, side, hoyo, deleted = group.groups()
|
||||
@@ -53,11 +91,40 @@ for line in args.log.read_text(encoding="utf-8", errors="replace").splitlines():
|
||||
floors[floor]["groups"][group_id] = {"load_side": side, "is_hoyo_group": hoyo == "true", "is_deleted": deleted == "true"}
|
||||
elif end:
|
||||
completed.add(end[1])
|
||||
if not floors or any(key not in completed or len(value["groups"]) != value["group_count"] for key, value in floors.items()):
|
||||
if not floors or any(key not in completed or key not in props_completed or len(value["groups"]) != value["group_count"] for key, value in floors.items()):
|
||||
raise SystemExit("Incomplete scene metadata capture; no output written")
|
||||
for key, value in placements.items():
|
||||
if key in floors and key in placement_completed:
|
||||
floors[key].update(value)
|
||||
for key, floor in floors.items():
|
||||
if args.omit_start_anchor:
|
||||
anchor = floor.pop("start_anchor", None)
|
||||
if anchor is not None:
|
||||
floor["floor_default_anchor"] = anchor
|
||||
if args.dimension_index is not None:
|
||||
rows = dimensions.get(key, {}).get(args.dimension_index)
|
||||
if not isinstance(rows, list):
|
||||
raise SystemExit(f"Missing SavedValues capture for floor {key}, dimension index {args.dimension_index}; no output written")
|
||||
defaults, rules = {}, {}
|
||||
for row in rows:
|
||||
if row.get("IsDelete"):
|
||||
continue
|
||||
name = row["Name"]
|
||||
value, minimum, maximum = row["DefaultValue"], row["MinValue"], row["MaxValue"]
|
||||
allowed = row.get("AllowedValues", [])
|
||||
if name in defaults or not name or not minimum <= value <= maximum or (allowed and value not in allowed):
|
||||
raise SystemExit(f"Invalid captured SavedValue {key}/{name}; no output written")
|
||||
defaults[name] = value
|
||||
rules[name] = {"DefaultValue": value, "MinValue": minimum, "MaxValue": maximum}
|
||||
floor["saved_values"], floor["saved_value_rules"] = defaults, rules
|
||||
selected = dimension_groups.get(key, {}).get(args.dimension_index)
|
||||
if selected is None:
|
||||
raise SystemExit(f"Missing dimension group membership for floor {key}; no output written")
|
||||
identity, ids = selected
|
||||
if not ids or any(str(group) not in floor["groups"] for group in ids):
|
||||
raise SystemExit(f"Invalid dimension group membership for floor {key}; no output written")
|
||||
floor["dimension_id"] = identity
|
||||
floor["dimension_groups"] = {str(identity): sorted(set(ids))}
|
||||
result = {"client_version": args.client_version, "source": {
|
||||
"kind": "Client GameCoreConfigLoader.LoadRtLevelFloorInfo / GroupInstanceCommonMap",
|
||||
"assembly_sha256": hashlib.file_digest(args.assembly.open("rb"), "sha256").hexdigest(),
|
||||
@@ -69,6 +136,8 @@ if args.merge and args.output.exists():
|
||||
raise SystemExit("Cannot merge metadata from different Clients")
|
||||
previous["floors"].update(floors)
|
||||
result["floors"] = previous["floors"]
|
||||
# Keep provenance and policies for untouched floors when adding a capture.
|
||||
result["source"] = {**previous["source"], **result["source"]}
|
||||
result["source"]["previous_capture_sha256"] = previous["source"].get("previous_capture_sha256", []) + [previous["source"]["capture_sha256"]]
|
||||
args.output.write_text(json.dumps(result, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
||||
print(f"Exported {len(floors)} floors to {args.output}")
|
||||
Reference in new issue
Block a user