Skip to content

Commit

Permalink
Merge branch 'inf-480-525-fo4-records-pt5' into dev
Browse files Browse the repository at this point in the history
The fifth part of my ongoing records refactoring towards Fallout 4.

Nothing particularly interesting going on here beyond some refactoring
to avoid duplication in GameInfo.init().

New FO4 records implemented: ECZN, EFSH, ENCH, EQUP, EXPL, EYES, FACT,
FLOR, FLST, FSTP, FSTS, FURN.
  • Loading branch information
Infernio committed Aug 27, 2022
2 parents 28578a6 + 15ab937 commit bb7e5ae
Show file tree
Hide file tree
Showing 34 changed files with 1,455 additions and 1,112 deletions.
16 changes: 14 additions & 2 deletions Mopy/Docs/Wrye Bash Advanced Readme.html
Original file line number Diff line number Diff line change
Expand Up @@ -5590,6 +5590,14 @@ <h3 id="patch-tags">Bash Tags <a class="back2top" href="#contents">Back to top</
<li>(DATA) All</li>
</ul>
</li>
<li>(EXPL) Explosion:
<ul>
<li>(MODL) Model</li>
<li>(MNAM) Image Space Modifier</li>
<li>(DATA) Light</li>
<li>(DATA) Impact Dataset</li>
</ul>
</li>
<li>(FLOR) Flora: (MODL) Model</li>
<li>(FURN) Furniture: (MODL) Model</li>
<li>(GRAS) Grass: (MODL) Model</li>
Expand Down Expand Up @@ -5884,8 +5892,12 @@ <h3 id="patch-tags">Bash Tags <a class="back2top" href="#contents">Back to top</
</li>
<li>(EXPL) Explosion:
<ul>
<li>(DATA) Sound 1</li>
<li>(DATA) Sound 2</li>
<li>(DATA)
<ul>
<li>Sound Level</li>
<li>Sound 1</li>
<li>Sound 2</li>
</ul>
</ul>
</li>
<li>(FLOR) Flora:
Expand Down
6 changes: 3 additions & 3 deletions Mopy/bash/basher/saves_links.py
Original file line number Diff line number Diff line change
Expand Up @@ -517,11 +517,11 @@ class Save_EditCreated(OneItemLink):
_help = _(u'Allow user to rename custom items (spells, enchantments, etc)')

def __init__(self, save_rec_type):
if save_rec_type not in Save_EditCreated.menuNames:
if save_rec_type not in self.menuNames:
raise ArgumentError
super(Save_EditCreated, self).__init__()
super().__init__()
self.save_rec_type = save_rec_type
self._text = Save_EditCreated.menuNames[self.save_rec_type]
self._text = self.menuNames[self.save_rec_type]

def Execute(self):
#--Get SaveFile
Expand Down
6 changes: 3 additions & 3 deletions Mopy/bash/bolt.py
Original file line number Diff line number Diff line change
Expand Up @@ -1327,7 +1327,7 @@ def clearReadOnly(dirPath):
#------------------------------------------------------------------------------
class Flags(object):
"""Represents a flag field."""
__slots__ = [u'_field']
__slots__ = ('_field',)
_names = {}

@classmethod
Expand All @@ -1344,7 +1344,7 @@ def from_names(cls, *names):
elif flg_name: #--skip if "name" is 0 or None
namesDict[flg_name] = index
class __Flags(cls):
__slots__ = []
__slots__ = ()
_names = namesDict
return __Flags

Expand Down Expand Up @@ -1459,7 +1459,7 @@ def __repr__(self):
class TrimmedFlags(Flags):
"""Flag subtype that will discard unnamed flags on __init__ and dump
(or perform other kind of trimming)."""
__slots__ = []
__slots__ = ()

def __init__(self, value=0):
super().__init__(value)
Expand Down
8 changes: 4 additions & 4 deletions Mopy/bash/bosh/_saves.py
Original file line number Diff line number Diff line change
Expand Up @@ -744,11 +744,11 @@ def setCastWhenUsedEnchantmentNumberOfUses(self,uses):
enchant cost)."""
count = 0
for record in self.createdEnchantments:
if record.itemType in [1,2]:
if record.item_type in (1, 2):
charge_over_uses = 0 if uses == 0 else max(
record.chargeAmount // uses, 1)
if record.enchantCost == charge_over_uses: continue
record.enchantCost = charge_over_uses
record.charge_amount // uses, 1)
if record.enchantment_cost == charge_over_uses: continue
record.enchantment_cost = charge_over_uses
record.setChanged()
record.getSize()
count += 1
Expand Down
221 changes: 144 additions & 77 deletions Mopy/bash/brec/common_records.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,9 @@
MelUnion, MelSorted, MelSimpleArray
from .basic_elements import MelBase, MelFid, MelFids, MelFloat, MelGroups, \
MelLString, MelNull, MelStruct, MelUInt32, MelSInt32, MelFixedString, \
MelUnicode, unpackSubHeader, MelUInt32Flags, MelString
from .common_subrecords import MelEdid, MelDescription, MelColor, MelDebrData
MelUnicode, unpackSubHeader, MelUInt32Flags, MelString, MelUInt8Flags
from .common_subrecords import MelEdid, MelDescription, MelImpactDataset, \
MelColor, MelDebrData, MelFull, MelIcon, MelBounds
from .record_structs import MelRecord, MelSet
from .utils_constants import FID, FormId
from .. import bolt, exception
Expand All @@ -44,40 +45,76 @@
class AMreWithItems(MelRecord):
"""Base class for record types that contain a list of items (see
common_subrecords.AMelItems)."""
__slots__ = []
__slots__ = ()

def mergeFilter(self, modSet):
self.items = [i for i in self.items if i.item.mod_id in modSet]

#------------------------------------------------------------------------------
class AMreActor(AMreWithItems):
"""Base class for Creatures and NPCs."""
__slots__ = []
__slots__ = ()

def mergeFilter(self, modSet):
super().mergeFilter(modSet)
self.spells = [x for x in self.spells if x.mod_id in modSet]
self.factions = [x for x in self.factions if x.faction.mod_id in modSet]

#------------------------------------------------------------------------------
class AMreGmst(MelRecord):
"""Game Setting record. Base class, each game should derive from this
class."""
Ids = None
rec_sig = b'GMST'
class AMreFlst(MelRecord):
"""FormID List."""
rec_sig = b'FLST'
__slots__ = ('mergeOverLast', 'mergeSources', 'items', 'de_records',
're_records')

melSet = MelSet(
MelEdid(),
MelUnion({
u'b': MelUInt32(b'DATA', u'value'), # actually a bool
u'f': MelFloat(b'DATA', u'value'),
u's': MelLString(b'DATA', u'value'),
}, decider=AttrValDecider(
u'eid', transformer=lambda e: e[0] if e else u'i'),
fallback=MelSInt32(b'DATA', u'value')
),
)
__slots__ = melSet.getSlotsUsed()
def __init__(self, header, ins=None, do_unpack=False):
super().__init__(header, ins, do_unpack=do_unpack)
self.mergeOverLast = False #--Merge overrides last mod merged
self.mergeSources = None #--Set to list by other functions
self.items = None #--Set of items included in list
#--Set of items deleted by list (Deflst mods) unused for Skyrim
self.de_records = None #--Set of items deleted by list (Deflst mods)
self.re_records = None # unused, needed by patcher

def mergeFilter(self, modSet):
self.formIDInList = [f for f in self.formIDInList if
f.mod_id in modSet]

def mergeWith(self,other,otherMod):
"""Merges newLevl settings and entries with self.
Requires that: self.items, other.de_records be defined."""
#--Remove items based on other.removes
if other.de_records:
removeItems = self.items & other.de_records
self.formIDInList = [fi for fi in self.formIDInList
if fi not in removeItems]
self.items |= other.de_records
#--Add new items from other
newItems = set()
formIDInListAppend = self.formIDInList.append
newItemsAdd = newItems.add
for fi in other.formIDInList:
if fi not in self.items:
formIDInListAppend(fi)
newItemsAdd(fi)
if newItems:
self.items |= newItems
#--Is merged list different from other? (And thus written to patch.)
if len(self.formIDInList) != len(other.formIDInList):
self.mergeOverLast = True
else:
for selfEntry, otherEntry in zip(self.formIDInList,
other.formIDInList):
if selfEntry != otherEntry:
self.mergeOverLast = True
break
else:
self.mergeOverLast = False
if self.mergeOverLast:
self.mergeSources.append(otherMod)
else:
self.mergeSources = [otherMod]
self.setChanged()

#------------------------------------------------------------------------------
class AMreHeader(MelRecord):
Expand Down Expand Up @@ -206,7 +243,7 @@ def getNextObject(self):
@property
def num_masters(self): return len(self.masters)

__slots__ = []
__slots__ = ()

#------------------------------------------------------------------------------
class AMreLeveledList(MelRecord):
Expand All @@ -230,8 +267,8 @@ class AMreLeveledList(MelRecord):
top_copy_attrs = ()
# TODO(inf) Only overriden for FO3/FNV right now - Skyrim/FO4?
entry_copy_attrs = (u'listId', u'level', u'count')
__slots__ = [u'mergeOverLast', u'mergeSources', u'items', u'de_records',
u're_records']
__slots__ = ('mergeOverLast', 'mergeSources', 'items', 'de_records',
're_records')
# + ['flags', 'entries'] # define those in the subclasses

def __init__(self, header, ins=None, do_unpack=False):
Expand Down Expand Up @@ -327,6 +364,21 @@ def mergeWith(self,other,otherMod):

#------------------------------------------------------------------------------
# Full classes ----------------------------------------------------------------
#------------------------------------------------------------------------------
class MreAstp(MelRecord):
"""Association Type."""
rec_sig = b'ASTP'

melSet = MelSet(
MelEdid(),
MelString(b'MPRT', 'male_parent_title'),
MelString(b'FPRT', 'female_parent_title'),
MelString(b'MCHT', 'male_child_title'),
MelString(b'FCHT', 'female_child_title'),
MelUInt32(b'DATA', 'family_association'),
)
__slots__ = melSet.getSlotsUsed()

#------------------------------------------------------------------------------
class MreColl(MelRecord):
"""Collision Layer."""
Expand Down Expand Up @@ -397,67 +449,63 @@ class MreDlvw(MelRecord):
__slots__ = melSet.getSlotsUsed()

#------------------------------------------------------------------------------
class MreFlst(MelRecord):
"""FormID List."""
rec_sig = b'FLST'
class MreDual(MelRecord):
"""Dual Cast Data."""
rec_sig = b'DUAL'

_inherit_scale_flags = Flags.from_names('hit_effect_art_scale',
'projectile_scale', 'explosion_scale')

melSet = MelSet(
MelEdid(),
MelFids('formIDInList', MelFid(b'LNAM')), # do *not* sort!
MelBounds(),
MelStruct(b'DATA', ['6I'], (FID, 'dual_projectile'),
(FID, 'dual_explosion'), (FID, 'effect_shader'),
(FID, 'dual_hit_effect_art'), (FID, 'dual_impact_dataset'),
(_inherit_scale_flags, 'inherit_scale_flags')),
)
__slots__ = melSet.getSlotsUsed()

__slots__ = melSet.getSlotsUsed() + [u'mergeOverLast', u'mergeSources',
u'items', u'de_records',
u're_records']
#------------------------------------------------------------------------------
class MreEyes(MelRecord):
"""Eyes."""
rec_sig = b'EYES'

def __init__(self, header, ins=None, do_unpack=False):
super(MreFlst, self).__init__(header, ins, do_unpack=do_unpack)
self.mergeOverLast = False #--Merge overrides last mod merged
self.mergeSources = None #--Set to list by other functions
self.items = None #--Set of items included in list
#--Set of items deleted by list (Deflst mods) unused for Skyrim
self.de_records = None #--Set of items deleted by list (Deflst mods)
self.re_records = None # unused, needed by patcher
# not_male and not_female exist since FO3
_eyes_flags = Flags.from_names('playable', 'not_male', 'not_female')

def mergeFilter(self, modSet):
self.formIDInList = [f for f in self.formIDInList if
f.mod_id in modSet]
melSet = MelSet(
MelEdid(),
MelFull(),
MelIcon(),
MelUInt8Flags(b'DATA', 'flags', _eyes_flags),
)
__slots__ = melSet.getSlotsUsed()

def mergeWith(self,other,otherMod):
"""Merges newLevl settings and entries with self.
Requires that: self.items, other.de_records be defined."""
#--Remove items based on other.removes
if other.de_records:
removeItems = self.items & other.de_records
self.formIDInList = [fi for fi in self.formIDInList
if fi not in removeItems]
self.items |= other.de_records
#--Add new items from other
newItems = set()
formIDInListAppend = self.formIDInList.append
newItemsAdd = newItems.add
for fi in other.formIDInList:
if fi not in self.items:
formIDInListAppend(fi)
newItemsAdd(fi)
if newItems:
self.items |= newItems
#--Is merged list different from other? (And thus written to patch.)
if len(self.formIDInList) != len(other.formIDInList):
self.mergeOverLast = True
else:
for selfEntry, otherEntry in zip(self.formIDInList,
other.formIDInList):
if selfEntry != otherEntry:
self.mergeOverLast = True
break
else:
self.mergeOverLast = False
if self.mergeOverLast:
self.mergeSources.append(otherMod)
else:
self.mergeSources = [otherMod]
self.setChanged()
#------------------------------------------------------------------------------
class MreFstp(MelRecord):
"""Footstep."""
rec_sig = b'FSTP'

melSet = MelSet(
MelEdid(),
MelImpactDataset(b'DATA'),
MelString(b'ANAM', 'fstp_tag'),
)
__slots__ = melSet.getSlotsUsed()

#------------------------------------------------------------------------------
class MreFsts(MelRecord):
"""Footstep Set."""
rec_sig = b'FSTS'

melSet = MelSet(
MelEdid(),
MelStruct(b'XCNT', ['5I'], 'count_walking', 'count_running',
'count_sprinting', 'count_sneaking', 'count_swimming'),
MelSimpleArray('footstep_sets', MelFid(b'DATA')),
)
__slots__ = melSet.getSlotsUsed()

#------------------------------------------------------------------------------
class MreGlob(MelRecord):
Expand All @@ -474,6 +522,25 @@ class MreGlob(MelRecord):
)
__slots__ = melSet.getSlotsUsed()

#------------------------------------------------------------------------------
class MreGmst(MelRecord):
"""Game Setting.."""
rec_sig = b'GMST'
isKeyedByEid = True # NULL fids are acceptable.

melSet = MelSet(
MelEdid(),
MelUnion({
u'b': MelUInt32(b'DATA', u'value'), # actually a bool
u'f': MelFloat(b'DATA', u'value'),
u's': MelLString(b'DATA', u'value'),
}, decider=AttrValDecider(
u'eid', transformer=lambda e: e[0] if e else u'i'),
fallback=MelSInt32(b'DATA', u'value')
),
)
__slots__ = melSet.getSlotsUsed()

#------------------------------------------------------------------------------
class MreLand(MelRecord):
"""Land."""
Expand Down
Loading

1 comment on commit bb7e5ae

@Infernio
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once again forgot: Under #480, #482 and #525.

Please sign in to comment.