Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Final Fantasy IV Free Enterprise: Add New Game #3897

Open
wants to merge 124 commits into
base: main
Choose a base branch
from

Conversation

Rosalie-A
Copy link
Contributor

What is this fixing or adding?

Implementing a fork of the open source version of the Final Fantasy IV: Free Enterprise randomizer as a new game in Archipelago

How was this tested?

Multiple private beta tests, some public ones, and single player games by both myself and contributing testers.

If this makes graphical changes, please attach screenshots.

AP 76989436201017225468 P1 Rosalie 2024-09-06 15 04 58

I'm putting this up as a draft for now. I still need to implement the WebWorld side of things and make proper documentation, but I figured I'd put this up now to get eyes on it for anything that needs to be changed (especially re: how I'm doing locations and rules) while I do that so I'm not just spinning my wheels when I want to work on this.

@Rosalie-A Rosalie-A marked this pull request as ready for review December 16, 2024 04:54
@Rosalie-A
Copy link
Contributor Author

Alrighty! Above comments have been addressed, more testing has happened, features are now where they need to be, and I'm confident everything is where it needs to be, functionally. I throw this into you all's overworked and underpaid hands to scrutinize at your leisure.

worlds/ff4fe/FreeEnterpriseForAP/FreeEnt/f4c/lark/LICENSE Outdated Show resolved Hide resolved
worlds/ff4fe/__init__.py Outdated Show resolved Hide resolved
Comment on lines +322 to +328
add_rule(self.get_location(location.name),
lambda state, tier=i: state.has_group("characters",
self.player,
FERules.logical_gating[tier]["characters"]) and
state.has_group("key_items",
self.player,
FERules.logical_gating[tier]["key_items"]))
Copy link
Collaborator

Choose a reason for hiding this comment

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

Will it create issues if someone adds a character to their start_inventory? Since the randomizer might see "ah, you have 2 of character A and 1 of character B, so you can get this check that requires 3 characters".
If that would be an issue, you should use has_group_unique instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Characters are not unique, so it's correct to not use has_group_unique. While it's true adding them to start inventory will cause the generator to think there are checks that are now "in logic", this will have little effect in practice:

  • characters in start inventory don't do anything (they're items for convenience's sake on the part of options and the code, not because they're equivalent to a Life potion or whatnot),
  • the gating logic is about spreading things out so at least some progression is located in less difficult areas.
  • that gating logic still requires key items as well, so nothing will change.
  • and even if there were changes, it makes things slightly more difficult for the player but doesn't render the game unbeatable.

"location_data": location_data.to_json(),
"item_data": ItemData.create_ap_item().to_json(),
"item_name": location.item.name,
"player_name": self.multiworld.player_name[location.item.player]
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
"player_name": self.multiworld.player_name[location.item.player]
"player_name": self.player_name,

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The placement dictionary includes names for all player's items, not just the FE player. I need to use the lookup to get those names too so text like "got Silver Sword for Player5" can be written into the game.

@Exempt-Medic Exempt-Medic added the waiting-on: peer-review Issue/PR has not been reviewed by enough people yet. label Dec 19, 2024
Copy link
Collaborator

@Silvris Silvris left a comment

Choose a reason for hiding this comment

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

Didn't make it through everything, but I'm out of time for review today.

inno_setup.iss Outdated Show resolved Hide resolved


class FF4FESettings(settings.Group):
class RomFile(settings.UserFilePath):
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
class RomFile(settings.UserFilePath):
class RomFile(settings.SNESRomPath):

Using this will automatically strip the header, so no additional hashes or manual removal of headers needed.

settings: typing.ClassVar[FF4FESettings]

web = FF4FEWebWorld()
topology_present = True
Copy link
Collaborator

Choose a reason for hiding this comment

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

AFAIK FE doesn't support any manner of entrance rando, so is this correct?

topology_present adds the region connections to the spoiler log. If they are always static, then there is no reason for this to be true.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I definitely maybe thought I had a good reason at one point, but mostly it let me confirm I set the regions and locations up correctly, so it can be removed, yeah. Will do so.

overworld.locations.append(new_location)

def create_item(self, item: str) -> FF4FEItem:
item_data: ItemData = [item_data for item_data in all_items if item_data.name == item].pop()
Copy link
Collaborator

Choose a reason for hiding this comment

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

Pretty sure this will result in a unhelpful exception if a player attempts to plando a nonsense item into FF4 (rather than telling the player that item does not exist for FF4).

Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
item_data: ItemData = [item_data for item_data in all_items if item_data.name == item].pop()
item_data: ItemData = next((item_data for item_data in all_items if item_data.name == item), None)
if not item_data:
raise Exception(f"{item} is not a valid item name for Final Fantasy 4 Free Enterprise")

One way to solve it, but probably not the best?

Comment on lines 185 to 188
restricted_character_allow_locations = list(set(character_locations) - set(locations.restricted_character_locations)).copy()
restricted_character_forbid_locations = list(set(character_locations) - set(restricted_character_allow_locations)).copy()
self.random.shuffle(restricted_character_allow_locations)
self.random.shuffle(restricted_character_forbid_locations)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
restricted_character_allow_locations = list(set(character_locations) - set(locations.restricted_character_locations)).copy()
restricted_character_forbid_locations = list(set(character_locations) - set(restricted_character_allow_locations)).copy()
self.random.shuffle(restricted_character_allow_locations)
self.random.shuffle(restricted_character_forbid_locations)
restricted_character_allow_locations = sorted(set(character_locations) - set(locations.restricted_character_locations))
restricted_character_forbid_locations = sorted(set(character_locations) - set(restricted_character_allow_locations))
self.random.shuffle(restricted_character_allow_locations)
self.random.shuffle(restricted_character_forbid_locations)

The order of restricted_character_allow_locations and restricted_character_forbid_locations are non-deterministic here. This will cause the outcome of the following shuffle to also be non-deterministic, leading to non-determinism in character placement.

Comment on lines 162 to 165
item_pool_result = create_itempool(locations.all_locations, self)
item_pool = item_pool_result[0]
self.chosen_character = item_pool_result[1]
self.second_character = item_pool_result[2]
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
item_pool_result = create_itempool(locations.all_locations, self)
item_pool = item_pool_result[0]
self.chosen_character = item_pool_result[1]
self.second_character = item_pool_result[2]
item_pool, self.chosen_character, self.second_character = create_itempool(locations.all_locations, self)

@@ -28,9 +29,12 @@
*multisave
*.archipelago
*.apsave
*.BIN
Copy link
Collaborator

Choose a reason for hiding this comment

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

The *.BIN ignore still needs to be added back in, even though you've included the necessary files.




root = tk.Tk()
Copy link
Collaborator

Choose a reason for hiding this comment

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

If this is intended to be included and not just a development file, not every system has tkinter so it would need to be checked.

Comment on lines 316 to 322
for sentinel in Rom.sentinel_addresses: # Defend against RAM initialized to 0xFF everywhere.
sentinel_data = await snes_read(ctx, sentinel, 1)
if sentinel_data is None:
return
sentinel_value = sentinel_data[0]
if sentinel_value == 0xFF:
return
Copy link
Collaborator

Choose a reason for hiding this comment

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

What would happen if ram is initialized to a value other than 0xFF? SNES9X initializes to 0x55, which would be valid here and trip victory if read fast enough.

Copy link
Collaborator

Choose a reason for hiding this comment

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

It should be noted that no particular value can be checked against here, since a real SNES has effectively junk data upon initialization.

"LunarPalace"
]

overworld_areas = [area for area in areas if area not in [*hook_areas, *underworld_areas, *moon_areas]]
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
overworld_areas = [area for area in areas if area not in [*hook_areas, *underworld_areas, *moon_areas]]
overworld_areas = [area for area in areas if area not in [*hook_areas, *underworld_areas, *moon_areas]]

@Exempt-Medic Exempt-Medic added the is: new game Pull requests for implementing new games into Archipelago. label Dec 24, 2024
@Rosalie-A
Copy link
Contributor Author

Updated according to PR comments. I could click "resolve" on all of them but not sure if that's desired.

I also removed the FEExternalDataLoader program in favor of the way I should have done it before a better way to implement external harp songs and z sprites without packaging derivative material with the project.

@qwint qwint removed affects: core Issues/PRs that touch core and may need additional validation. affects: webhost Issues/PRs that touch webhost and may need additional validation. labels Jan 15, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
is: new game Pull requests for implementing new games into Archipelago. waiting-on: peer-review Issue/PR has not been reviewed by enough people yet.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants