#Making a new map/hideout location
1564 messages ยท Page 2 of 2 (latest)
It doesn't actually do anything outside of the stringify, here.
Its sole purpose is pretty much just readability.
Alright, I just saw at the bottom the enums and it kinda looked like flow and how it operated.
Is it something you have to declare in the class? Cause I'm looking at the BGM code and I don't really see anything to do with the enums. Sorry if I'm just not reading correctly.
Hm, maybe I'm misremembering... but it should only be in the stringify method.
It doesn't factor into the read/write.
is it hex(self.BitFlagSection << 16), self.BitFlagId ?
return "Field Major ID: {}, Field Minor ID: {}, Date Range: {:02d}/{:02d} - {:02d}/{:02d}, Unknown Type: {}, Weather Type: {}, Field Type: {}, BGM Cue ID: {}, BitFlag: {} + {}".format(
self.FieldMajorId, self.FieldMinorId,
self.MinMonth, self.MinDay, self.MaxMonth, self.MaxDay,
UnknownTypes(self.UnknownType).name, WeatherTypes(self.WeatherType).name, FieldTypes(self.FieldType).name,
self.BgmCueId, hex(self.BitFlagSection << 16), self.BitFlagId,
) this is all thats there
No, it should be like WeatherTypes(...).name
oh
That's what maps the number to the string when printed.
I was right then
I get this now
whats the criteria for putting the ANY = 255 in the enums?
More convention than anything -- if you see a field that's like 0, 1, 2, 3, 4... and then just 255, that's the max uint8 value / equivalent to the int8 -1. The convention the BGM table is following is that that big number means that field's value isn't considered for that entry.
Max int16 is 65535, which you might also spot here.
You'll see that number in the field IDs of the output, as well. It means that the condition is going to apply to any field.
I see so then
Wood = 1,
Stone = 2,
Grass = 3,
Soil = 4,
Carpet = 5,
Metal = 6,
Bare = 7,
Crawl = 8,
Sand = 9,
Would have the any = 255
It's only really relevant if it's actually observed, though. It won't be relevant for every field.
I wouldn't add it to the footsteps one unless it's observed.
And in that case, it might actually mean NONE or something similar.
The actual use of the sort of "weird" final field value is just a question of what's being done with it in the game's code.
what happens if it isn't put in the enum but then 255 gets called by the script? Would it display at all?
The stringify method would give an error!
Enums have the added function of helping to make sure you're covering all observed values.
(Because enum-using code will die if you try to give it a not-defined value.)
I see, just wanted to make sure before it blew up my pc or something (you never know)
Lol, no, it'll just give a regular, non-explosive error. Probably.
So if it's under an enum do I define it in the init or do I leave them out? I assume leave it but I want to make sure.
The only thing that needs to be in the init is the actual variable the number gets read into. Then, in stringify, the enum can optionally be used to map that number to a string for readability.
Alright cool. Footstep should be done soon then.
Nice. This is an interesting way to learn Python, eh? ๐
And I wouldn't have it any other way.
class FLDFOOTSTEPCND(Serializable):
def __init__(self):
self.FieldMajorId = None
self.FieldMinorId = None
self.FootstepTypes = None
def __rw_hook__(self, rw, datasize):
self.FieldMajorId = rw.rw_uint16(self.FieldMajorId)
self.FieldMinorId = rw.rw_uint16(self.FieldMinorId)
self.FootstepTypes = rw.rw_uint16(self.FootstepTypes)
def stringify(self):
return "Field Major ID: {}, Field Minor ID: {}, Footstep Type {}".format(
self.FieldMajorId, self.FieldMinorId, FootstepTypes(self.FootstepType).name,
)
I appear to be having an assertion error.
class FootstepTypes(Enum):
Wood = 1
Stone = 2
Grass = 3
Soil = 4
Carpet = 5
Metal = 6
Bare = 7
Crawl = 8
Sand = 9
Wet = 10
Creak = 11
Wood_And_Creak = 12
Silence = 13
Thin_Metal = 14
this is my enum btw
hmm, how complex is GUI coding for python?
I wonder if it'd be a good idea to have the scripts be a GUI thing rather than commandline.
You can certainly do it, but distributing it is annoying because Microsoft blocks that shit like craaazy.
Fortunately, these can be ported directly to C#, which is what I do for EVTUI. including making them in python first because it's quicker for dev/testing lol
I've definitely thought about basically making 010 Editor 2, lmao.
But that would inherently have less flexibility than the scripts. Moving toward a GUI comes with an inherent loss of flexibility... which, in some cases, can be fine!
Thats true. It was just on my mind and was curious. I'd be down to help you out with 010 Editor 2 though. Since this is pretty fun so far.
I think my main idea with the scripts is that they can just have some general-purpose / sample tools, but if something seems like it would be good as a bigger independent project, then it should split off accordingly.
So like, my ACB/AWB/ADX parsers aren't in here, because they're a separate project.
Understandable. You wouldn't want to bloat the project with stuff that can be split off.
But if you have a vision for a GUI version of the FTD code, whether it's just 010 2 or something slightly different, that could be neat ๐
Maybe, idk. I'll think about it after I get done helping you with all of these files that need interpreting.
in any case, I'm not exactly sure what the issue is.
What's the assertion error?
File "<frozen runpy>", line 198, in _run_module_as_main
File "<frozen runpy>", line 88, in _run_code
File "E:\Pictures\Rune\Parser\ParserPlayground-main\Scripts\PrintTable.py", line 19, in <module>
main()
File "E:\Pictures\Rune\Parser\ParserPlayground-main\Scripts\PrintTable.py", line 14, in main
table.read(args.table_path, filename=(args.table_name if args.table_name else Path(args.table_path).stem))
File "E:\Pictures\Rune\Parser\ParserPlayground-main\Formats\exbip\Serializable\Traits.py", line 6, in read
rw.rw_obj(self, *args, **kwargs)
File "E:\Pictures\Rune\Parser\ParserPlayground-main\Formats\exbip\Descriptors\Object.py", line 12, in construct
value.rw_hook(binary_target, *args, **kwargs)
File "E:\Pictures\Rune\Parser\ParserPlayground-main\Formats\FTD.py", line 82, in rw_hook
self.Entries[i] = rw.rw_obj(self.Entries[i], FtdList, filename)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "E:\Pictures\Rune\Parser\ParserPlayground-main\Formats\exbip\Descriptors\Object.py", line 12, in construct
value.rw_hook(binary_target, *args, **kwargs)
File "E:\Pictures\Rune\Parser\ParserPlayground-main\Formats\FTD.py", line 157, in rw_hook
assert rw.tell() == self.DataSize
^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError
You're missing a field! There should be one more for Padding.
(The error means the entry was too short. i.e., not enough fields being read.)
Aw, yeah, you should get to bed.
not until this is done
๐ซก
hmm, it seems to not be recognizing FootstepTypes(self.FootstepType).name, even though it's the same as your weather one WeatherTypes(self.WeatherType).name,
Traceback (most recent call last):
File "<frozen runpy>", line 198, in _run_module_as_main
File "<frozen runpy>", line 88, in _run_code
File "E:\Pictures\Rune\Parser\ParserPlayground-main\Scripts\PrintTable.py", line 19, in <module>
main()
File "E:\Pictures\Rune\Parser\ParserPlayground-main\Scripts\PrintTable.py", line 15, in main
table.pretty_print()
File "E:\Pictures\Rune\Parser\ParserPlayground-main\Formats\FTD.py", line 38, in pretty_print
self.Entries[i].pretty_print(indent_level=indent_level+1)
File "E:\Pictures\Rune\Parser\ParserPlayground-main\Formats\FTD.py", line 126, in pretty_print
print("{}({}) {}".format(" "*indent_level, i, self.Entries[i].stringify()))
^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "E:\Pictures\Rune\Parser\ParserPlayground-main\Formats\FTD.py", line 343, in stringify
self.FieldMajorId, self.FieldMinorId, FootstepTypes(self.FootstepType).name, self.Padding,
^^^^^^^^^^^^^^^^^
AttributeError: 'FLDFOOTSTEPCND' object has no attribute 'FootstepType'. Did you mean: 'FootstepTypes'?
Your variable in the class is also called FootstepTypes, so you should rename it in the init/rw methods if you want.
I just did that. Same error.
class FLDFOOTSTEPCND(Serializable):
def __init__(self):
self.FieldMajorId = None
self.FieldMinorId = None
self.FootstepType = None
self.Padding = None
def __rw_hook__(self, rw, datasize):
self.FieldMajorId = rw.rw_uint16(self.FieldMajorId)
self.FieldMinorId = rw.rw_uint16(self.FieldMinorId)
self.FootstepType = rw.rw_uint16(self.FootstepTypes)
self.Padding = rw.rw_uint16(self.Padding)
def stringify(self):
return "Field Major ID: {}, Field Minor ID: {}, Footstep Type {}".format(
self.FieldMajorId, self.FieldMinorId, FootstepTypes(self.FootstepType).name, self.Padding,
)
updated version
oh
You left one called FootstepTypes in the rw_hook.
yeah
That should be all, I think.
File "<frozen runpy>", line 198, in _run_module_as_main
File "<frozen runpy>", line 88, in _run_code
File "E:\Pictures\Rune\Parser\ParserPlayground-main\Scripts\PrintTable.py", line 19, in <module>
main()
File "E:\Pictures\Rune\Parser\ParserPlayground-main\Scripts\PrintTable.py", line 15, in main
table.pretty_print()
File "E:\Pictures\Rune\Parser\ParserPlayground-main\Formats\FTD.py", line 38, in pretty_print
self.Entries[i].pretty_print(indent_level=indent_level+1)
File "E:\Pictures\Rune\Parser\ParserPlayground-main\Formats\FTD.py", line 126, in pretty_print
print("{}({}) {}".format(" "*indent_level, i, self.Entries[i].stringify()))
^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "E:\Pictures\Rune\Parser\ParserPlayground-main\Formats\FTD.py", line 343, in stringify
self.FieldMajorId, self.FieldMinorId, FootstepTypes(self.FootstepType).name, self.Padding,
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\Freeman Cavarretta\AppData\Local\Programs\Python\Python312\Lib\enum.py", line 757, in call
return cls.new(cls, value)
^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\Freeman Cavarretta\AppData\Local\Programs\Python\Python312\Lib\enum.py", line 1171, in new
raise ve_exc
ValueError: 0 is not a valid FootstepTypes
Gotta add a 0 entry to the enum, apparently ๐ค
Odd, because that's not in the template.
I'm guessing 0 = none.
But no, that's 13... hm....
You can try just making it UNK for now, just to get the code to work.
nice, it works now
but yeah odd
I see a couple UNKs in the table.
guess we'll figure that out later.
def __init__(self):
self.FieldMajorId = None
self.FieldMinorId = None
self.FootstepType = None
self.Padding = None
def __rw_hook__(self, rw, datasize):
self.FieldMajorId = rw.rw_uint16(self.FieldMajorId)
self.FieldMinorId = rw.rw_uint16(self.FieldMinorId)
self.FootstepType = rw.rw_uint16(self.FootstepType)
self.Padding = rw.rw_uint16(self.Padding)
def stringify(self):
return "Field Major ID: {}, Field Minor ID: {}, Footstep Type {}".format(
self.FieldMajorId, self.FieldMinorId, FootstepTypes(self.FootstepType).name, self.Padding,
)```
that should be both the enum and the main code
Might be worth double-checking this enum for accuracy. Which, I guess could be done with some modding.
How do you do that bash thing where the whole messages gets highlighted like this
Yeah i'll try that out tomorrow.
Wrap the code block in angle quotes: ```your code here```
perfect thank you
In any case, nice work!
UNK = 0
Wood = 1
Stone = 2
Grass = 3
Soil = 4
Carpet = 5
Metal = 6
Bare = 7
Crawl = 8
Sand = 9
Wet = 10
Creak = 11
Wood_And_Creak = 12
Silence = 13
Thin_Metal = 14```
Now that I know a bit more I'm gonna grind out as many as I can tomorrow. And check up on that UNK.
P5R but every footstep is... wet?
P5R but every footstep is UNK
Finally....
P5R has one good mod
Lol, anyway. Get some rest!
While you're on. How hard is it to decipher new filetypes?
Since sooner or later all the FTDs will be done
It's a lot of guess-and-checking, ime.
Most things have SOME basic template, at least. Just a lot of UNKs in there.
So the biggest challenge is figuring out what those mean.
I'm not sure there's any file type with absolutely nothing for it... other than maybe the menu star vector animation, which is weirdly unique.
Which I have a semi-formed parser for, lmao.
Alright, and I assume how you check it is just to change values then load up the game? Or is there any quicker way to do it?
That's the only actual way to do it, though I often find ways to reference images or videos to save myself the effort.
(I do not think finding videos would save any effort in this case.)
very true, i'd be very surprised if there were any
It'd have to be a full playthrough, if anything.
In any case that makes sense. I just wanted to make sure that I wasn't privy to any faster way to figure it out. But loading up the game isn't too bad. Now that my question was answered I'm gonna sleep. Thank you for all of this help. I know I keep saying it but man am I glad you spare your time to help. Just know that I appreciate it every time.
It's fun to help those who appreciate it!
Have a nice rest, and again, good work ๐
You too. 
Ahhhh, I figured out how FLDPLACENO.FTD actually assigns the name maps.

Updated the repo with your classes (with a couple new bits of info!) and a new script Scripts.CombineFieldNumbersAndNames :3
I definitely think it's worth double-checking that footsteps enum with the game, though, since the template was slightly inaccurate.
Thats awesome. How'd you figure it out? Oh I see it's 010 editor.
and i'll check out the footsteps ingame in a sec. Just got up
hmm, would it be possible to make a dynamic logger? So that you could load up a file and it'll log all of the table stuff that gets used like a normal logger.
okay, so another idea. Would it be possible to make it so the table gets printed (lets say to a txt file for example) then you could edit it however you want. Then repack it back into the FTD file?
So I checked footstep again. Most of the roomIDs are 0. That doesn't seem correct. Though the roomID could be per map.
Just interpreted FLDSYMMODELSCL
def __init__(self):
self.ShadowID = None
self.ModelScale = None
def __rw_hook__(self, rw, datasize):
self.ShadowID = rw.rw_uint16(self.ShadowID)
self.ModelScale = rw.rw_unit16(self.ModelScale)
def stringify(self):
return "Shadow ID: {}, Model Scale: {}".format(
self.ShadowID, self.ModelScale,
)```
And FLDSYMMODELNO aswell
def __init__(self):
self.ShadowOffset = None
def __rw_hook__(self, rw, datasize):
self.ShadowOffset = rw.rw_uint16(self.ShadowOffset)
def stringify(self):
return "Shadow Offset: {}".format(
self.ShadowOffset,
)```
so that leaves:
FLDWIREANIMDATA
fclPublicShopDataTable
FLDMODELSE
FLDGIMMICKSE
FLDDOORSE
FLDADDACTANIM
FLDDOORANIM
fclCombElectItemTable
fclItemLineUpAccTable
fclItemLineUpTable
FLDLMAPFARE
FLDLMAPSTATION
FLDWHOLEMAPTABLE and FLDWHOLEMAPTABLEDNG
FLDDNGPACK
And then a couple more give or take any mistakes.
Out of curiosity I looked at the files for the crowds and damn. Theres a whole bunch of unkowns. But it's crowds so that should be easy to test whenever we get to that.
Wait, is it in the 010 editor templates?? I don't see it if so. No, I just figured it out with some inference ๐คท The contents of FLDPLACENO were pretty straightforward.
I feel like Cornflakes may have been working on the debug console having a live table editing capability...? But I could be wildly bullshitting there.
The UI looks like 010 editor's UI.
Ohhh, no, that's just my commandline.
The signature purple of the Ubuntu terminal....
oh i see
She was. Dunno how far it's been developed
The main issue there would be having access to wherever the game stores a copy of the table in memory, since I'm pretty sure it doesn't access the file every time (only on startup).
With that, you could get a code mod to do whatever, probably.
They should mostly be 0! The fields that have footsteps for rooms 1 or 2 are indeed the ones that have those extra rooms defined. But not all multi-room fields specify footsteps for every room... I suspect the game just takes the value for the field's 0 room for all, in that case.
I see, was just curious. I assume you also figured out it was wrong based on inference?
Yeah, I tried to treat it as padding/reserve and checked that it was always zero, but it was sometimes 256 or 512. Those are very specific large numbers that suggest the one int16 field is actually two int8s. And that the first of those two int8s can be 0, 1, or 2, which makes much more sense.
(And corresponds to room IDs.)
I see, i'll try and work that into the interpretations if need be.
But yeah, I have a better way to explain it now. Would it be possible to have something similar to the atlus script tools for the parser? So that editing them would be as simple as the script tools. Since a lot of these are gonna have to be tested extensively.
Hm, you mean a way to decompile the tables to a human-readable file format, which can then be (modified and) recompiled back into FTDs.
It would be possible, if we could think of a reasonable format. Although at that point a GUI might in fact be easier to set up ๐ค I only haven't done this because my workflow is just to have a script that modifies and prints the output, and to run that over and over ๐
I see, yeah cause stuff like the crowd files have tons of unknown variables and that would not be fun to actually test for. Granted I haven't really made a script that edits stuff so it might be less annoying than I think it would be.
Kinda. But if the process doesn't require a ton of tinkering I can get into a good flow of it and really knock out stuff (like the backpack flag and whatnot)
Thats probably why I like bruteforcing. I can just get in the zone.
True! There may be a pure-script way that's just as easy as having an output format, since even in the latter case, you'd still have to run the compile script every time.
It would probably be a matter of just like. Setting up all the variables. Hmmm....
I will ponder this....
Before that, I can probably pull together a script that checks what all the observed values are for the different unknowns.
That would help save some time, too.
Yeah that could be cool. In any case, what does s32 from the templates transfer to in the py script?
s32... probably int32?
Alright.
It's probably just a normal int, yes. Signed 32 bit value
so im looking at fldWireAnimData. Theres a bunch of unanmed fields. Too many to where it can't be interpreted without an enum. But I don't know which ones to put in said enum because they're unamed.
Hmm, it's hard to say how far enums will get us without knowing what the possible values are. Maybe I'll cook up a script that can show all observed values for each field in a table, so we can see which are just empty fields vs. which seem like an unknown enum.
Yeah, cause a couple in here seem pretty hard if we don't have something like that.
funny idea I just had. We call it 020 editor.
LMAO.
But yeah, I usually have a script for collating observed values like this, but mostly for specific formats. Like that's how I do a lot of EVT analysis.
Should be reasonably generalizable for different FTD types, though ๐ค
I see, that makes sense.
On the same topic what does ENC mean?
It's a struct defined in the 010 FTD template.
typedef struct
{
u16 EncounterID;
u8 Weight;
u8 Padding; // always 0
} ENC <optimize=false, read=Str( "%d"+" Encounter: "+"%d", Weight, EncounterID )>;
In the Python scripts, that would be translated as a new Serializable class, which gets read via rw_obj. There're some rw_objs in the code for an example, but I could also do that part if it's confusing.
I see, I think I get it. I'll get back to it once im done with FLDADDACTANIM
on FLDADDACTANIM (sorry had other things to do so im getting to it now) theres this comment
//readString=Str("m%03d_%03d || PlayerAnim Gap ID -> %d", fldObjMajorId, fldObjMinorId, fldAnimGapID);
Is whats inside the comment something I have to interpret or is it just fluff?
Looks like it's the equivalent of a stringify, though in this case to indicate the GAP file + animation ID being referenced. Purely a print-formatting thing.
Or maybe the animations are stored in the field model itself...? Well, either way.
I see, I was thinking it was something like stringify but I wasn't sure. Didn't want to try and add it if it wasn't anything.
Yep, just string formatting stuff that would only matter for display purposes.
clyde blocked the message? lol
class FLDADDACTANIM(Serializable):
def __init__(self):
self.FieldObjMajorId = None
self.FieldObjMinorId = None
self.FLDAnimGapID = None
self.UNKNOWN0 = None
self.UNKNOWN01 = None
self.UNKNOWN1 = None
self.UNKNOWN2 = None
self.UNKNOWN3 = None
self.UNKNOWN4 = None
self.UNKNOWN5 = None
self.UNKNOWN6 = None
def __rw_hook__(self, rw, datasize):
self.FieldObjMajorId = rw.rw_int16(self.FieldObjMajorId)
self.FieldObjMinorId = rw.rw_int16(self.FieldObjMinorId)
self.FLDAnimGapID = rw.rw_uint8(self.FLDAnimGapID)
self.UNKNOWN0 = rw.rw_uint8(self.UNKNOWN0)
self.UNKNOWN01 = rw.rw_int16(self.UNKNOWN01)
self.UNKNOWN1 = rw.rw_int16(self.UNKNOWN1)
self.UNKNOWN2 = rw.rw_int16(self.UNKNOWN2)
self.UNKNOWN3 = rw.rw_int32(self.UNKNOWN3)
self.UNKNOWN4 = rw.rw_int16(self.UNKNOWN4)
self.UNKNOWN5 = rw.rw_int16(self.UNKNOWN5)
self.UNKNOWN6 = rw.rw_int32(self.UNKNOWN6)
def stringify(self):
return "Field Major ID: {}, Field Minor ID: {}, FLD AnimGap ID: {}, UNKNOWN 0: {}, UNKNOWN 01: {}, UNKNOWN 1: {}, UNKNOWN 2: {}, UNKNOWN 3: {}, UNKNOWN 4: {}, UNKNOWN 5: {}, UNKNOWN 6: {}".format(
self.FieldMajorId, self.FieldMinorId,
self.FLDAnimGapID, self.UNKNOWN0, self.UNKNOWN1,
self.UNKNOWN01, self.UNKNOWN2, self.UNKNOWN3, self.UNKNOWN4, self.UNKNOWN5, self.UNKNOWN6,
)
so it's fine with the code but noit my explanation?
As I was going to say. This is what I have so far for FLDADDACTANIM. Im getting an assertion error but I think I know what it is.
true
okay nvm the thing I was thinking about wasn't it.
is it the unkowns? I hope not cause that'd suck
What's the error?
Traceback (most recent call last):
File "<frozen runpy>", line 198, in _run_module_as_main
File "<frozen runpy>", line 88, in _run_code
File "E:\Pictures\Rune\NewParser\ParserPlayground-main\Scripts\PrintTable.py", line 19, in <module>
main()
File "E:\Pictures\Rune\NewParser\ParserPlayground-main\Scripts\PrintTable.py", line 14, in main
table.read(args.table_path, filename=(args.table_name if args.table_name else Path(args.table_path).stem))
File "E:\Pictures\Rune\NewParser\ParserPlayground-main\Formats\exbip\Serializable\Traits.py", line 6, in read
rw.rw_obj(self, *args, **kwargs)
File "E:\Pictures\Rune\NewParser\ParserPlayground-main\Formats\exbip\Descriptors\Object.py", line 12, in construct
value.rw_hook(binary_target, *args, **kwargs)
File "E:\Pictures\Rune\NewParser\ParserPlayground-main\Formats\FTD.py", line 80, in rw_hook
self.Entries[i] = rw.rw_obj(self.Entries[i], FtdList, filename)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "E:\Pictures\Rune\NewParser\ParserPlayground-main\Formats\exbip\Descriptors\Object.py", line 12, in construct
value.rw_hook(binary_target, *args, **kwargs)
File "E:\Pictures\Rune\NewParser\ParserPlayground-main\Formats\FTD.py", line 175, in rw_hook
assert rw.tell() == self.DataSize
^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError
Hm, not the right number/size of fields, then.
I though that the first time (I added UNKOWN1 cause I forgot about it) but I guess theres more?
Though the template doesn't say any more
ADDACTANIM":
s16 fldObjMajorId;
s16 fldObjMinorId;
u8 fldAnimGapID;
u8 unk0;
s16 unk01;
s16 unk1;
s16 unk2;
s32 unk3;
s16 unk4;
s16 unk5;
s32 unk6;
this is their fork
But I guess that answers it
Finally it works now
Odd how it didn't show up for me but oh well. Hopefully thats the only one.
In any case. Heres the code
;-; clyde
Your message could not be delivered. This is usually because you don't share a server with the recipient or the recipient is only accepting direct messages from friends.
New discord feature ig
def init(self):
self.FieldObjMajorId = None
self.FieldObjMinorId = None
self.FLDAnimGapID = None
self.UNKNOWN0 = None
self.UNKNOWN01 = None
self.UNKNOWN1 = None
self.UNKNOWN2 = None
self.UNKNOWN3 = None
self.UNKNOWN4 = None
self.UNKNOWN5 = None
self.UNKNOWN6 = None
self.UNKNOWN7 = None```
self.FieldObjMajorId = rw.rw_int16(self.FieldObjMajorId)
self.FieldObjMinorId = rw.rw_int16(self.FieldObjMinorId)
self.FLDAnimGapID = rw.rw_uint8(self.FLDAnimGapID)
self.UNKNOWN0 = rw.rw_uint8(self.UNKNOWN0)
self.UNKNOWN01 = rw.rw_int16(self.UNKNOWN01)
self.UNKNOWN1 = rw.rw_int16(self.UNKNOWN1)
self.UNKNOWN2 = rw.rw_int16(self.UNKNOWN2)
self.UNKNOWN3 = rw.rw_int32(self.UNKNOWN3)
self.UNKNOWN4 = rw.rw_int16(self.UNKNOWN4)
self.UNKNOWN5 = rw.rw_int16(self.UNKNOWN5)
self.UNKNOWN6 = rw.rw_int32(self.UNKNOWN6)
self.UNKNOWN7 = rw.rw_int16(self.UNKNOWN7)
def stringify(self):
return "FieldObj Major ID: {}, FieldObj Minor ID: {}, FLD AnimGap ID: {}, UNKNOWN 0: {}, UNKNOWN 01: {}, UNKNOWN 1: {}, UNKNOWN 2: {}, UNKNOWN 3: {}, UNKNOWN 4: {}, UNKNOWN 5: {}, UNKNOWN 6: {}, UNKNOWN 7: {}".format(
self.FieldObjMajorId, self.FieldObjMinorId,
self.FLDAnimGapID, self.UNKNOWN0,self.UNKNOWN01, self.UNKNOWN1, self.UNKNOWN7,
self.UNKNOWN2, self.UNKNOWN3, self.UNKNOWN4, self.UNKNOWN5, self.UNKNOWN6,
)```
there we go
TF....
Yeah I have no idea. I guess maybe some new broken automated thing detecting the code?
To go back to ENCs. The example you were referencing was from FLDBGMCND right?
There should be a few examples of rw_obj, including in the main FTD class up top.
Yeah I just saw it ;-;. How would I go about adding it? Would it be added to every self. that includes ENC? Or would it be similar to the enums
Hm, it's probably easier for me to show by example and then explain.
damn
That's possibly more my skill issue for the explaining part, but the idea here is... every Serializable class works the same way, and rw_obj lets one pass the reading onto another one to keep things simpler.
The ENC class represents a common bundle of encounter data, so it ends up being convenient to let it read those three fields rather than explicitly coding those three same fields in a bunch of different places.
I see, so I looked at the rw_obj in the main FTD. So if im getting this right. You write down one ENC bit and then it kinda acts like the enums where then the other parts that need to reference it can just reference it there?
A bit, yeah, but this time it does actually affect the rw part, unlike the enums.
It might take some extra logic to make the stringify part work for the ENC objects, though.
Okay, so then I would write down one self. that has the ENC code then I assume I have to write something down for the other ENC tied selfs then it'll be referenced? Am I getting this right?
and then put something in the stringify
Probably not. Though I mostly get that from self.Entries = rw.rw_objs(self.Entries, FtdEntryTypes.__dict__.get(filename, FtdEntryTypes.Generic), self.EntryCount, self.DataSize//self.EntryCount)
You'd make a new Serializable ENC class, much like the other Serializable classes, and probably down near the enums. And sure, give it a stringify, too.
Then, pass ENC as one of the arguments to rw_obj, looking something like self.ThisIsAnEncObj = rw.rw_obj(self.ThisIsAnEncObj, ENC).
And when you go to stringify the parent class, you'll want to include self.ThisIsAnEncObj.stringify() to display that object as desired.
ohhhhhhhhh that makes so much sense
Oh, right, that's an rw_objs... with an extra argument. That's probably confusing, so ignore that I guess, lol.
Not relevant to what you're trying to do at the moment.
I hope im not too overbearing with my questions. Trust me im learning so much from this you have no idea.
I'm kinda just teaching you Python and OOP in one go, lol, so you're actually picking up an impressive amount very quickly. There may be a point where we want to move this to DMs, but to be fair, it has been related to parsing field tables so far ๐
Pretty soon I want to start parsing the map related stuff honestly. This is just a stepping stone to that.
Yessss ๐
But thank you. I never prided myself on being a quick learner before honestly but you've been a great teacher so far so maybe I was just having a skill issue before.
It's much easier to be a quick learner when you're applying stuff immediately to a project you care about!
Thats very true. Honestly this kinda reignited my passion for IT. Highschool ruined that for me due to bad teachers and whatnot. And honestly I'm really having fun with it again after so long. It feels nice.
High school and even college coding courses are really hit-or-miss, and most people who teach it are, uh... bad at doing that ๐
All of my best experiences with it have been working on super applied topics/projects. All my experiences teaching it, too, heh
Yeah, they just didn't really teach us at all. They just gave us testout and that was it. God that was immensely boring. There also was a whole ton of drama and stuff (especially with the teachers) that really dragged down the experience.
Yeah, lame....
class ENC(Serializable):
def __init__(self):
self.EncounterID = None
self.Weight = None
self.Padding = None
def __rw_hook__(self, rw, datasize):
self.EncounterID = rw.rw_uint16(self.EncounterID)
self.Weight = rw.rw_uint8(self.Weight)
self.Padding = rw.rw_uint8(self.Padding)
def stringify(self):
return "Encounter ID: {}, Weight: {}, Padding: {}".format(
self.ShadowID, self.ModelScale, self.ShadowID,
)
So this is what I have for the ENC class. I know im missing something but im not sure what it is.
u16 EncounterID;
u8 Weight;
u8 Padding; // always 0
} ENC <optimize=false, read=Str( "%d"+" Encounter: "+"%d", Weight, EncounterID )>;
this is the ENC code btw
Looks reasonable at a glance. Are you getting an error?
Not yet. I just wanted to see if I was actually doing it right.
It looks correct to me!
nice
hmm, it seems to think ENC isn't defined. Am I doing the code right?
class DATENCOUNTPACK(Serializable):
def init(self):
self.field_0 = None
self.UnusedSlot = None
self.AmbushList = None
self.NormalEncounter = None
self.NormalPinchEncounter = None
self.WhatDo = None
self.TheseOnesDo = None
self.StrongEncounter = None
self.StrongPinchEncounter = None
self.ReaperEncounter = None
self.Padding = None
def rw_hook(self, rw, datasize):
self.field_0 = rw.rw_uint8(self.field_0)
self.UnusedSlot = rw.rw_uint8(self.UnusedSlot)
self.AmbushList = rw.rw_uint16(self.AmbushList)
self.NormalEncounter = rw.rw_obj(self.NormalEncounter, ENC)
self.NormalPinchEncounter = rw.rw_obj(self.NormalPinchEncounter, ENC)
self.WhatDo = rw.rw_obj(self.WhatDo, ENC)
self.TheseOnesDo = rw.rw_obj(self.TheseOnesDo, ENC)
self.StrongEncounter = rw.rw_obj(self.StrongEncounter, ENC)
self.StrongPinchEncounter = rw.rw_obj(self.StrongPinchEncounter, ENC)
self.ReaperEncounter = rw.rw_obj(self.ReaperEncounter, ENC)
self.Padding = rw.rw_uint32(self.Padding)
def stringify(self):
return "field 0: {}, Unused Slot: {}, Ambush List: {}, Normal Encounter: {}, Normal Pinch Encounter: {}, What Do: {}, These Ones Do: {}, Strong Encounter: {}, Strong Pinch Encounter: {}, Reaper Encounter: {}, Padding: {}".format(
self.field_0, self.UnusedSlot, self.AmbushList,
self.NormalEncounter.stringify(), self.NormalPinchEncounter.stringify(), self.WhatDo.stringify(),
self.TheseOnesDo.stringify(), self.StrongEncounter.stringify(), self.Padding,
self.StrongPinchEncounter.stringify(), self.ReaperEncounter.stringify(),
)
discord keeps nuking me code ;-; had to split it up in 2 messages
Where'd you put the ENC definition? Is it inside of another class? It should be on the same indentation level as the enums.
so thats my problem
Yep, just calling it ENC assumes it's not inside anything else.
File "E:\Pictures\Rune\NewParser\ParserPlayground-main\Formats\exbip\Descriptors\Object.py", line 34, in construct
out.append(binary_target.rw_obj(value, constructor, *args, **kwargs))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "E:\Pictures\Rune\NewParser\ParserPlayground-main\Formats\exbip\Descriptors\Object.py", line 12, in construct
value.rw_hook(binary_target, *args, **kwargs)
File "E:\Pictures\Rune\NewParser\ParserPlayground-main\Formats\FTD.py", line 509, in rw_hook
self.NormalEncounter = rw.rw_obj(self.NormalEncounter, ENC)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "E:\Pictures\Rune\NewParser\ParserPlayground-main\Formats\exbip\Descriptors\Object.py", line 12, in construct
value.rw_hook(binary_target, *args, **kwargs)
^^^^^^^^^^^^^^^^^
AttributeError: 'ENC' object has no attribute 'rw_hook'
Do you have to put something inside the hook?
Oh, oops, my bad. You should get rid of the datasize argument from its __rw_hook__.
Inside of ENC, I mean!
ohhh
It doesn't use that argument, and you're not providing it (rightly), so the call doesn't recognize the extra-argument definition.
still same error
Hmmm....
You've formatted the name correctly? As __rw_hook__? And it's at the right indent level within the class?
Do I move them all the way to the left like the class?
also yes I finally figured out dark mode
Ah, both the init and the hook need two underscores before and after the names.
The underscores mean they are special boys.
how'd that happen?? ;-;
Underscores get treated as formatting on Discord, so if you did any copy/pasting, it might have removed them.
Progress!
do the selfs have to be indented like the Enums?
You probably have too many indents here, lol. The def lines should have one indent, and the stuff under those lines (inside the methods) should have two.
Leading indents, of course. The ones before the = are just for readability.
Think of the indentation like how you'd organize a nested bullet-point list.
I did that yet the error persists. Probably should've posted the error before.
File "E:\Pictures\Rune\NewParser\ParserPlayground-main\Formats\exbip\Descriptors\Object.py", line 34, in construct
out.append(binary_target.rw_obj(value, constructor, *args, **kwargs))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "E:\Pictures\Rune\NewParser\ParserPlayground-main\Formats\exbip\Descriptors\Object.py", line 12, in construct
value.rw_hook(binary_target, *args, **kwargs)
File "E:\Pictures\Rune\NewParser\ParserPlayground-main\Formats\FTD.py", line 509, in rw_hook
self.NormalEncounter = rw.rw_obj(self.NormalEncounter, ENC)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "E:\Pictures\Rune\NewParser\ParserPlayground-main\Formats\exbip\Descriptors\Object.py", line 12, in construct
value.rw_hook(binary_target, *args, **kwargs)
File "E:\Pictures\Rune\NewParser\ParserPlayground-main\Formats\FTD.py", line 535, in rw_hook
self.EncounterID = rw.rw_uint16(self.EncounterID)
^^^^^^^^^^^^^^^^
AttributeError: 'ENC' object has no attribute 'EncounterID'
Did you fix the name of __init__, too?
;-;
It also needs the double underscores to get called.
now just an assertion
File "E:\Pictures\Rune\NewParser\ParserPlayground-main\Formats\exbip\Descriptors\Object.py", line 12, in construct
value.rw_hook(binary_target, *args, **kwargs)
File "E:\Pictures\Rune\NewParser\ParserPlayground-main\Formats\FTD.py", line 175, in rw_hook
assert rw.tell() == self.DataSize
^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError
Too few fields... I think some of these may actually be arrays in the template ๐ค
So self.UnusedSlot = rw.uint8s(self.UnusedSlot, 7), to turn it into an array of 7 uint8s.
And the same for the rest in the template that have the [ 7 ] type of things at the end. Adjusting for the right number.
So there are actually 13 normal encounters.
oh, the numbers there mean something? Do I add the numbers from all of them in their corresponding self or just the 7?
Replace the 7 with whatever number is there in the template!
It's different for each one, yeah.
you never know with atlus
so then like this?
self.field_0 = rw.rw_uint8(self.field_0)
self.UnusedSlot = rw.rw_uint8(self.UnusedSlot, 7)
self.AmbushList = rw.rw_uint16(self.AmbushList, 8)
self.NormalEncounter = rw.rw_obj(self.NormalEncounter, ENC, 13)
self.NormalPinchEncounter = rw.rw_obj(self.NormalPinchEncounter, ENC, 5)
self.WhatDo = rw.rw_obj(self.WhatDo, ENC, 5)
self.TheseOnesDo = rw.rw_obj(self.TheseOnesDo, ENC, 5)
self.StrongEncounter = rw.rw_obj(self.StrongEncounter, ENC, 5)
self.StrongPinchEncounter = rw.rw_obj(self.StrongPinchEncounter, ENC, 5)
self.ReaperEncounter = rw.rw_obj(self.ReaperEncounter, ENC, 1)
self.Padding = rw.rw_uint32(self.Padding)
if so. Heres the funny error.
File "E:\Pictures\Rune\NewParser\ParserPlayground-main\Formats\exbip\Descriptors\Object.py", line 34, in construct
out.append(binary_target.rw_obj(value, constructor, *args, **kwargs))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "E:\Pictures\Rune\NewParser\ParserPlayground-main\Formats\exbip\Descriptors\Object.py", line 12, in construct
value.rw_hook(binary_target, *args, **kwargs)
File "E:\Pictures\Rune\NewParser\ParserPlayground-main\Formats\FTD.py", line 507, in rw_hook
self.UnusedSlot = rw.rw_uint8(self.UnusedSlot, 7)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "E:\Pictures\Rune\NewParser\ParserPlayground-main\Formats\exbip\Descriptors\Primitives.py", line 13, in construct
return binary_target._rw_typed(value, typecode, elem_size, endianness)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "E:\Pictures\Rune\NewParser\ParserPlayground-main\Formats\exbip\Descriptors\Core.py", line 27, in construct
return struct.unpack(endianness + typecode, serialized_value)[0]
~~~~^
TypeError: unsupported operand type(s) for +: 'int' and 'str'
You have to add an s to the end of every rw if you're making it an array! So rw_uint8s, rw_objs, etc.
ahhh
makes sense
hmm, another assertion error. I should have the right amount of fields.
value.rw_hook(binary_target, *args, **kwargs)
File "E:\Pictures\Rune\NewParser\ParserPlayground-main\Formats\FTD.py", line 80, in rw_hook
self.Entries[i] = rw.rw_obj(self.Entries[i], FtdList, filename)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "E:\Pictures\Rune\NewParser\ParserPlayground-main\Formats\exbip\Descriptors\Object.py", line 12, in construct
value.rw_hook(binary_target, *args, **kwargs)
File "E:\Pictures\Rune\NewParser\ParserPlayground-main\Formats\FTD.py", line 175, in rw_hook
assert rw.tell() == self.DataSize
^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError
sorry about all of the annoying questions and errors.
Missing TreasureEncounter, I think.
im going insane
โจCodingโจ
do you have to do something specific with the stringify?
self.NormalEncounter.stringify() should be like this right?
Ooh, well, now that it's an array, you'd have to do it for each ENC element in the array...
Probably need to make this a multiline print to make it not an insanely long line ๐
I did that, it's 'list' object has no attribute 'stringify'
Right, because each one is a list of ENCs, not a single ENC.
So it'd have to be like ", ".join(enc.stringify() for enc in self.NormalEncounter)
self.NormalEncounter.join(enc.stringify() ?
No, just like I did above. That stringifies each ENC element of self.NormalEncounter and then combines them with commas.
so then 7 copies of .join(enc.stringify() tacked on?
sorry, trying to wrap my head around it
Yep, exactly!
An insalely long string ๐
We can come up with alternatives once you get it working, lol.
so then it would be self.NormalEncounter.stringify() then the 7 copies? So like self.NormalEncounter.stringify(), .join(enc.stringify(), .join(enc.stringify(), .join(enc.stringify().join(enc.stringify(), .join(enc.stringify(), .join(enc.stringify(), .join(enc.stringify()
sorry if this is wrong im a persona fan so I can't read.
โ๏ธ If you use this version, it does all 7 in this one go. That's what the for loop does. It goes over them all!
But this would technically work if I did it right?
Nope, can't stringify NormalEncounter directly at all ๐
this is so sad.
You could do self.NormalEncounter[0].stringify(), self.NormalEncounter[1].stringify() ... self.NormalEncounter[6].stringify()
thats a lot better
oh, that was what I was just looking at.
so then for the non ENC ones. It would be self.AmbushList[0] ?
That'll give you the first element, yeah.
self.Entries[i].pretty_print(indent_level=indent_level+1)
File "E:\Pictures\Rune\NewParser\ParserPlayground-main\Formats\FTD.py", line 144, in pretty_print
print("{}({}) {}".format(" "*indent_level, i, self.Entries[i].stringify()))
^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "E:\Pictures\Rune\NewParser\ParserPlayground-main\Formats\FTD.py", line 522, in stringify
self.field_0, self.UnusedSlot, self.AmbushList, self.NormalEncounter[13].stringify(),
~~~~~~~~~~~~~~~~~~~~^^^^
IndexError: list index out of range
The indexes go up to the sizes minus 1! So if it's size 13, it's 0-12.
man, this python sure is picky
๐
finally, the snake charming worked
Giving you the longest lines of text known to man? ๐
Beautiful....
if it works it works
def __init__(self):
self.field_0 = None
self.UnusedSlot = None
self.AmbushList = None
self.NormalEncounter = None
self.NormalPinchEncounter = None
self.WhatDo = None
self.TreasureEncounter = None
self.TheseOnesDo = None
self.StrongEncounter = None
self.StrongPinchEncounter = None
self.ReaperEncounter = None
self.Padding = None
self.field_0 = rw.rw_uint8(self.field_0)
self.UnusedSlot = rw.rw_uint8s(self.UnusedSlot, 7)
self.AmbushList = rw.rw_uint16s(self.AmbushList, 8)
self.NormalEncounter = rw.rw_objs(self.NormalEncounter, ENC, 13)
self.NormalPinchEncounter = rw.rw_objs(self.NormalPinchEncounter, ENC, 5)
self.WhatDo = rw.rw_objs(self.WhatDo, ENC, 5)
self.TheseOnesDo = rw.rw_objs(self.TheseOnesDo, ENC, 5)
self.TreasureEncounter = rw.rw_objs(self.TreasureEncounter, ENC, 5)
self.StrongEncounter = rw.rw_objs(self.StrongEncounter, ENC, 5)
self.StrongPinchEncounter = rw.rw_objs(self.StrongPinchEncounter, ENC, 5)
self.ReaperEncounter = rw.rw_objs(self.ReaperEncounter, ENC, 1)
self.Padding = rw.rw_uint32(self.Padding)
def stringify(self):
return "field 0: {}, Unused Slot: {}, Ambush List: {}, Normal Encounter: {}, Normal Pinch Encounter: {}, What Do: {}, These Ones Do: {}, Strong Encounter: {}, Treasure Encounter: {}, Strong Pinch Encounter: {}, Reaper Encounter: {}, Padding: {}".format(
self.field_0, self.UnusedSlot, self.AmbushList, self.NormalEncounter[12].stringify(),
self.NormalPinchEncounter[4].stringify(), self.WhatDo[4].stringify(),
self.StrongPinchEncounter[4].stringify(), self.ReaperEncounter[0].stringify(),
self.TheseOnesDo[4].stringify(), self.StrongEncounter[4].stringify(), self.TreasureEncounter[4].stringify(), self.Padding,
)```
def __init__(self):
self.EncounterID = None
self.Weight = None
self.Padding = None
def __rw_hook__(self, rw):
self.EncounterID = rw.rw_uint16(self.EncounterID)
self.Weight = rw.rw_uint8(self.Weight)
self.Padding = rw.rw_uint8(self.Padding)
def stringify(self):
return "Encounter ID: {}, Weight: {}, Padding: {}".format(
self.EncounterID, self.Weight, self.Padding,
)```
that should be it.
Nice ๐
we still have a few more to go through but I'm getting more 'confident'. Hopefully I can interpret every single non impossible one soon. Then we can move on to the other filetypes.
time to sleep. I shall continue tomorrow.
๐ซก Goodnight!
Pushed the new table formats! Including some polish to how DATENCOUNTPACK gets printed, heheh.
It seems like a lot of these DNG tables treat the "dungeons" as a set of 12, but I have yet to figure out if there's a direct list of which is which, or corresponding fields.... FLDDNGPLACENO doesn't look like it, though I could be wrong....
Akechi palace confirmed..............
I wonder how hard it would be to make a new palace?
Also I see what you mean about the polish. I see why you made the stringify code look like that but why exactly are the asserts there?
oh man, I looked at FLDLMAPFARE and yesterday I had no idea how to interpret it. But looking at it today now I do. Thats so cool.
assertion error. But what field am I missing? hmm
there we go
okay I figured out my issue.
I was looking at the P5 version of the FTD rather than the P5R version
def __init__(self):
self.travelCost = None
def __rw_hook__(self, rw, datasize):
self.travelCost = rw.rw_int32s(self.travelCost, 36)
def stringify(self):
return "Travel cost to Lmap Destination: {}".format(
self.travelCost,
)```
heres the FLDLMAPFARE code.
def __init__(self):
self.DoorObjMajorId = None
self.DoorObjMinorId = None
self.DungeonAcbCueId = None
self.SecondaryCueId = None
def __rw_hook__(self, rw, datasize):
self.DoorObjMajorId = rw.rw_uint16(self.DoorObjMajorId)
self.DoorObjMinorId = rw.rw_uint16(self.DoorObjMinorId)
self.DungeonAcbCueId = rw.rw_int32(self.DungeonAcbCueId)
self.SecondaryCueId = rw.rw_int32(self.SecondaryCueId)
def stringify(self):
return "DoorObj Major Id: {}, DoorObj Minor Id: {}, DungeonAcb Cue Id: {}, Secondary Cue Id: {}".format(
self.DoorObjMajorId, self.DoorObjMinorId,
self.DungeonAcbCueId, self.SecondaryCueId,
)```
FLDDOORSE done.
def __init__(self):
self.fldObjMajorId = None
self.fldObjMinorId = None
self.AnimationId = None
self.CueId = None
def __rw_hook__(self, rw, datasize):
self.fldObjMajorId = rw.rw_int16(self.fldObjMajorId)
self.fldObjMinorId = rw.rw_int16(self.fldObjMinorId)
self.AnimationId = rw.rw_uint32(self.AnimationId)
self.CueId = rw.rw_uint32(self.CueId)
def stringify(self):
return "fldObj Major Id: {}, fldObj Minor Id: {}, Animation Id: {}, Cue Id: {}".format(
self.fldObjMajorId, self.fldObjMinorId,
self.AnimationId, self.CueId,
)```
FLDGIMMICKSE is also done
Prolific! 
Asserts don't add anything to the main functionality, but I like to add them to track "rules" a format follows. That mostly ends up as checking that padding/reserve slots are all zero, but in this case I also added a check to see that the encounter category probabilities add up to 100.
(Probabilities/weights, that is.)
I see, that makes sense.
im almost done with FLDDOORANIM then im gonna count up all the ones that are left.
def __init__(self):
self.DoorObjMajorId = None
self.DoorObjMinorId = None
self.FldAnimGapId = None
self.playerXOffset = None
self.playerYOffset = None
self.playerZOffset = None
self.playerXOffset2 = None
self.playerYOffset2 = None
self.playerZOffset2 = None
self.UNKNOWN0 = None
def __rw_hook__(self, rw, datasize):
self.DoorObjMajorId = rw.rw_uint16(self.DoorObjMajorId)
self.DoorObjMinorId = rw.rw_uint16(self.DoorObjMinorId)
self.FldAnimGapId = rw.rw_uint16(self.FldAnimGapId)
self.playerXOffset = rw.rw_int16(self.playerXOffset)
self.playerYOffset = rw.rw_int16(self.playerYOffset)
self.playerZOffset = rw.rw_int16(self.playerZOffset)
self.playerXOffset2 = rw.rw_int16(self.playerXOffset2)
self.playerYOffset2 = rw.rw_int16(self.playerYOffset2)
self.playerZOffset2 = rw.rw_int16(self.playerZOffset2)
self.UNKNOWN0 = rw.rw_int16(self.UNKNOWN0)
def stringify(self):
return "DoorObjMajorId: {}, DoorObjMinorId: {}, FldAnimGapId: {}, playerXOffset: {}, playerYOffset: {}, playerZOffset: {}, playerXOffset2: {}, playerYOffset2: {}, playerZOffset2: {}, UNKNOWN0: {}".format(
self.DoorObjMajorId, self.DoorObjMinorId, self.FldAnimGapId,
self.playerXOffset, self.playerYOffset, self.playerZOffset, self.playerXOffset2,
self.playerYOffset2, self.playerZOffset2, self.UNKNOWN0,
)```
FLDDOORANIM is now done.
time to count
FLDOBJFLAG
DATTBOXRND
DATTBOX
FLDWHOLEMAPTABLEDNG
FLDLMAPSTATION
cmmFormat
cmmFunctionTable
cmmFunctionInfoTable
cmmHelp
cmpPsCoopSkillTable
fclItemLineUpTable
fclItemLineUpAccTable
fclCombElectItemTable
FLDMODELSE
FLDWIREANIMDATA(impossible currently)
im not really sure about the tables. Since idk if you've already implemented something similar to them just with a different name.
If you don't see their names in the FTD file, I don't have anything for them.
alright cool, was just making sure.
so for the tables. How do i interpret them?
ItemType Category : 4<format = hex, name = "Item Category">;
short ItemId : 12<name = "Consumable ID">;
readString=Str("%s %d", EnumToString(Category), ItemId);```
for example
Hmmmm, I'm confused about this one in the template, because it looks like it's expecting one or two int16s, but I only see two bytes per entry in the file. Do you see the same thing, if you just print the table generically? Just two numbers per line?
i'll try it. I was working on FLDMODELSE since thats the last one I can read without asking you questions. But i'll check it rq
sorry, had to do a couple things. Where are the table stuff like fclItemLineUpTable stored?
They're inside INIT/FCLTABLE.BIN.
ah
doesn't seem like it recognizes it. This is what I have for the code so maybe it's just a me issue.
def __init__(self):
self.ItemCategory = None
self.ConsumableID = None
self.RESERVE = None
def __rw_hook__(self, rw, datasize):
self.ItemCategory = rw.rw_int16(self.ItemCategory)
self.ConsumableID = rw.rw_int16(self.ConsumableID)
self.RESERVE = rw.rw_uint32(self.RESERVE)
assert self.RESERVE == 0
def stringify(self):
return "Item Category: {}, ConsumableID: {}".format(
self.ItemCategory, self.ConsumableID
)```
it also does 2 numbers per line forgot to say mb
(0) array('B', [48, 2])
(1) array('B', [48, 3])
(2) array('B', [48, 18])
(3) array('B', [48, 19])
(4) array('B', [48, 20])
(5) array('B', [48, 22])
(6) array('B', [48, 61])
(7) array('B', [49, 97])
(8) array('B', [0, 0])
(9) array('B', [48, 2])
(10) array('B', [48, 3])
Okay, yeah, weird. That doesn't fit the template whatsoever, either for P5 or P5R ๐ฅด
do we just skip them for now and do the rest of the non table FTDs?
Yeah, I'd just skip for now. They're not field-related, anyway.
def __init__(self):
self.fldObjMajorId = None
self.fldObjMinorId = None
self.AnimationId = None
self.UNKNOWN1 = None
self.UNKNOWN2 = None
self.UNKNOWN3 = None
self.UNKNOWN4 = None
self.UNKNOWN5 = None
self.DungeonAcbCueId = None
self.UNKNOWN6 = None
def __rw_hook__(self, rw, datasize):
self.fldObjMajorId = rw.rw_uint16(self.fldObjMajorId)
self.fldObjMinorId = rw.rw_uint16(self.fldObjMinorId)
self.AnimationId = rw.rw_uint16(self.AnimationId)
self.UNKNOWN1 = rw.rw_int16(self.UNKNOWN1)
self.UNKNOWN2 = rw.rw_int16(self.UNKNOWN2)
self.UNKNOWN3 = rw.rw_int16(self.UNKNOWN3)
self.UNKNOWN4 = rw.rw_int16(self.UNKNOWN4)
self.UNKNOWN5 = rw.rw_int16(self.UNKNOWN5, 4)
self.DungeonAcbCueId = rw.rw_int16(self.DungeonAcbCueId)
self.UNKNOWN6 = rw.rw_int16(self.UNKNOWN6, 7)
def stringify(self):
return "fldObjMajorId: {}, DoorObjMinorId: {}, AnimationId: {}, UNKNOWN 1: {}, UNKNOWN 2: {}, UNKNOWN 3: {}, UNKNOWN 4: {}, UNKNOWN 5: {}, DungeonAcb Cue Id: {}, UNKNOWN 6: {}".format(
self.fldObjMajorId, self.fldObjMinorId, self.AnimationId,
self.UNKNOWN1, self.UNKNOWN2, self.UNKNOWN3, self.UNKNOWN4,
self.UNKNOWN5, self.DungeonAcbCueId, self.UNKNOWN6,
)```
FLDMODELSE is done.
so the next one I'm looking at is FLDLMAPSTATION. And all it has is this. How exactly do I interpret this?
fldMapTable Data;```
oh is that a struct?
okay cool it is
fldMapEntry entry[964 / 48] <optimize = false> is this more print?
Looks like it's a list of... 964/48 entries of type fldMapEntry?
That's about 20, but it's not an even division, so that's a bit weird.
so then should I interpret it as a array for entry? it is quite puzzling.
I could just try stuff to see what happens. What would entrys int/u be?
would I get an assertion from wrong int/u or is it only because it needs more or less fields?
I... hm..... it could be either, I think.
My approach here would be to treat the table as generic and print out the datasize as a point of reference.
That should help figure out the right counts.
Lol, yeah, that's why you may want to add a print(datasize) to the rw temporarily just so you don't have to count manually.
how would I add that? Just tack it on like this?
1124
1124
1124
1124
1124
1124
1124
1124
1124
1124
1124
Traceback (most recent call last):
File "<frozen runpy>", line 198, in _run_module_as_main
File "<frozen runpy>", line 88, in _run_code
File "E:\Pictures\Rune\NewParser\ParserPlayground-main\Scripts\PrintTable.py", line 19, in <module>
main()
File "E:\Pictures\Rune\NewParser\ParserPlayground-main\Scripts\PrintTable.py", line 14, in main
table.read(args.table_path, filename=(args.table_name if args.table_name else Path(args.table_path).stem))
File "E:\Pictures\Rune\NewParser\ParserPlayground-main\Formats\exbip\Serializable\Traits.py", line 6, in read
rw.rw_obj(self, *args, **kwargs)
File "E:\Pictures\Rune\NewParser\ParserPlayground-main\Formats\exbip\Descriptors\Object.py", line 12, in construct
value.rw_hook(binary_target, *args, **kwargs)
File "E:\Pictures\Rune\NewParser\ParserPlayground-main\Formats\FTD.py", line 80, in rw_hook
self.Entries[i] = rw.rw_obj(self.Entries[i], FtdList, filename)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "E:\Pictures\Rune\NewParser\ParserPlayground-main\Formats\exbip\Descriptors\Object.py", line 12, in construct
value.rw_hook(binary_target, *args, **kwargs)
File "E:\Pictures\Rune\NewParser\ParserPlayground-main\Formats\FTD.py", line 175, in rw_hook
assert rw.tell() == self.DataSize
^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError
class FLDWHOLEMAPTABLEDNG(Serializable):
def __init__(self):
self.entry = None
self.Field3C0 = None
self.Field3C2 = None
def __rw_hook__(self, rw, datasize):
self.entry = rw.rw_uint16s(self.entry, 20)
self.Field3C0 = rw.rw_uint16(self.Field3C0)
self.Field3C2 = rw.rw_uint16(self.Field3C2)
print(datasize)
def stringify(self):
return "entry: {}, Field3C0: {}, Field3C2: {}".format(
self.entry, self.Field3C0, self.Field3C2,
)
Okie doke, let me take a crack at it....
I'll work on another one then.
How would I go about interpreting this?
fldObj Objects[10];```
An array of 10 fldObjs?
oh okay it's a struct I think
{
u32 Flag <format=hex>;
TboxIndex Items[ 6 ];
} fldObj <size=0x10, optimize=false, read=fldObj_Read>;
these last couple FTDs are getting pretty difficult
A lot of nested structs and arrays just to really confuse you ๐
what would be the int/u for these fields that don't have one like TboxIndex?
TBoxIndex is either a struct or an enum, so it's a matter of figuring that out first.
so entry is aswell?
truly an atlus creation
it is not im afriad
I don't think so
the only other mention of it is in DATTBOXRND
It's defined here: https://github.com/Secre-C/010-Editor-Templates/blob/master/templates/Persona_5_ENUMS_ITEM.bt
A u16 enum.
ohhhhh
tunnel vision ;-;
mb
so then, would I have to add in all of the entries for TBoxIndex from the enum list into the py script?
I assume so but I want to make sure
You could, but you could also just leave it as a uint16 for now, lol.
Moving that huge enum sounds annoying ๐
true
hmm, so then since it's inside the fldobj. Would I have fldobj then TboxIndex and Flag? Or something similar to that?
It would be another thing like ENC, yeah.
ahh, that makes sense
oh yeah sorry I haven't done much work today. I had to deal with a consultation which took a few hours and after that I just felt really lethargic. I should be back to really start tomorrow though. Did you ever take a crack at that weird FTD?
Yep, just updated it! The template's enum for travel types was super incomplete, so I had to make some educated guesses in filling it in.
I added some other stuff, too -- including a script for validating that the FTD parser works across all tables, and a parser for the weather schedule.
oh nice, thats awesome ajda. So then all we need to do is finish up those last couple FTDs then we'll be done. I'll get started on it again after I stop being tired.
man it's really hard to wrap my brain around FLDOBJFLAG but I think I get it.
oh right, you changed the enc code.
me when assertion
thats not an assertion
File "E:\Pictures\Rune\NewParser\ParserPlayground-main\Formats\exbip\Descriptors\Object.py", line 34, in construct
out.append(binary_target.rw_obj(value, constructor, *args, **kwargs))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "E:\Pictures\Rune\NewParser\ParserPlayground-main\Formats\exbip\Descriptors\Object.py", line 12, in construct
value.rw_hook(binary_target, *args, **kwargs)
File "E:\Pictures\Rune\NewParser\ParserPlayground-main\Formats\FTD.py", line 810, in rw_hook
self.fldObj = rw.rw_objs(self.fldObj, fldobj, 10)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "E:\Pictures\Rune\NewParser\ParserPlayground-main\Formats\exbip\Descriptors\Object.py", line 34, in construct
out.append(binary_target.rw_obj(value, constructor, *args, **kwargs))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "E:\Pictures\Rune\NewParser\ParserPlayground-main\Formats\exbip\Descriptors\Object.py", line 12, in construct
value.rw_hook(binary_target, *args, **kwargs)
File "E:\Pictures\Rune\NewParser\ParserPlayground-main\Formats\FTD.py", line 897, in rw_hook
self.TboxIndex = rw.rw_objs(self.TboxIndex, 6)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: ObjectsDescriptor.construct() missing 1 required positional argument: 'shape'
The name of the object/class needs to be in there as the second argument!
So you have to even though it's the 'ENC' part of the code?
That's just how rw_objs works. If you're using it, you have to specify what type the objects are.
i see, so then it would be like this?
class fldobj(Serializable):
def __init__(self):
self.TboxIndex = None
self.Flag = None
self.RESERVE = None
def __rw_hook__(self, rw):
self.TboxIndex = rw.rw_objs(self.TboxIndex, fldobj, 6)
self.Flag = rw.rw_uint32(self.Flag)
self.RESERVE = rw.rw_uint8(self.RESERVE)
assert self.RESERVE == 0
def stringify(self):
return "ID = {} ({}%)".format(
self.TboxIndex[5], self.Flag
)
Oh, uh... no, there would probably need to be another class for whateer TboxIndex is. Not the same one you're in.
Let me refresh my memory....
yeah thats what I thought. So can enums be added into this like any other class?
Okay, right, we wanted to forego the enum for now because it's too big to bother with. So then enums aren't treated quite the same way as other objects. They're fancy wrappers for regular numbers. So you should just use self.TboxIndex = rw.rw_uint16s(self.TboxIndex, 6).
If we want to add the actual enum in the future, that would get used only in the stringify.
Okay, that makes so much more sense. Sorry, my mind has had a really hard time figuring out this cause theres so many nested things.
now were back to assertions. I wonder if this has a similar issue to where it isn't documented completely?
Looking at the template, it looks like Flag should get read before TboxIndex, and there should be no RESERVE.
Im not really sure why I thought that didn't matter. But good thing I know now.
perfect. it works now
def __init__(self):
self.fldObj = None
def __rw_hook__(self, rw, datasize):
self.fldObj = rw.rw_objs(self.fldObj, fldobj, 10)
def stringify(self):
return "Shadow ID: {}".format(
self.fldObj[9],
)```
Main code.
def __init__(self):
self.Flag = None
self.TboxIndex = None
def __rw_hook__(self, rw):
self.Flag = rw.rw_uint32(self.Flag)
self.TboxIndex = rw.rw_uint16s(self.TboxIndex, 6)
def stringify(self):
return "ID = {} ({}%)".format(
self.Flag, self.TboxIndex[5],
)```
fldobj 'ENC' code.
While were on the topic. I know you did your stringify ENC code differently. Though for that it was multiple ENCs within one FTD. How exactly do you go about making it work for just one ENC ? Do I just have to get rid of the ifs and leave the outside code?
The ifs...?
Oh, my printing shenanigans, lol.
Hm, still not sure exactly what you mean. ENC has its own stringify, so if that were all you had, then you would just do self.MyEncObj.stringify() and be done with it. The above class is specifically a bunch of lists of ENC objects, so those for loops go through and stringify each one in each list individually.
I was just curious, since it changed quite a bit from the original stringify code. And I thought I had just done it inefficiently or something similar.
thats good to know though.
You were only stringifying one element from each list, rather than all of them.
I also added a bunch of extra logic to make it print nicely on more than one line for readability.
I see, that makes sense.
in any case. We have 3 more left then we'll be done with FTDs
Done with all, or just the 010-documented ones?
I've been pondering making a spreadsheet or a page on Amicitia to document it all. Both to see what all is left but also because I don't know that there's an actual source that explains what all the tables are for....
We could, I don't mind going through the files to see if any were missed or whatnot.
Documentation would ultimately go beyond just the field tables, but for the current focus, yeah, that'd be a good reference to have.
alright, I'll take a look around.
I asked one of my dev members to make a little spread sheet (since she's good at them) that I'll put all the FTDs I find inside. So that it doesn't clog up this chat.
Since most of the 010 documented stuff is done (and im working on making the list which is going well). How 'easy' is it to start interpreting new file types?
alright, update on the FTDs. Just finished jotting all of the FTDs that were in the folders. This doesn't include any of the nested ones (liked those tables) but other than that it should be comprehensive. Gonna cross reference with our documentation to see which ones haven't been documented/parsed
Sorry, had some other stuff to do so didn't really get to check up on what ones we've done. Though I can assume we still have to do quite a bit. Which is a shame since I have to get through those other filetypes as well. I still have a bit of time so i'll take a bit of a skim through to see any others I missed.
We can prioritize! And I still have to think about a good way to make it so you can quickly plug in values to test them.
thats true
im not sure how possible it is. But maybe you could simply have it print the table like how it works normally. Then after editing it reconvert it back into the file it was?
Yeah, I'll think about whether that'd be quicker/easier than setting up something programmatic ๐ค
Would probably be cool to have YAML deserialization of FTDs, anyway.
Alright, honestly I don't mind if the way is complicated. It just needs to be able to be editable without having to code it in like how you did with the field name.
yeah that'd be cool
skimmed through the doc. I definitely don't have all of them but I checked off the ones we did off the top of my head. I'll take a more deep dive tomorrow.
I've almost got all of the FTDs marked on the doc. Just a few more to go
yeah I think i've got everything now
...And I went ahead and added some of the info to new FTD pages on Amicitia!
Hm, well we definitely got lost in the weeds a bit here, lmao.
This is ultimately about making new fields, so maybe we edit a few more of the tables to see what we can get to work and call it a thread?
I'm determined to try that field BGM one again.... I think it's just a matter of adding the new entry first rather than last ๐ค
Which ones have you used edits of so far?
It happens but it was a good thing we did.
let me check rq
Since I transitioned to using the accessway. I'm only using the FLDPLACENAME one to change the name.
But I guess now that I know it's possible to change stuff I could probably move back to using that unused field.
Kinda. I still need to get to those other filetypes such as the crowd ones and whatnot.
I mean now that we both know a fair bit more (and documented it) im sure we could get it working.
True, I was considering that still to be a new field thing, but maybe the crowd files deserve their own thread ๐ค
Thats true. Breaking into new filetypes needs to be a thread.
were up to 1512 messages in this thread lol so yeah probably needs to be a new thread
we'll do the BGM thing then I'll make a new thread.
I might be stupid
I was just sitting here thinking about the music thing. And I just realized.
I had my music volume turned down
so let me test that old BGM file rq
LOL. I still suspect it won't do anything.
If the file works how I expect it to (and isn't secretly useless and hardcoded-over
)
That's the default music, so I think that would have happened without my change.
yeah it isn't the file
good to get that done after such a long time though.
Honestly because of testing I just completely forget the music ever exists sometimes.
Tokyo Daylight is basically the final "we don't know what to play here" field BGM option in the file, lol. To actually make changes, I think I'd need to add the new entry at the top of the table, not at the bottom.
Makes sense, otherwise you'd drive yourself up the wall with all the repetition....
Thats interesting. I was expecting it to just have no music play. But oh well.
Nope, if you look at the table, there's an entry at the bottom with a bunch of -1 entries to catch all remaining cases. That's Tokyo Daylight ๐
Ah i see,
In any case. Can't I just reuse your code from when you were trying to add a name entry for the BGM FTD?
Though im not sure how to make it go to the top instead of the bottom
I don't think I put that code in the repo, but basically, you'd need to make a new entry and insert it at the 0th position in the table rather than appending it at the end.
It'll be super quick once I'm at my computer, but I'll add it as a script so you can see what I mean.
table1.Entries[-1].Data = b"The Coolest Place\x00"```
So then just make this 0 instead of -1? (you've had it in the repo ever since you put it up)
That's for the field name table!
And that line changes the entry after it already exists. We need to add the new entry, first. But then, yes, it would be at 0 instead of -1.
This would be an entry in the FLDBGMCND table, or whatever it's called.
I know : )
Cool, cool.
oh man I forgot how complicated FLDBGMCND is. You win this time... Since I need to make 7ft tall lavenza.
A noble goal unto itself.
very true
Okie doke, added a script modifying the BGM table! And that includes a MODDED_FLDBGMCND.FTD in the repo, but I'll also just attach it here:
You can see its contents with python -m Scripts.PrintTable --table-path /path/to/MODDED_FLDBGMCND.FTD --table-name FLDBGMCND.
(I made sure to add entries for both F001_006 and F015_001 so that, if this is going to work at all, it should do so wherever you have the field currently.)
Since thats done I'm gonna make a new thread and we can focus on breaking into those other map files.
hmm, do I make a https://discord.com/channels/746211612981198989/1020628359610376232 thread or a modder help thread? I guess it would be more suited to a project thread.
I think a projects thread makes sense at this point! (too late anyway though lol)
Oh, did it play the goofy happy music I selected? I forget the track name, haha.
I'm always* right. *(sometimes)
Yeah you did a great job.
Well you've been doing an amazing job consistently but yeah
Now we can put a bow on this thread... and maybe pick a different track later ๐
