Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Basic MCC Mozz support #78

Open
wants to merge 51 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
7aba949
Duplicate H1EK defs for MCC purposes
Dec 26, 2023
0e89283
MCC defs
Dec 26, 2023
8ee2ed4
Fix extraction bug with projectile velocity
Dec 26, 2023
cff6e7f
Import HEK definitions in unchanged files
Jan 5, 2024
1bab7d9
Simplify MCC changes further
Jan 15, 2024
eb62916
More padding changes for MCC defs
Jan 15, 2024
f02f42a
Removed unneeded files
Jan 15, 2024
60b8d84
Fixes for Stubbs
Jan 16, 2024
42e0e6b
Halo 1 MCC map extraction support
Jan 18, 2024
bd51f35
Halo 1 MCC map pointers
Jan 19, 2024
e79f055
initial fmod work
Jan 20, 2024
834acd7
More fmod work
Jan 20, 2024
e7d1894
fix some sizes
Jan 20, 2024
ee29200
Fix bug in reader
Jan 20, 2024
6b0bbf1
H1 MCC sound work
Jan 22, 2024
5b7094e
Recalculate pointers in fmod files when saving
Jan 22, 2024
ed98613
Fix MCC sound map being used improperly and filthy part indices not b…
Jan 22, 2024
aeb8c44
Better support for MCC resource files
Jan 23, 2024
aa77e56
bump version
Jan 23, 2024
ebbf9f3
Fix missing field in MCC damage_effect tags
Jan 23, 2024
ef01634
bump version
Jan 23, 2024
5e71c39
Def cleanup
Jan 26, 2024
f65a59b
bump verison
Jan 26, 2024
15c68a7
def cleanup
Feb 10, 2024
7f5bcfa
bump version
Feb 10, 2024
b76edc9
Include shader permutation in model dumps
Feb 10, 2024
69272e4
bump version
Feb 10, 2024
d34bd9f
Fix bitmap extraction
Feb 11, 2024
cceb9cd
bump version
Feb 11, 2024
c5b7d7f
Change how we define max element count
Feb 11, 2024
134c7e1
bump version
Feb 11, 2024
e3d071d
Specify size for firing block
Feb 12, 2024
d6a193f
Add support for older animation import files
Feb 12, 2024
b0a71ba
bump version
Feb 12, 2024
64a1064
Def cleanup and improved OS support
Feb 20, 2024
16b22c8
Bump version
Feb 20, 2024
76ebd11
Update audio conversion to work with python 3.12
Feb 26, 2024
46d2b8d
bump version
Feb 26, 2024
1f973d1
Sound playback and HEK+ workarounds
Mar 10, 2024
3377ea0
bump version
Mar 10, 2024
fbccd1a
Delete profile_data.txt
Mar 10, 2024
d2cabb5
Include new imports
Mar 10, 2024
f5ede5a
Fix script extraction and tag crc
Mar 10, 2024
9be7ccc
OGG support
Mar 16, 2024
255f0fa
Fixed sound playback bug
Mar 17, 2024
d63d5f1
Fix some regressions
Mar 18, 2024
fd10710
Fix H1X mip map issue
Mar 28, 2024
3395683
Fix previous commit
Mar 28, 2024
ca5f640
Set JMS versions
Mar 31, 2024
59d8e8e
Remove math import name
Mar 31, 2024
21e7fae
Fix effect saving
May 9, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
4 changes: 2 additions & 2 deletions reclaimer/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
# ##############
__author__ = "Sigmmma"
# YYYY.MM.DD
__date__ = "2021.03.23"
__version__ = (2, 11, 2)
__date__ = "2024.03.18"
__version__ = (2, 22, 0)
__website__ = "https://github.com/Sigmmma/reclaimer"
__all__ = (
"animation", "bitmaps", "h2", "h3", "halo_script", "hek", "meta", "misc",
Expand Down
18 changes: 9 additions & 9 deletions reclaimer/animation/animation_compilation.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"ANIMATION_COMPILE_MODE_NEW", "ANIMATION_COMPILE_MODE_PRESERVE",
"ANIMATION_COMPILE_MODE_ADDITIVE")

ANIMATION_COMPILE_MODE_NEW = 0
ANIMATION_COMPILE_MODE_NEW = 0
ANIMATION_COMPILE_MODE_PRESERVE = 1
ANIMATION_COMPILE_MODE_ADDITIVE = 2

Expand All @@ -39,9 +39,9 @@ def compile_animation(anim, jma_anim, ignore_size_limits=False, endian=">"):
default_data_size = jma_anim.default_data_size
frame_data_size = jma_anim.frame_data_frame_size * stored_frame_count

max_frame_info_size = anim.frame_info.get_desc('MAX', 'size')
max_default_data_size = anim.default_data.get_desc('MAX', 'size')
max_frame_data_size = anim.frame_data.get_desc('MAX', 'size')
max_frame_info_size = util.get_block_max(anim.frame_info)
max_default_data_size = util.get_block_max(anim.default_data)
max_frame_data_size = util.get_block_max(anim.frame_data)

if not ignore_size_limits:
if frame_info_size > max_frame_info_size:
Expand Down Expand Up @@ -103,10 +103,10 @@ def compile_animation(anim, jma_anim, ignore_size_limits=False, endian=">"):
return errors


def compile_model_animations(antr_tag, jma_anim_set, ignore_size_limits=False,
animation_count_limit=256, delta_tolerance=None,
update_mode=ANIMATION_COMPILE_MODE_PRESERVE,
mod2_nodes=None):
def compile_model_animations(
antr_tag, jma_anim_set, ignore_size_limits=False, delta_tolerance=None,
update_mode=ANIMATION_COMPILE_MODE_PRESERVE, mod2_nodes=None
):
errors = []

tagdata = antr_tag.data.tagdata
Expand Down Expand Up @@ -220,7 +220,7 @@ def compile_model_animations(antr_tag, jma_anim_set, ignore_size_limits=False,
anim_index = antr_indices_by_type_strings.get(
name_pieces, len(antr_anims))

if anim_index >= animation_count_limit:
if anim_index >= util.get_block_max(tagdata.animations):
errors.append(
"Too many animations. Cannot add '%s'" % jma_anim_name)
continue
Expand Down
119 changes: 100 additions & 19 deletions reclaimer/animation/jma.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,18 @@
".jma", ".jmm", ".jmo", ".jmr", ".jmt", ".jmw", ".jmz",
)

JMA_VER_HALO_1_OLDEST_KNOWN = 16390
JMA_VER_HALO_1_NODE_NAMES = 16391
JMA_VER_HALO_1_NODE_HIERARCHY = 16392
JMA_VER_HALO_1_RETAIL = JMA_VER_HALO_1_NODE_HIERARCHY

JMA_VER_ALL = frozenset((
JMA_VER_HALO_1_OLDEST_KNOWN,
JMA_VER_HALO_1_NODE_NAMES,
JMA_VER_HALO_1_NODE_HIERARCHY,
JMA_VER_HALO_1_RETAIL,
))


def get_anim_ext(anim_type, frame_info_type, world_relative=False):
anim_type = anim_type.lower()
Expand Down Expand Up @@ -150,6 +162,7 @@ class JmaAnimation:
node_list_checksum = 0
nodes = ()
frames = ()
version = 0

rot_keyframes = ()
trans_keyframes = ()
Expand All @@ -173,7 +186,9 @@ class JmaAnimation:

def __init__(self, name="", node_list_checksum=0,
anim_type="", frame_info_type="", world_relative=False,
nodes=None, frames=None, actors=None, frame_rate=30):
nodes=None, frames=None, actors=None, frame_rate=30,
version=JMA_VER_HALO_1_RETAIL,
):

self.name = name.strip(" ")
self.node_list_checksum = node_list_checksum
Expand All @@ -184,6 +199,7 @@ def __init__(self, name="", node_list_checksum=0,
self.frame_info_type = frame_info_type
self.frame_rate = frame_rate
self.actors = actors if actors else ["unnamedActor"]
self.version = version

self.root_node_info = []
self.setup_keyframes()
Expand All @@ -203,6 +219,11 @@ def has_keyframe_data(self):
return False
return True

@property
def has_node_names(self): return self.version >= JMA_VER_HALO_1_NODE_NAMES
@property
def has_node_hierarchy(self): return self.version >= JMA_VER_HALO_1_NODE_HIERARCHY

@property
def ext(self):
return get_anim_ext(self.anim_type, self.frame_info_type,
Expand Down Expand Up @@ -438,8 +459,27 @@ def calculate_root_node_info(self):
z += dz
yaw += dyaw

verify_nodes_valid = JmsModel.verify_nodes_valid
get_node_depths = JmsModel.get_node_depths
def get_node_depths(self):
if self.has_node_hierarchy:
return JmsModel.get_node_depths(self)
return []

def verify_nodes_valid(self):
errors = []
if self.has_node_hierarchy or len(self.nodes) not in range(1, 64):
return JmsModel.verify_nodes_valid(self)
elif self.has_node_names:
seen_names = set()
for i in range(len(self.nodes)):
n = self.nodes[i]
if len(n.name) >= 32:
errors.append("Node name node '%s' is too long." % n.name)
elif n.name.lower() in seen_names:
errors.append("Multiple nodes named '%s'." % n.name)

seen_names.add(n.name.lower())

return errors

def calculate_animation_flags(self, tolerance=None):
if tolerance is None:
Expand Down Expand Up @@ -486,9 +526,15 @@ def verify_animations_match(self, other_jma):
errors.append("Node counts do not match.")
return errors

for i in range(len(self.nodes)):
if not self.nodes[i].is_node_hierarchy_equal(other_jma.nodes[i]):
errors.append("Nodes '%s' do not match." % i)
if self.has_node_names and other_jma.has_node_names:
for i in range(len(self.nodes)):
if not self.nodes[i].is_node_hierarchy_equal(
other_jma.nodes[i], not (
self.has_node_hierarchy and
other_jma.has_node_hierarchy
)
):
errors.append("Nodes '%s' do not match." % i)

return errors

Expand Down Expand Up @@ -516,6 +562,7 @@ class JmaAnimationSet:
node_list_checksum = 0
nodes = ()
animations = ()
version = JMA_VER_HALO_1_OLDEST_KNOWN

def __init__(self, *jma_animations):
self.nodes = []
Expand All @@ -526,14 +573,27 @@ def __init__(self, *jma_animations):

verify_animations_match = JmaAnimation.verify_animations_match

@property
def has_node_names(self): return self.version >= JMA_VER_HALO_1_NODE_NAMES
@property
def has_node_hierarchy(self): return self.version >= JMA_VER_HALO_1_NODE_HIERARCHY

def merge_jma_animation(self, other_jma):
assert isinstance(other_jma, JmaAnimation)

if not other_jma:
return

if other_jma.version > self.version and self.nodes:
# if the other jma's version is newer, grab its nodes if they
# match well enough, since they'll contain more detailed data
if (self.node_list_checksum == other_jma.node_list_checksum and
not self.verify_animations_match(other_jma)):
self.nodes = []

if not self.nodes:
self.node_list_checksum = other_jma.node_list_checksum
self.version = other_jma.version
self.nodes = []
for node in other_jma.nodes:
self.nodes.append(
Expand All @@ -559,15 +619,18 @@ def read_jma(jma_string, stop_at="", anim_name=""):
anim_name, ext = os.path.splitext(anim_name)
anim_type, frame_info_type, world_relative = get_anim_types(ext)

jma_anim = JmaAnimation(anim_name, 0, anim_type,
frame_info_type, world_relative)
jma_string = jma_string.replace("\n", "\t")

data = tuple(d for d in jma_string.split("\t") if d)
dat_i = 0
version = parse_jm_int(data[dat_i])

if parse_jm_int(data[dat_i]) != 16392:
print("JMA identifier '16392' not found.")
jma_anim = JmaAnimation(anim_name, 0, anim_type,
frame_info_type, world_relative,
version=version)

if version not in JMA_VER_ALL:
print("Unknown JMA version '%s' found." % version)
return jma_anim

dat_i += 1
Expand Down Expand Up @@ -640,17 +703,31 @@ def read_jma(jma_string, stop_at="", anim_name=""):
print("Could not read node list checksum.")
return jma_anim

if jma_anim.node_list_checksum >= 0x80000000:
# jma gave us an unsigned checksum.... sign it
jma_anim.node_list_checksum -= 0x100000000

if stop_at == "nodes": continue

# read the nodes
try:
i = 0 # make sure i is defined in case of exception
nodes[:] = [None] * node_count
for i in range(node_count):
nodes[i] = JmsNode(
data[dat_i], parse_jm_int(data[dat_i+1]), parse_jm_int(data[dat_i+2])
)
dat_i += 3
if jma_anim.has_node_hierarchy:
node_data = (
data[dat_i],
parse_jm_int(data[dat_i+1]),
parse_jm_int(data[dat_i+2])
)
dat_i += 3
elif jma_anim.has_node_names:
node_data = (data[dat_i], )
dat_i += 1
else:
node_data = ("fake_node_%d" % i, )

nodes[i] = JmsNode(*node_data)
JmsNode.setup_node_hierarchy(nodes)
except Exception:
print(traceback.format_exc())
Expand Down Expand Up @@ -705,7 +782,7 @@ def write_jma(filepath, jma_anim, use_blitzkrieg_rounding=False):
jma_anim.apply_root_node_info_to_states()

with filepath.open("w", encoding='latin1', newline="\r\n") as f:
f.write("16392\n") # version number
f.write("%d\n" % jma_anim.version)
f.write("%s\n" % jma_anim.frame_count)
f.write("%s\n" % jma_anim.frame_rate)

Expand All @@ -720,10 +797,14 @@ def write_jma(filepath, jma_anim, use_blitzkrieg_rounding=False):
f.write("%s\n" % len(nodes))
f.write("%s\n" % int(jma_anim.node_list_checksum))

for node in nodes:
f.write("%s\n%s\n%s\n" %
(node.name[: 31], node.first_child, node.sibling_index)
)
if jma_anim.has_node_hierarchy:
for node in nodes:
f.write("%s\n%s\n%s\n" %
(node.name[: 31], node.first_child, node.sibling_index)
)
elif jma_anim.has_node_names:
for node in nodes:
f.write("%s\n" % node.name[: 31])

for frame in frames:
for nf in frame:
Expand Down
25 changes: 16 additions & 9 deletions reclaimer/animation/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@

from math import pi

from reclaimer.util import get_block_max
from reclaimer.enums import unit_animation_names, unit_weapon_animation_names,\
unit_weapon_type_animation_names, vehicle_animation_names,\
weapon_animation_names, device_animation_names, fp_animation_names,\
weapon_animation_names, device_animation_names, fp_animation_names_mcc,\
unit_damage_animation_names
from reclaimer.hek.defs.mod2 import TagDef, Pad, marker as mod2_marker_desc,\
node as mod2_node_desc, reflexive, Struct
Expand Down Expand Up @@ -89,7 +90,7 @@ def split_anim_name_into_type_strings(anim_name):
if part3: remainder = " ".join((part3, remainder))

part2, part2_sani, perm_num = split_permutation_number(part2, remainder)
if ((part1_sani == "first-person" and part2_sani in fp_animation_names) or
if ((part1_sani == "first-person" and part2_sani in fp_animation_names_mcc) or
(part1_sani == "vehicle" and part2_sani in vehicle_animation_names) or
(part1_sani == "device" and part2_sani in device_animation_names)):
type_strings = part1_sani, part2_sani, perm_num
Expand Down Expand Up @@ -215,9 +216,10 @@ def set_animation_index(antr_tag, anim_name, anim_index,

if part1 == "suspension":
anim_name = anim_name.split(" ", 1)[1]
anim_enums = antr_vehicles[0].suspension_animations.STEPTREE
if len(anim_enums) >= 8:
# max of 8 suspension animations
susp_anims = antr_vehicles[0].suspension_animations
anim_enums = susp_anims.STEPTREE
if len(anim_enums) >= get_block_max(susp_anims):
# at the limit of suspension animations
return False

anim_enums.append()
Expand All @@ -226,7 +228,8 @@ def set_animation_index(antr_tag, anim_name, anim_index,

elif part1 in ("first-person", "device", "vehicle"):
if part1 == "first-person":
options = fp_animation_names
# NOTE: using mcc because they're the same, with an extension
options = fp_animation_names_mcc
block = antr_fp_animations
elif part1 == "device":
options = device_animation_names
Expand All @@ -238,6 +241,10 @@ def set_animation_index(antr_tag, anim_name, anim_index,
if not block:
block.append()

# trim the options to how many are actually allowed
# NOTE: this is really just for fp_animation_names_mcc
max_anims = get_block_max(block[0].animations)
options = options[:max_anims]
try:
enum_index = options.index(part2)
except ValueError:
Expand Down Expand Up @@ -338,7 +345,7 @@ def set_animation_index(antr_tag, anim_name, anim_index,
break

if unit is None:
if len(antr_units) >= antr_units.MAX:
if len(antr_units) >= get_block_max(antr_units.parent):
return False

antr_units.append()
Expand Down Expand Up @@ -369,7 +376,7 @@ def set_animation_index(antr_tag, anim_name, anim_index,
break

if unit_weap is None:
if len(unit_weaps) >= unit_weaps.MAX:
if len(unit_weaps) >= get_block_max(unit_weaps.parent):
return False

unit_weaps.append()
Expand Down Expand Up @@ -397,7 +404,7 @@ def set_animation_index(antr_tag, anim_name, anim_index,
break

if unit_weap_type is None:
if len(unit_weap_types) >= unit_weap_types.MAX:
if len(unit_weap_types) >= get_block_max(unit_weap_types.parent):
return False

unit_weap_types.append()
Expand Down
Loading