Skip to content

Commit

Permalink
convert: Graphics export logic.
Browse files Browse the repository at this point in the history
  • Loading branch information
heinezen committed Feb 14, 2020
1 parent cdba26d commit f9f32c1
Show file tree
Hide file tree
Showing 9 changed files with 164 additions and 94 deletions.
29 changes: 27 additions & 2 deletions openage/convert/dataformat/aoc/combined_sprite.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,15 @@ def get_filename(self):
"""
return self.filename

def get_graphics(self):
"""
Return all graphics referenced by this sprite.
"""
graphics = [self.data.genie_graphics[self.head_sprite_id]]
graphics.extend(self.data.genie_graphics[self.head_sprite_id].get_subgraphics())

return graphics

def get_id(self):
"""
Returns the head sprite ID of the sprite.
Expand All @@ -79,9 +88,25 @@ def remove_reference(self, referer):
"""
self._refs.remove(referer)

def resolve_location(self):
def resolve_graphics_location(self):
"""
Returns the planned location in the modpack of all image files
referenced by the sprite.
"""
location_dict = {}

for graphic in self.get_graphics():
if graphic.is_shared():
location_dict.update({graphic.get_id(): "data/game_entity/shared/graphics/"})

else:
location_dict.update({graphic.get_id(): self.resolve_sprite_location()})

return location_dict

def resolve_sprite_location(self):
"""
Returns the location of the definition file in the modpack.
Returns the planned location of the definition file in the modpack.
"""
if len(self._refs) > 1:
return "data/game_entity/shared/graphics/"
Expand Down
42 changes: 42 additions & 0 deletions openage/convert/dataformat/aoc/genie_graphic.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,47 @@ def __init__(self, graphic_id, full_data_set, members=None):

self.data = full_data_set

# Direct subgraphics (deltas) of this graphic
self.subgraphics = []

# Other graphics that have this graphic as a subgraphic (delta)
self._refs = []

def add_reference(self, referer):
"""
Add another graphic that is referencing this sprite.
"""
self._refs.append(referer)

def detect_subgraphics(self):
"""
Add references for the direct subgraphics to this object.
"""
graphic_deltas = self.get_member("graphic_deltas").get_value()

for subgraphic in graphic_deltas:
graphic_id = subgraphic.get_value()["graphic_id"].get_value()

# Ignore invalid IDs
if graphic_id < 0:
continue

graphic = self.data.genie_graphics[graphic_id]

self.subgraphics.append(graphic)
graphic.add_reference(self)

def get_subgraphics(self):
"""
Return the subgraphics of this graphic
"""
return self.subgraphics

def is_shared(self):
"""
Return True if the number of references to this graphic is >1.
"""
return len(self._refs) > 1

def __repr__(self):
return "GenieGraphic<%s>" % (self.get_id())
6 changes: 3 additions & 3 deletions openage/convert/dataformat/modpack.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,11 @@ def add_media_export(self, export_request):
raise Exception("%s: export file must be of type MediaExportRequest"
"not %s" % (self, type(export_request)))

if export_request.get_media_type() in self.media_export_files.keys():
self.media_export_files[export_request.get_media_type()].append(export_request)
if export_request.get_type() in self.media_export_files.keys():
self.media_export_files[export_request.get_type()].append(export_request)

else:
self.media_export_files[export_request.get_media_type()] = [export_request]
self.media_export_files[export_request.get_type()] = [export_request]

def get_info(self):
"""
Expand Down
8 changes: 4 additions & 4 deletions openage/convert/dataformat/version_detect.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ class GameExpansion(Enum):
Support.nope,
{("resources/_common/dat/empires2_x2_p1.dat", {})},
{MediaType.GRAPHICS: (False, ["resources/_common/slp/"]),
MediaType.SOUNDS: (True, ["resources/_common/sound/"]),
MediaType.INTERFACE: (True, ["resources/_common/drs/interface/"]),
MediaType.TERRAIN: (True, ["resources/_common/terrain"])},
MediaType.SOUNDS: (False, ["resources/_common/sound/"]),
MediaType.INTERFACE: (False, ["resources/_common/drs/interface/"]),
MediaType.TERRAIN: (False, ["resources/_common/terrain/"])},
["aoe2-ak", "aoe2-ak-graphics"])


Expand All @@ -44,7 +44,7 @@ class GameEdition(Enum):
The Conquerors are considered "downgrade" expansions.
"""

AOC = GameEditionInfo("Age of Empires 2: The Conqueror's (Patch 1.0c)",
AOC = GameEditionInfo("Age of Empires 2: The Conqueror's",
Support.yes,
{('age2_x1/age2_x1.exe', {}),
('data/empires2_x1_p1.dat', {})},
Expand Down
53 changes: 46 additions & 7 deletions openage/convert/export/media_export_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,16 @@
Specifies a request for a media resource that should be
converted and exported into a modpack.
"""
from openage.convert.dataformat.media_types import MediaType
from openage.convert.slp import SLP
from openage.util.fslike.path import Path
from openage.convert.colortable import ColorTable
from openage.convert.texture import Texture


class MediaExportRequest:

def __init__(self, media_type, targetdir, source_filename, target_filename):
def __init__(self, targetdir, source_filename, target_filename):
"""
Create a request for a media file.
Expand All @@ -20,8 +25,6 @@ def __init__(self, media_type, targetdir, source_filename, target_filename):
:type target_filename: str
"""

self.media_type = media_type

self.set_targetdir(targetdir)
self.set_source_filename(source_filename)
self.set_target_filename(target_filename)
Expand All @@ -30,19 +33,21 @@ def get_type(self):
"""
Return the media type.
"""
return self.media_type
raise NotImplementedError("%s has not implemented get_type()"
% (self))

def export(self, sourcedir, exportdir):
def save(self, sourcedir, exportdir):
"""
Convert the media to openage target format and output the result
to a file.
to a file. Encountered metadata is returned on completion.
:param sourcedir: Relative path to the source directory.
:type sourcedir: ...util.fslike.path.Path
:param exportdir: Relative path to the export directory.
:type exportdir: ...util.fslike.path.Path
"""
# TODO: Depends on media type and source file type
raise NotImplementedError("%s has not implemented save()"
% (self))

def set_source_filename(self, filename):
"""
Expand Down Expand Up @@ -82,3 +87,37 @@ def set_targetdir(self, targetdir):
type(targetdir))

self.targetdir = targetdir


class GraphicsMediaExportRequest(MediaExportRequest):
"""
Export requests for ingame graphics such as animations or sprites.
"""

def get_type(self):
return MediaType.GRAPHICS

def save(self, sourcedir, exportdir, palette_file):
sourcefile = sourcedir.joinpath(self.source_filename)

media_file = sourcefile.open("rb")
palette_file = palette_file.open("rb")
palette_table = ColorTable(palette_file.read())

if sourcefile.suffix().lower() == "slp":
from ..slp import SLP

image = SLP(media_file.read())

elif sourcefile.suffix().lower() == "smp":
from ..smp import SMP

image = SMP(media_file.read())

elif sourcefile.suffix().lower() == "smx":
from ..smx import SMX

image = SMX(media_file.read())

texture = Texture(image, palette_table)
texture.save(self.targetdir, self.target_filename)
95 changes: 18 additions & 77 deletions openage/convert/processor/aoc/media_subprocessor.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,97 +6,38 @@
"""
from openage.convert.export.media_export_request import MediaExportRequest
from openage.convert.dataformat.media_types import MediaType
from openage.convert.export.formats.sprite_metadata import SpriteMetadata,\
LayerMode


class AoCMediaSubprocessor:

@classmethod
def convert(cls, full_data_set):

cls._create_sprites(full_data_set)
cls._create_graphics_requests(full_data_set)

@classmethod
def _create_sprites(cls, full_data_set):
@staticmethod
def _create_graphics_requests(full_data_set):
"""
Create sprite metadata and PNG files from CombinedSprite objects.
Create export requests for graphics referenced by ComibinedSprite objects.
"""
combined_sprites = full_data_set.combined_sprites.values()
handled_graphic_ids = set()

for sprite in combined_sprites:
cls._create_sprite_media_request(sprite, full_data_set)
cls._create_sprite_metadata(sprite, full_data_set)

@staticmethod
def _create_sprite_media_request(combined_sprite, full_data_set):
"""
Create a request for the exporter to convert and output a graphics file.
"""
graphic_id = combined_sprite.get_id()
graphic = full_data_set.genie_graphics[graphic_id]

# Export request for head graphic
targetdir = combined_sprite.resolve_location()
source_filename = graphic.get_member("filename").get_value()
target_filename = combined_sprite.get_filename()

export_request = MediaExportRequest(MediaType.GRAPHICS, targetdir,
source_filename, target_filename)
full_data_set.graphics_exports.update({graphic_id: export_request})

# TODO: Deltas

@staticmethod
def _create_sprite_metadata(combined_sprite, full_data_set):
"""
Metadata extraction. Not everything can be figured out here.
Some of the info has to be fetched from the SLP/SMX files.
"""
graphic_id = combined_sprite.get_id()
graphic = full_data_set.genie_graphics[graphic_id]

targetdir = combined_sprite.resolve_location()
target_filename = combined_sprite.get_filename()
img_index = 0

# Image
sprite_metadata = SpriteMetadata(targetdir, target_filename)
sprite_metadata.add_image(img_index, target_filename)

# Layer
mode = graphic.get_member("sequence_type").get_value()

layer_mode = LayerMode.OFF
if mode & 0x01:
layer_mode = LayerMode.LOOP
if mode & 0x08:
layer_mode = LayerMode.ONCE

layer_pos = graphic.get_member("layer").get_value()
time_per_frame = graphic.get_member("frame_rate").get_value()
replay_delay = graphic.get_member("replay_delay").get_value()

sprite_metadata.add_layer(img_index, layer_mode, layer_pos,
time_per_frame, replay_delay)

# Angles
if mode & 0x02 and not mode & 0x04:
angle_count = graphic.get_member("angle_count").get_value()
angle_step = 360 / angle_count
angle = 0

while angle <= 180:
sprite_metadata.add_angle(angle)
angle += angle_step
ref_graphics = sprite.get_graphics()
graphic_targetdirs = sprite.resolve_graphics_location()

while angle < 360:
sprite_metadata.add_angle(angle, mirror_from=(angle - 180))
angle += angle_step
for graphic in ref_graphics:
graphic_id = graphic.get_id()
if graphic_id in handled_graphic_ids:
continue

else:
sprite_metadata.add_angle(45)
targetdir = graphic_targetdirs[graphic_id]
source_filename = graphic.get_member("filename").get_value()
target_filename = graphic.get_member("filename").get_value()

combined_sprite.add_metadata(sprite_metadata)
export_request = MediaExportRequest(MediaType.GRAPHICS, targetdir,
source_filename, target_filename)
full_data_set.graphics_exports.update({graphic_id: export_request})

# TODO: Deltas
handled_graphic_ids.add(graphic_id)
9 changes: 9 additions & 0 deletions openage/convert/processor/aoc/modpack_subprocessor.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ def _get_aoe2_base(cls, gamedata):
mod_def.add_assets_to_load("data/*")

cls._organize_nyan_objects(modpack, gamedata)
cls._organize_media_objects(modpack, gamedata)

return modpack

Expand Down Expand Up @@ -80,3 +81,11 @@ def _organize_nyan_objects(modpack, full_data_set):
modpack.add_data_export(nyan_file)

nyan_file.add_nyan_object(raw_api_object.get_nyan_object())

@staticmethod
def _organize_media_objects(modpack, full_data_set):
"""
Put export requests and sprite files into as given modpack.
"""
for graphic_export in full_data_set.graphics_exports.values():
modpack.add_media_export(graphic_export)
9 changes: 9 additions & 0 deletions openage/convert/processor/aoc/processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -327,12 +327,21 @@ def _extract_genie_graphics(gamespec, full_data_set):
raw_graphics = gamespec.get_value()[0].get_value()["graphics"].get_value()

for raw_graphic in raw_graphics:
# Can be ignored if there is no filename associated
filename = raw_graphic.get_value()["filename"].get_value()
if not filename:
continue

graphic_id = raw_graphic.get_value()["graphic_id"].get_value()
graphic_members = raw_graphic.get_value()

graphic = GenieGraphic(graphic_id, full_data_set, members=graphic_members)
full_data_set.genie_graphics.update({graphic.get_id(): graphic})

# Detect subgraphics
for genie_graphic in full_data_set.genie_graphics.values():
genie_graphic.detect_subgraphics()

@staticmethod
def _extract_genie_sounds(gamespec, full_data_set):
"""
Expand Down
7 changes: 6 additions & 1 deletion openage/convert/processor/modpack_exporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,9 @@ def export(modpack, exportdir):
# Media files
media_files = modpack.get_media_files()

# TODO: Media file export
for media_type in media_files.keys():
cur_export_requests = media_files[media_type]

# TODO: Source directory derived from game edition
for request in cur_export_requests:
request.save(None, exportdir)

0 comments on commit f9f32c1

Please sign in to comment.