class DataTableRow_NamedBits(DataTableRow): """ A generic table row representing a named bit in a bitmask. """ __abstract__ = True _KEY_NAME = "bit_value" _KEY_FORMAT = "d" bit_value = _SQLA_Column(_SQLA_Integer, primary_key=True) bit_name = _SQLA_Column(_SQLA_String) @classmethod def from_dict(cls, attributes_dict): """ Creates an instance from an attributes dictionary. """ return cls(bit_value=cls.key_from_dict(attributes_dict), bit_name=attributes_dict["bit_name"]) @classmethod def key_from_dict(cls, attributes_dict): """ Returns value of primary key from an attributes dictionary. """ return int(attributes_dict[cls._KEY_NAME]) def _pformat_object(self, tables, pformat_config=_PrettyFormatConfig()): """ Nicely formats the object data for display. """ return self.bit_name
class ArmorProtection(_Armor_ForeignKey): """ A protection value for a zone of an armor. """ __tablename__ = "protections_by_armor" _KEY_NAME = "zone_number" _KEY_FORMAT = "d" zone_number = _SQLA_Column( _SQLA_Integer, _SQLA_ForeignKey(ArmorProtectionZone.TABLE_NAME() + "." + ArmorProtectionZone.KEY_NAME()), primary_key=True) protection = _SQLA_Column(_SQLA_Integer) def pformat_object(self, tables, pformat_config=_PrettyFormatConfig()): """ Nicely formats the object for display. """ pformat_config_zone = pformat_config.clone(render_title=False) template = "{zone_name}: {protection}" args = { "zone_name": tables[ ArmorProtectionZones_DataTable.LABEL( ) ]\ .pformat_table_lookup( self.zone_number, tables, pformat_config = pformat_config_zone ), "protection": self.protection } return template.format(**args)
class DataTableRow_NamedInteger(DataTableRow): """ A generic table row, naming an integer value. """ __abstract__ = True _KEY_NAME = "number" _KEY_FORMAT = "d" number = _SQLA_Column(_SQLA_Integer, primary_key=True) name = _SQLA_Column(_SQLA_String) @classmethod def from_dict(cls, attributes_dict): """ Creates an instance from an attributes dictionary. """ return cls(number=cls.key_from_dict(attributes_dict), name=attributes_dict["name"]) @classmethod def key_from_dict(cls, attributes_dict): """ Returns value of primary key from an attributes dictionary. """ return int(attributes_dict[cls._KEY_NAME]) def _pformat_object(self, tables, pformat_config=_PrettyFormatConfig()): """ Nicely formats the object data for display. """ return self.name
def _generated_SQLA_Table_columns( cls ): """ Returns additional SQLAlchemy columns for a table mapping. """ return [ _SQLA_Column( "damage_base", _SQLA_Integer ), _SQLA_Column( "damage_per_level", _SQLA_Integer ), _SQLA_Column( "totally_damage", _SQLA_Boolean ) ]
def _generated_SQLA_Table_columns( cls ): """ Returns additional SQLAlchemy columns for a table mapping. """ return [ # TODO: Add foreign key on monsters table. _SQLA_Column( "monster_number", _SQLA_Integer ), _SQLA_Column( "monster_group_tag", _SQLA_Integer, _SQLA_ForeignKey( MonsterTag.TABLE_NAME( ) + "." + MonsterTag.KEY_NAME( ) ) ) ]
def generated_SQLA_Table( cls, argument_table_name ): """ Returns a SQLAlchemy table to be mapped to an object. """ return _SQLA_Table( argument_table_name + "_by_effect", cls.metadata, _SQLA_Column( "record_id", _SQLA_Integer, primary_key = True ), _SQLA_Column( "effect_record_id", _SQLA_Integer, _SQLA_ForeignKey( Effect.TABLE_NAME( ) + "." + Effect.KEY_NAME( ) ) ), *cls._generated_SQLA_Table_columns( ), keep_existing = True )
def _generated_SQLA_Table_columns( cls ): """ Returns additional SQLAlchemy columns for a table mapping. """ # TODO? Break out base value and per-level value. return [ _SQLA_Column( "healing", _SQLA_Integer ) ]
def _generated_SQLA_Table_columns( cls ): """ Returns additional SQLAlchemy columns for a table mapping. """ # TODO: Add foreign key against monster table. return [ _SQLA_Column( "monster_number", _SQLA_Integer ) ]
def nation_number( cls ): return _SQLA_Column( _SQLA_Integer, _SQLA_ForeignKey( Nation.TABLE_NAME( ) + "." + Nation.KEY_NAME( ) ), primary_key = True )
def effect_record_id( cls ): return _SQLA_Column( _SQLA_Integer, _SQLA_ForeignKey( Effect.TABLE_NAME( ) + "." + Effect.KEY_NAME( ) ), primary_key = True )
def attribute_record_id( cls ): return _SQLA_Column( _SQLA_Integer, _SQLA_ForeignKey( Attribute.TABLE_NAME( ) + "." + Attribute.KEY_NAME( ) ), primary_key = True )
def spell_number( cls ): return _SQLA_Column( _SQLA_Integer, _SQLA_ForeignKey( Spell.TABLE_NAME( ) + "." + Spell.KEY_NAME( ) ), primary_key = True )
def generated_SQLA_Table( cls, value_table_name ): """ Returns a SQLAlchemy table to be mapped to an object. """ return _SQLA_Table( value_table_name + "_by_attribute", cls.metadata, _SQLA_Column( "record_id", _SQLA_Integer, primary_key = True ), _SQLA_Column( "attribute_record_id", _SQLA_Integer, _SQLA_ForeignKey( Attribute.TABLE_NAME( ) + "." + Attribute.KEY_NAME( ) ) ), _SQLA_Column( "attribute_number", _SQLA_Integer, _SQLA_ForeignKey( AttributeKey.TABLE_NAME( ) + "." + AttributeKey.KEY_NAME( ) ) ), *cls._generated_SQLA_Table_columns( ), keep_existing = True )
class DataTableRow_UnknownField(DataTableRow): """ A generic table row representing an unknown field. """ __abstract__ = True @_SQLA_declared_attr def offset(cls): return _SQLA_Column(_SQLA_Integer, primary_key=True) value = _SQLA_Column(_SQLA_Integer) _KEY_NAME = "offset" def pformat_object(self, tables, pformat_config=_PrettyFormatConfig()): """ Nicely formats the object for display. """ template = "<Unknown> [Offset: {offset}]: {value}" args = {"offset": self.offset, "value": self.value} template = pformat_config.indent + template return template.format(**args)
def monster_number(cls): return _SQLA_Column(_SQLA_Integer, primary_key=True)
def _generated_SQLA_Table_columns( cls ): """ Returns additional SQLAlchemy columns for a table mapping. """ return [ _SQLA_Column( "value", _SQLA_Integer ) ]
class Attribute( _DataTableRow ): """ An attribute of a Dominions object. """ __tablename__ = "attributes" record_id = _SQLA_Column( _SQLA_Integer, primary_key = True, autoincrement = "ignore_fk" ) attribute_number = _SQLA_Column( _SQLA_Integer, _SQLA_ForeignKey( AttributeKey.TABLE_NAME( ) + "." + AttributeKey.KEY_NAME( ) ) ) object_type = _SQLA_Column( _SQLA_String ) raw_value = _SQLA_Column( _SQLA_Integer ) _KEY_NAME = "record_id" @classmethod def __declare_last__( cls ): # Perform late binding against abstract concrete bases. cls.value = _SQLA_relationship( AttributeValue, uselist = False ) @classmethod def from_raw_data( cls, attribute_number, object_type, raw_value ): """ Creates an instance from a set of raw arguments. """ args = { "attribute_number": attribute_number, "object_type": object_type, "raw_value": raw_value, } self = cls( **args ) self.value = eval( "Attribute{attribute_number}Value.from_raw_value".format( attribute_number = args[ "attribute_number" ] ) )( attribute_record_id = self.record_id, attribute_number = attribute_number, raw_value = raw_value ) return self def pformat_object( self, tables, pformat_config = _PrettyFormatConfig( ) ): """ Nicely formats the object for display. """ return self.value.pformat_object( tables, pformat_config = pformat_config )
class Nation(_DataTableRow_NamedInteger, _DataTableRow_ProgramImage): """ A nation. """ __tablename__ = "nations" epithet = _SQLA_Column(_SQLA_String) abbreviation = _SQLA_Column(_SQLA_String) file_name_base = _SQLA_Column(_SQLA_String) # TODO: Add foreign key against monsters table. initial_scout = _SQLA_Column(_SQLA_Integer) # TODO: Add foreign key against monsters table. initial_leader = _SQLA_Column(_SQLA_Integer) # TODO: Add foreign key against monsters table. initial_troops_type_1 = _SQLA_Column(_SQLA_Integer) initial_troops_count_1 = _SQLA_Column(_SQLA_Integer) # TODO: Add foreign key against monsters table. initial_troops_type_2 = _SQLA_Column(_SQLA_Integer) initial_troops_count_2 = _SQLA_Column(_SQLA_Integer) pretender_types = _SQLA_relationship("NationPretenderType") unpretender_types = _SQLA_relationship("NationUnpretenderType") fort_leader_types = _SQLA_relationship("NationFortLeaderType") fort_troop_types = _SQLA_relationship("NationFortTroopType") nonfort_leader_types = _SQLA_relationship("NationNonfortLeaderType") nonfort_troop_types = _SQLA_relationship("NationNonfortTroopType") coast_troop_types = _SQLA_relationship("NationCoastTroopType") coast_leader_types = _SQLA_relationship("NationCoastLeaderType") attributes = _SQLA_relationship("_NationAttribute") unknown_fields = _SQLA_relationship("NationUnknownField") _TITLE = "Nation" _PROGRAM_IMAGE_RECORD_SIZES = { "4.03": 1108, "4.04": 1108, "4.05": 1108, "4.05b": 1108, "4.07": 1108, "4.10": 1108, "4.14": 1108, "4.16": 1108, "4.17": 1108, "4.20": 1108, "4.21": 1108, "4.22": 1108, "4.23": 1108, "4.24": 1108, "4.25": 1108, "4.26": 1108, "4.27": 1108, "4.28": 1108, "4.29": 1108, "4.30": 1108 } @classmethod def from_program_image(cls, program_image, base_offset, number, dominions_version): """ Creates an instance from a program image. """ # TODO: Version these constants. NAME_LENGTH = 36 EPITHET_LENGTH = 36 GROUP_CODE_LENGTH = 5 FILE_NAME_BASE_LENGTH = 63 offset = base_offset args = {"number": number} unknowns = _OrderedDict() args["name"], __ = _from_string(program_image, offset, NAME_LENGTH) if "end" == args["name"]: raise StopIteration() offset += NAME_LENGTH args[ "epithet" ], __ \ = _from_string( program_image, offset, EPITHET_LENGTH ) offset += EPITHET_LENGTH args[ "abbreviation" ], __ \ = _from_string( program_image, offset, GROUP_CODE_LENGTH ) offset += GROUP_CODE_LENGTH args[ "file_name_base" ], __ \ = _from_string( program_image, offset, FILE_NAME_BASE_LENGTH ) offset += FILE_NAME_BASE_LENGTH # TEMP HACK: For decoding. for i in range(16): unknowns[ offset - base_offset ], offset \ = _from_native_uint16( program_image, offset ) attribute_keys = [] for i in range(64): attribute_key, offset \ = _from_native_uint32( program_image, offset ) attribute_keys.append(attribute_key) attribute_values = [] for i in range(64): attribute_value, offset \ = _from_native_int32( program_image, offset ) attribute_values.append(attribute_value) # unknowns[ offset - base_offset ], offset \ # = _from_native_uint32( program_image, offset ) # 90 troop type slots at end args["fort_troop_types"] = [] for slot_idx in range(90): monster_number, offset \ = _from_native_int32( program_image, offset ) if 0 >= monster_number: break troop_type = NationFortTroopType(nation_number=number, monster_number=monster_number) args["fort_troop_types"].append(troop_type) for slot_idx in range(slot_idx, 89): if -2 == monster_number: args["fort_leader_types"] = [] found = 0 for slot_idx in range(slot_idx, 89): monster_number, offset \ = _from_native_int32( program_image, offset ) if 0 >= monster_number: break troop_type = NationFortLeaderType( nation_number=number, monster_number=monster_number) if found == 1: if monster_number == 2751: continue args["fort_leader_types"].append(troop_type) if number == 81: if monster_number == 2751: found = 1 if -5 == monster_number: args["coast_troop_types"] = [] for slot_idx in range(slot_idx, 89): monster_number, offset \ = _from_native_int32( program_image, offset ) if 0 >= monster_number: break troop_type = NationCoastTroopType( nation_number=number, monster_number=monster_number) args["coast_troop_types"].append(troop_type) if -6 == monster_number: args["coast_leader_types"] = [] for slot_idx in range(slot_idx, 89): monster_number, offset \ = _from_native_int32( program_image, offset ) if 0 >= monster_number: break troop_type = NationCoastLeaderType( nation_number=number, monster_number=monster_number) args["coast_leader_types"].append(troop_type) if -3 == monster_number: args["nonfort_troop_types"] = [] for slot_idx in range(slot_idx, 89): monster_number, offset \ = _from_native_int32( program_image, offset ) if 0 >= monster_number: break troop_type = NationNonfortTroopType( nation_number=number, monster_number=monster_number) args["nonfort_troop_types"].append(troop_type) if -4 == monster_number: args["nonfort_leader_types"] = [] for slot_idx in range(slot_idx, 89): monster_number, offset \ = _from_native_int32( program_image, offset ) if 0 >= monster_number: break troop_type = NationNonfortLeaderType( nation_number=number, monster_number=monster_number) args["nonfort_leader_types"].append(troop_type) if -1 == monster_number: args["pretender_types"] = [] args["unpretender_types"] = [] monster_numbers = set() for slot_idx in range(slot_idx, 89): monster_number, offset \ = _from_native_int32( program_image, offset ) if monster_number in monster_numbers: continue else: monster_numbers.add(monster_number) if -1 == monster_number: break if 0 == monster_number: continue # Skip 134, not a pretender if 134 == monster_number: continue if 0 < monster_number: troop_type = NationPretenderType( nation_number=number, monster_number=monster_number) args["pretender_types"].append(troop_type) else: troop_type = NationUnpretenderType( nation_number=number, monster_number=-monster_number) args["unpretender_types"].append(troop_type) # Note: Should not have any non-zero values. for slot_idx in range(slot_idx, 89): unknowns[ offset - base_offset ], offset \ = _from_native_int32( program_image, offset ) attributes = [] for key, value in zip(attribute_keys, attribute_values): if not key: continue attributes.append( _NationAttribute.from_raw_data(nation_number=number, attribute_number=key, raw_value=value)) args["attributes"] = attributes args["unknown_fields"] = [ NationUnknownField(nation_number=number, offset=offset, value=value) for offset, value in unknowns.items() if value ] return cls(**args) def pformat_row(self, tables, pformat_config=_PrettyFormatConfig()): """ Nicely formats the table row for display. """ output = [] pformat_config_no_key_padding = pformat_config.clone( key_format=self._KEY_FORMAT) pformat_config_1 = pformat_config.clone(indent=pformat_config.indent + 4 * " ") pformat_config_2 = pformat_config_1.clone( indent=pformat_config_1.indent + 4 * " ") indent = pformat_config.indent + 4 * " " output.append( (pformat_config.indent + "{title} #{number}: {name}").format( title=self._TITLE, number=self.pformat_key( tables, pformat_config=pformat_config_no_key_padding), name=self.name)) if pformat_config.render_compactly: return output[0] if self.epithet: output.append(indent + "Epithet: {epithet}".format(epithet=self.epithet)) if self.abbreviation: output.append(indent + "Abbreviation: {abbreviation}".format( abbreviation=self.abbreviation)) if self.file_name_base: output.append(indent + "File Name Base: {file_name_base}".format( file_name_base=self.file_name_base)) if self.initial_scout: # TODO: Fill out via table lookup. output.append(indent + "Initial Scout {{#startscout}}: " "{initial_scout}".format( initial_scout=self.initial_scout)) if self.initial_leader: # TODO: Fill out via table lookup. output.append(indent + "Initial Leader {{#startcom}}: " "{initial_leader}".format( initial_leader=self.initial_leader)) # TODO: Fill out via table lookup. output.append( indent + "Initial Troops (Type I) {{#startunittype1}}: " "{initial_troops_type} " "(Count {{#startunitnbs1}}: {initial_troops_count})".format( initial_troops_type=self.initial_troops_type_1, initial_troops_count=self.initial_troops_count_1)) # TODO: Fill out via table lookup. output.append( indent + "Initial Troops (Type II) {{#startunittype2}}: " "{initial_troops_type} " "(Count {{#startunitnbs2}}: {initial_troops_count})".format( initial_troops_type=self.initial_troops_type_2, initial_troops_count=self.initial_troops_count_2)) indent_1 = indent + 4 * " " if self.unpretender_types: output.append(indent + "Excluded Pretenders {#delgod}") for troop_type in self.unpretender_types: # TODO: Fill out via table lookup. output.append(indent_1 + str(troop_type.monster_number)) if self.pretender_types: output.append(indent + "Pretenders {#addgod}") for troop_type in self.pretender_types: # TODO: Fill out via table lookup. output.append(indent_1 + str(troop_type.monster_number)) if self.fort_leader_types: output.append(indent + "Recruitable Leaders (Fortification) {#addreccom}") for troop_type in self.fort_leader_types: # TODO: Fill out via table lookup. output.append(indent_1 + str(troop_type.monster_number)) if self.fort_troop_types: output.append(indent + "Recruitable Troops (Fortification) {#addrecunit}") for troop_type in self.fort_troop_types: # TODO: Fill out via table lookup. output.append(indent_1 + str(troop_type.monster_number)) if self.nonfort_leader_types: output.append(indent + "Recruitable Leaders (Foreign) {#addforeigncom}") for troop_type in self.nonfort_leader_types: # TODO: Fill out via table lookup. output.append(indent_1 + str(troop_type.monster_number)) if self.nonfort_troop_types: output.append(indent + "Recruitable Troops (Foreign) {#addforeignunit}") for troop_type in self.nonfort_troop_types: # TODO: Fill out via table lookup. output.append(indent_1 + str(troop_type.monster_number)) if self.coast_troop_types: output.append(indent + "Coast Troops {#coastunit}") for troop_type in self.coast_troop_types: # TODO: Fill out via table lookup. output.append(indent_1 + str(troop_type.monster_number)) if self.coast_leader_types: output.append(indent + "Coast Leaders {#coastcom}") for troop_type in self.coast_leader_types: # TODO: Fill out via table lookup. output.append(indent_1 + str(troop_type.monster_number)) if self.attributes: for attribute in self.attributes: output.append( attribute.pformat_object(tables, pformat_config=pformat_config_1)) if not pformat_config.suppress_unknowns and self.unknown_fields: output.append(indent + "Unknowns") for unknown_field in self.unknown_fields: output.append( unknown_field.pformat_object( tables, pformat_config=pformat_config_2)) return "\n".join(output) + "\n"
def armor_number( cls ): return _SQLA_Column( _SQLA_Integer, _SQLA_ForeignKey( Armor.TABLE_NAME( ) + "." + Armor.KEY_NAME( ) ), primary_key = True )
def offset( cls ): return _SQLA_Column( _SQLA_Integer, primary_key = True )
def weapon_number(cls): return _SQLA_Column(_SQLA_Integer, _SQLA_ForeignKey(Weapon.TABLE_NAME() + "." + Weapon.KEY_NAME()), primary_key=True)
def monster_number( cls ): return _SQLA_Column( _SQLA_Integer, primary_key = True )
class Armor(_DataTableRow_NamedInteger, _DataTableRow_ProgramImage): """ An armor. """ __tablename__ = "armors" _TITLE = "Armor" _PROGRAM_IMAGE_RECORD_SIZES = { "4.03": 96, "4.04": 96, "4.05": 96, "4.05b": 96, "4.07": 96, "4.10": 96, "4.14": 96, "4.16": 96, "4.17": 96, "4.20": 96, "4.21": 96, "4.22": 96, "4.23": 96, "4.24": 96, "4.25": 96, "4.26": 96, "4.27": 96, "4.28": 96, "4.29": 96, "4.30": 96 } armor_type = _SQLA_Column( _SQLA_Integer, _SQLA_ForeignKey(ArmorType.TABLE_NAME() + "." + ArmorType.KEY_NAME())) # TODO: Add field for calculated protection value. defense = _SQLA_Column(_SQLA_Integer) encumbrance = _SQLA_Column(_SQLA_Integer) resource_cost = _SQLA_Column(_SQLA_Integer) protections = _SQLA_relationship("ArmorProtection") attributes = _SQLA_relationship("_ArmorAttribute") unknown_fields = _SQLA_relationship("ArmorUnknownField") @classmethod def from_program_image(cls, program_image, base_offset, number, dominions_version): """ Creates an instance from a program image. """ # TODO: Version this constant. NAME_LENGTH = 36 offset = base_offset unknowns = _OrderedDict() name, __ = _from_string(program_image, offset, NAME_LENGTH) if "end" == name: raise StopIteration() offset += NAME_LENGTH protections = [] for i in range(6): protection_zone, offset \ = _from_native_uint16( program_image, offset ) protection_amount, offset = _from_native_uint16( program_image, offset) if protection_zone: protections.append( ArmorProtection(armor_number=number, zone_number=protection_zone, protection=protection_amount)) unknowns[ offset ], offset \ = _from_native_uint16( program_image, offset ) defense, offset = _from_native_int16(program_image, offset) encumbrance, offset = _from_native_uint16(program_image, offset) armor_type, offset = _from_native_uint16(program_image, offset) resource_cost, offset = _from_native_uint16(program_image, offset) unknowns[ offset ], offset \ = _from_native_uint16( program_image, offset ) attribute_keys = [] for i in range(3): attribute_key, offset \ = _from_native_uint32( program_image, offset ) attribute_keys.append(attribute_key) attribute_values = [] for i in range(3): attribute_value, offset \ = _from_native_uint32( program_image, offset ) attribute_values.append(attribute_value) attributes = [] for key, value in zip(attribute_keys, attribute_values): if not key: continue attributes.append( _ArmorAttribute.from_raw_data(armor_number=number, attribute_number=key, raw_value=value)) unknown_fields = [ ArmorUnknownField(armor_number=number, offset=offset, value=value) for offset, value in unknowns.items() if value ] return cls(number=number, name=name, armor_type=armor_type, protections=protections, defense=defense, encumbrance=encumbrance, resource_cost=resource_cost, attributes=attributes, unknown_fields=unknown_fields) def pformat_row(self, tables, pformat_config=_PrettyFormatConfig()): """ Nicely formats the table row for display. """ output = [] pformat_config_no_key_padding = pformat_config.clone( key_format=self._KEY_FORMAT) pformat_config_1 = pformat_config.clone(indent=pformat_config.indent + 4 * " ") pformat_config_2 = pformat_config_1.clone( indent=pformat_config_1.indent + 4 * " ") indent = pformat_config.indent + 4 * " " output.append( (pformat_config.indent + "{title} #{number}: {name}").format( title=self._TITLE, number=self.pformat_key( tables, pformat_config=pformat_config_no_key_padding), name=self.name)) if pformat_config.render_compactly: return output[0] output.append( tables[ArmorTypes_DataTable.LABEL()].pformat_table_lookup( self.armor_type, tables, pformat_config=pformat_config_1)) # TODO: Output actual protection. if self.protections: output.append(indent + "Protection by Zone") for protection in self.protections: output.append( protection.pformat_object(tables, pformat_config=pformat_config_2)) output.append(indent + "Defense {{Arm: #def}}: {defense}".format( defense=self.defense)) output.append(indent + "Encumbrance {{Arm: #enc}}: {encumbrance}".format( encumbrance=self.encumbrance)) output.append(indent + "Resource Cost {{Arm: #rcost}}: {resource_cost}".format( resource_cost=self.resource_cost)) if self.attributes: for attribute in self.attributes: output.append( attribute.pformat_object(tables, pformat_config=pformat_config_1)) if not pformat_config.suppress_unknowns and self.unknown_fields: output.append(indent + "Unknowns") for unknown_field in self.unknown_fields: output.append( unknown_field.pformat_object( tables, pformat_config=pformat_config_2)) return "\n".join(output) + "\n"
class Weapon(_DataTableRow_NamedInteger, _DataTableRow_ProgramImage): """ A weapon. """ __tablename__ = "weapons" effect_record_id = _SQLA_Column( _SQLA_Integer, _SQLA_ForeignKey(_Effect.TABLE_NAME() + "." + _Effect.KEY_NAME())) effect = _SQLA_relationship( _Effect, uselist=False, primaryjoin=effect_record_id == _Effect.record_id) attack = _SQLA_Column(_SQLA_Integer) defense = _SQLA_Column(_SQLA_Integer) length = _SQLA_Column(_SQLA_Integer) attack_rate = _SQLA_Column(_SQLA_Integer) attacks_total = _SQLA_Column(_SQLA_Integer) secondary_effect_on_hit = _SQLA_Column( _SQLA_Integer, _SQLA_ForeignKey(__tablename__ + "." + _DataTableRow_NamedInteger.KEY_NAME())) secondary_effect_always = _SQLA_Column( _SQLA_Integer, _SQLA_ForeignKey(__tablename__ + "." + _DataTableRow_NamedInteger.KEY_NAME())) resource_cost = _SQLA_Column(_SQLA_Integer) attributes = _SQLA_relationship("_WeaponAttribute") unknown_fields = _SQLA_relationship("WeaponUnknownField") _TITLE = "Weapon" _PROGRAM_IMAGE_RECORD_SIZES = { "4.03": 112, "4.04": 112, "4.05": 112, "4.05b": 112, "4.07": 112, "4.10": 112, "4.14": 112, "4.16": 112, "4.17": 112, "4.20": 112, "4.21": 112, "4.22": 112, "4.23": 112, "4.24": 112, "4.25": 112, "4.26": 112, "4.27": 112, "4.28": 112, "4.29": 112, "4.30": 112 } @classmethod def from_program_image(cls, program_image, base_offset, number, dominions_version): """ Creates an instance from a program image. """ # TODO: Version this constant. NAME_LENGTH = 36 offset = base_offset unknowns = _OrderedDict() name, __ = _from_string(program_image, offset, NAME_LENGTH) if "end" == name: raise StopIteration() offset += NAME_LENGTH unknowns[ offset ], offset \ = _from_native_uint32( program_image, offset ) effect_argument, offset \ = _from_native_int64( program_image, offset ) attack, offset = _from_native_int16(program_image, offset) defense, offset = _from_native_int16(program_image, offset) effect_number, offset = _from_native_uint16(program_image, offset) length, offset = _from_native_uint16(program_image, offset) range_of_effect, offset = _from_native_int16(program_image, offset) attack_rate, offset = _from_native_int16(program_image, offset) attacks_total, offset = _from_native_uint16(program_image, offset) unknowns[ offset ], offset \ = _from_native_uint16( program_image, offset ) effect_modifiers, offset = _from_native_int64(program_image, offset) secondary_effect, offset = _from_native_int16(program_image, offset) if 0 > secondary_effect: secondary_effect_always = -secondary_effect secondary_effect_on_hit = 0 else: secondary_effect_always = 0 secondary_effect_on_hit = secondary_effect flight_sprite_number, offset \ = _from_native_int16( program_image, offset ) flight_sprite_length, offset \ = _from_native_uint16( program_image, offset ) explosion_sprite_number, offset \ = _from_native_int16( program_image, offset ) explosion_sprite_length, offset \ = _from_native_uint16( program_image, offset ) area_of_effect, offset = _from_native_uint16(program_image, offset) sound_number, offset = _from_native_uint16(program_image, offset) resource_cost, offset = _from_native_uint16(program_image, offset) attribute_keys = [] for i in range(3): attribute_key, offset \ = _from_native_uint32( program_image, offset ) attribute_keys.append(attribute_key) attribute_values = [] for i in range(3): attribute_value, offset \ = _from_native_uint32( program_image, offset ) attribute_values.append(attribute_value) attributes = [] for key, value in zip(attribute_keys, attribute_values): if not key: continue attributes.append( _WeaponAttribute.from_raw_data(weapon_number=number, attribute_number=key, raw_value=value)) unknown_fields = [ WeaponUnknownField(weapon_number=number, offset=offset, value=value) for offset, value in unknowns.items() if value ] effect = _Effect.from_raw_data( effect_number=effect_number, object_type=cls.TITLE(), raw_argument=effect_argument, modifiers_mask=effect_modifiers, raw_range=range_of_effect, raw_area=area_of_effect, sound_number=sound_number, flight_sprite_number=flight_sprite_number, flight_sprite_length=flight_sprite_length, explosion_sprite_number=explosion_sprite_number, explosion_sprite_length=explosion_sprite_length) return cls(number=number, name=name, effect_record_id=effect.record_id, effect=effect, attack=attack, defense=defense, attack_rate=attack_rate, attacks_total=attacks_total, length=length, secondary_effect_on_hit=secondary_effect_on_hit, secondary_effect_always=secondary_effect_always, resource_cost=resource_cost, attributes=attributes, unknown_fields=unknown_fields) def pformat_row(self, tables, pformat_config=_PrettyFormatConfig()): """ Nicely formats the table row for display. """ output = [] pformat_config_no_key_padding = pformat_config.clone( key_format=self._KEY_FORMAT) pformat_config_1 = pformat_config.clone(indent=pformat_config.indent + 4 * " ") pformat_config_2 = pformat_config_1.clone( indent=pformat_config_1.indent + 4 * " ") pformat_config_compact = pformat_config.clone(indent="", render_title=False, render_compactly=True) indent = pformat_config.indent + 4 * " " output.append( (pformat_config.indent + "{title} #{number}: {name}").format( title=self._TITLE, number=self.pformat_key( tables, pformat_config=pformat_config_no_key_padding), name=self.name)) if pformat_config.render_compactly: return output[0] output.append( self.effect.pformat_object(tables, pformat_config=pformat_config_1)) output.append(indent + "Attack {{Wpn: #att}}: {attack}".format( attack=self.attack)) output.append(indent + "Defense {{Wpn: #def}}: {defense}".format( defense=self.defense)) # TODO: Handle negative rates. output.append(indent + "Attack Rate {{Wpn: #nratt}}: {attack_rate}".format( attack_rate=self.attack_rate)) if self.attacks_total: output.append(indent + "Attacks per Battle {{Wpn: #ammo}}: " "{attacks_total}".format( attacks_total=self.attacks_total)) output.append(indent + "Length {{Wpn: #len}}: {length}".format( length=self.length)) if self.secondary_effect_on_hit: output.append( indent + "On-Hit Secondary Effect {Wpn: #secondaryeffect}: " + tables[Weapons_DataTable.LABEL()].pformat_table_lookup( self.secondary_effect_on_hit, tables, pformat_config=pformat_config_compact)) if self.secondary_effect_always: output.append( indent + "Always Secondary Effect " "{Wpn: #secondaryeffectalways}: " + tables[Weapons_DataTable.LABEL()].pformat_table_lookup( self.secondary_effect_always, tables, pformat_config=pformat_config_compact)) output.append(indent + "Resource Cost {{Wpn: #rcost}}: {resource_cost}".format( resource_cost=self.resource_cost)) if self.attributes: for attribute in self.attributes: output.append( attribute.pformat_object(tables, pformat_config=pformat_config_1)) if not pformat_config.suppress_unknowns and self.unknown_fields: output.append(indent + "Unknowns") for unknown_field in self.unknown_fields: output.append( unknown_field.pformat_object( tables, pformat_config=pformat_config_2)) return "\n".join(output) + "\n"
def armor_number(cls): return _SQLA_Column(_SQLA_Integer, _SQLA_ForeignKey(Armor.TABLE_NAME() + "." + Armor.KEY_NAME()), primary_key=True)
class Spell( _DataTableRow_NamedInteger, _DataTableRow_ProgramImage ): """ A spell. """ __tablename__ = "spells" school = _SQLA_Column( _SQLA_Integer, _SQLA_ForeignKey( MagicSchool.TABLE_NAME( ) + "." + MagicSchool.KEY_NAME( ) ) ) research_level = _SQLA_Column( _SQLA_Integer ) path_0 = _SQLA_Column( _SQLA_Integer, _SQLA_ForeignKey( MagicPath.TABLE_NAME( ) + "." + MagicPath.KEY_NAME( ) ) ) path_level_0 = _SQLA_Column( _SQLA_Integer ) path_1 = _SQLA_Column( _SQLA_Integer, _SQLA_ForeignKey( MagicPath.TABLE_NAME( ) + "." + MagicPath.KEY_NAME( ) ) ) path_level_1 = _SQLA_Column( _SQLA_Integer ) effect_record_id = _SQLA_Column( _SQLA_Integer, _SQLA_ForeignKey( _Effect.TABLE_NAME( ) + "." + _Effect.KEY_NAME( ) ) ) effect = _SQLA_relationship( _Effect, uselist = False, primaryjoin = effect_record_id == _Effect.record_id ) effects_count = _SQLA_Column( _SQLA_Integer ) precision = _SQLA_Column( _SQLA_Integer ) fatigue = _SQLA_Column( _SQLA_Integer ) gem_cost = _SQLA_Column( _SQLA_Integer ) next_spell = _SQLA_Column( _SQLA_Integer, _SQLA_ForeignKey( __tablename__ + "." + _DataTableRow_NamedInteger.KEY_NAME( ) ) ) description = _SQLA_Column( _SQLA_String ) attributes = _SQLA_relationship( "_SpellAttribute" ) unknown_fields = _SQLA_relationship( "SpellUnknownField" ) _TITLE = "Spell" _PROGRAM_IMAGE_RECORD_SIZES = { "4.03": 200, "4.04": 200, "4.05": 200, "4.05b": 200, "4.07": 200, "4.10": 200, "4.14": 200, "4.16": 200, "4.17": 200, "4.20": 200, "4.21": 200, "4.22": 200, "4.23": 200, "4.24": 200, "4.25": 200, "4.26": 200, "4.27": 200, "4.28": 200, "4.29": 200, "4.30": 200 } @classmethod def from_program_image( cls, program_image, base_offset, number, dominions_version ): """ Creates an instance from a program image. """ # TODO: Version this constant. NAME_LENGTH = 36 offset = base_offset args = { "number": number } effect_args = { } unknowns = _OrderedDict( ) args[ "name" ], __ = _from_string( program_image, offset, NAME_LENGTH ) if "end" == args[ "name" ]: raise StopIteration( ) offset += NAME_LENGTH args[ "school" ], offset = _from_native_int8( program_image, offset ) args[ "research_level" ], offset \ = _from_native_uint8( program_image, offset ) path_mask, offset = _from_native_int16( program_image, offset ) if 0 > path_mask: if -1 == path_mask: args[ "path_0" ] = -1 args[ "path_1" ] = -1 else: args[ "path_0" ] = 256 + path_mask args[ "path_1" ] = -1 else: args[ "path_0" ] = 0x00ff & path_mask args[ "path_1" ] = (0xff00 & path_mask) >> 8 path_level_mask, offset = _from_native_uint16( program_image, offset ) args[ "path_level_0" ] = 0x00ff & path_level_mask args[ "path_level_1" ] = (0xff00 & path_level_mask) >> 8 fatigue, offset = _from_native_uint16( program_image, offset ) args[ "fatigue" ] = fatigue % 100 args[ "gem_cost" ] = fatigue // 100 effect_args[ "raw_area" ], offset \ = _from_native_uint16( program_image, offset ) effect_args[ "effect_number" ], offset \ = _from_native_uint16( program_image, offset ) effect_args[ "raw_range" ], offset \ = _from_native_uint16( program_image, offset ) args[ "precision" ], offset \ = _from_native_int16( program_image, offset ) unknowns[ offset ], offset \ = _from_native_uint32( program_image, offset ) effect_args[ "raw_argument" ], offset \ = _from_native_int64( program_image, offset ) args[ "effects_count" ], offset \ = _from_native_uint16( program_image, offset ) effect_args[ "flight_sprite_number" ], offset \ = _from_native_int16( program_image, offset ) effect_args[ "flight_sprite_length" ], offset \ = _from_native_uint16( program_image, offset ) effect_args[ "explosion_sprite_number" ], offset \ = _from_native_int16( program_image, offset ) effect_args[ "explosion_sprite_length" ], offset \ = _from_native_uint16( program_image, offset ) unknowns[ offset ], offset \ = _from_native_uint32( program_image, offset ) unknowns[ offset ], offset \ = _from_native_uint16( program_image, offset ) effect_args[ "modifiers_mask" ], offset \ = _from_native_int64( program_image, offset ) args[ "next_spell" ], offset \ = _from_native_uint16( program_image, offset ) effect_args[ "sound_number" ], offset \ = _from_native_uint16( program_image, offset ) attribute_keys = [ ] for i in range( 13 ): attribute_key, offset \ = _from_native_uint32( program_image, offset ) attribute_keys.append( attribute_key ) attribute_values = [ ] for i in range( 13 ): attribute_value, offset \ = _from_native_uint32( program_image, offset ) attribute_values.append( attribute_value ) unknowns[ offset ], offset \ = _from_native_uint32( program_image, offset ) attributes = [ ] for key, value in zip( attribute_keys, attribute_values ): if not key: continue attributes.append( _SpellAttribute.from_raw_data( spell_number = number, attribute_number = key, raw_value = value ) ) args[ "attributes" ] = attributes args[ "unknown_fields" ] = [ SpellUnknownField( spell_number = number, offset = offset, value = value ) for offset, value in unknowns.items( ) if value ] effect_args[ "object_type" ] = cls.TITLE( ) args[ "effect" ] = _Effect.from_raw_data( **effect_args ) return cls( **args ) def pformat_row( self, tables, pformat_config = _PrettyFormatConfig( ) ): """ Nicely formats the table row for display. """ output = [ ] pformat_config_no_key_padding = pformat_config.clone( key_format = self._KEY_FORMAT ) pformat_config_1 = pformat_config.clone( indent = pformat_config.indent + 4 * " " ) pformat_config_2 = pformat_config_1.clone( indent = pformat_config_1.indent + 4 * " " ) pformat_config_compact = pformat_config.clone( indent = "", render_title = False, render_compactly = True ) pformat_config_compact_no_key = pformat_config.clone( indent = "", render_title = False, render_key_with_object = False, render_compactly = True ) indent = pformat_config.indent + 4 * " " output.append( (pformat_config.indent + "{title} #{number}: {name}").format( title = self._TITLE, number = self.pformat_key( tables, pformat_config = pformat_config_no_key_padding ), name = self.name ) ) if pformat_config.render_compactly: return output[ 0 ] template = indent + "Research Requirement {{Spl: #school}}" args = { "school": tables[ MagicSchools_DataTable.LABEL( ) ].pformat_table_lookup( self.school, tables, pformat_config = pformat_config_compact_no_key ) } if 0 > self.school: template += ": {school}" else: template += " {{Spl: #researchlevel}}: {school} {level}" args[ "level" ] = self.research_level output.append( template.format( **args ) ) for idx in range( 2 ): path = eval( "self.path_{idx}".format( idx = idx ) ) if 0 <= path: output.append( indent + "Magic Path #{idx_plus_1} " "{{Spl: #path {idx}}} {{Spl: #pathlevel {idx}}}: " "{path} {level}".format( idx = idx, idx_plus_1 = idx + 1, path = tables[ MagicPaths_DataTable.LABEL( ) ]\ .pformat_table_lookup( path, tables, pformat_config = pformat_config_compact_no_key ), level = eval( "self.path_level_{idx}".format( idx = idx ) ) ) ) output.append( self.effect.pformat_object( tables, pformat_config = pformat_config_1 ) ) output.append( indent + "Number of Effects {{Spl: #nreff}}: " "{effects_count}".format( effects_count = self.effects_count ) ) if self.precision: output.append( indent + "Precision {{Spl: #precision}}: {precision}".format( precision = self.precision ) ) if self.fatigue: output.append( indent + "Fatigue {{Spl: #fatiguecost}}: {fatigue}".format( fatigue = self.fatigue ) ) elif self.gem_cost: output.append( indent + "Gem Cost {{Spl: #fatiguecost}}: {gem_cost}".format( gem_cost = self.gem_cost ) ) if self.next_spell: output.append( indent + "Next Spell {{Spl: #nextspell}}: " "{next_spell}".format( next_spell = tables[ Spells_DataTable.LABEL( ) ]\ .pformat_table_lookup( self.next_spell, tables, pformat_config = pformat_config_compact ) ) ) if self.attributes: for attribute in self.attributes: output.append( attribute.pformat_object( tables, pformat_config = pformat_config_1 ) ) if not pformat_config.suppress_unknowns and self.unknown_fields: output.append( indent + "Unknowns" ) for unknown_field in self.unknown_fields: output.append( unknown_field.pformat_object( tables, pformat_config = pformat_config_2 ) ) if self.description: output.append( "" ) text_wrapper = _TextWrapper( width = pformat_config.line_width, initial_indent = pformat_config_1.indent, subsequent_indent = pformat_config_1.indent ) output.extend( text_wrapper.wrap( self.description ) ) return "\n".join( output ) + "\n"
def offset(cls): return _SQLA_Column(_SQLA_Integer, primary_key=True)