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

fix: Dynamic region loading types #617

Merged
merged 3 commits into from
Feb 24, 2025

Conversation

titandino
Copy link
Contributor

Note: This is based off research in the #727 refactor but glancing at the #667 client the number of types and their mappings are backwards compatible.

Packet decoding for the various enum types:

if (type == 1) {
  this.currentLoadingType = RegionLoadingType.INSTANCE;
} else if (type == 2) {
  this.currentLoadingType = RegionLoadingType.INSTANCE_NO_ENTITY_RESET;
} else if (type == 3) {
  this.currentLoadingType = RegionLoadingType.INSTANCE_LARGE;
} else if (type == 4) {
  this.currentLoadingType = RegionLoadingType.INSTANCE_LARGE_NO_RELOAD_UPDATEZONES;
}

Type explanations

INSTANCE (STANDARD):

This one operates essentially identical to the standard buildMapRegion logic other than supporting the customized zones so nothing really to note about the logic.

INSTANCE_NO_ENTITY_RESET (STANDARD_NO_ENTITY_RESET)

Only one difference from the standard from what I can see regarding this one. Every time a region build comes in, the client will reset entities using the following function. As you can see, the NO_ENTITY_RESET and NO_RELOAD_UPDATE_ZONE variants are excluded from this logic so the entities don't need to be reinitialized on this type.

public void resetNonCutsceneEntities() {
  if (this.currentLoadingType != RegionLoadingType.CUTSCENE && this.previousLoadingType != RegionLoadingType.CUTSCENE) {
    if (this.currentLoadingType == RegionLoadingType.INSTANCE || this.currentLoadingType == RegionLoadingType.INSTANCE_LARGE || this.previousLoadingType != this.currentLoadingType &&
            		(this.currentLoadingType == RegionLoadingType.STANDARD || this.previousLoadingType == RegionLoadingType.STANDARD)) {
      client.NPC_UPDATE_INDEX = 0;
      client.npcCount = 0;
      client.NPC_MAP.clear();
    }
    this.previousLoadingType = this.currentLoadingType;
  }
}

INSTANCE_LARGE (LARGE)

The reason I call the remaining two types LARGE is due to how they differ with the following boolean in their enums in the client. The last 2 types contain a boolean set to true that I identify as "ignoreUpdateZoneSizeLimitations".

public static final RegionLoadingType CUTSCENE = new RegionLoadingType(true, false);
public static final RegionLoadingType STANDARD = new RegionLoadingType(false, false);
public static final RegionLoadingType INSTANCE = new RegionLoadingType(true, false);
public static final RegionLoadingType INSTANCE_NO_ENTITY_RESET = new RegionLoadingType(true, false);
public static final RegionLoadingType INSTANCE_LARGE = new RegionLoadingType(true, true);
public static final RegionLoadingType INSTANCE_LARGE_NO_RELOAD_UPDATEZONES = new RegionLoadingType(true, true);

Here is how that second boolean is used throughout the UpdateZone packets:

boolean withinSceneRange = localX >= 0 && localY >= 0 && localX < Resource.mapRegion.getSceneSizeX() && localY < Resource.mapRegion.getSceneSizeY();
if (withinSceneRange || Resource.mapRegion.getLoadingType().ignoreUpdateZoneSizeLimitations()) {
  GroundItemUpdating.addGroundItem(UpdateZonePacket.UPDATE_ZONE_PLANE, x, y, new GroundItemNode(id, amount));
  if (withinSceneRange)
    GroundItemUpdating.removeGroundItem(UpdateZonePacket.UPDATE_ZONE_PLANE, localX, localY);
}

It seems to override the check for whether the update should be rendered to the player or not which is why I called it LARGE.

INSTANCE_LARGE_NO_RELOAD_UPDATEZONES (LARGE_NO_RELOAD_UPDATEZONES)

This one retains all the same logic as the above LARGE type except has extra logic that prevents update zone items from being deleted. My guess is that this mode is used for areas like dungeoneering to save a large amount of bandwidth not having to reload all objs, locs, loc changes, etc every time a room is opened.

for (LocAction action = (LocAction)LocAction.pendingRemovals.getFirst(); action != null; action = (LocAction)LocAction.pendingRemovals.next()) {
  action.x -= deltaX;
  action.y -= deltaY;
  if (this.currentLoadingType != RegionLoadingType.INSTANCE_LARGE_NO_RELOAD_UPDATEZONES && (action.x < 0 || action.y < 0 || action.x >= this.sceneSizeX || action.y >= this.sceneSizeY))
    action.unlink();
}
for (LocAction action = (LocAction)LocAction.pendingCustomizations.getFirst(); action != null; action = (LocAction)LocAction.pendingCustomizations.next()) {
  action.x -= deltaX;
  action.y -= deltaY;
  if (this.currentLoadingType != RegionLoadingType.INSTANCE_LARGE_NO_RELOAD_UPDATEZONES && (action.x < 0 || action.y < 0 || action.x >= this.sceneSizeX || action.y >= this.sceneSizeY))
    action.unlink();
}
for (GroundItemWrapper groundItems = client.GROUND_ITEMS.first(); groundItems != null; groundItems = client.GROUND_ITEMS.next()) {
  int rotation = (int)(groundItems.pointer >> 28 & 0x3L);
  int sceneX = (int)(groundItems.pointer & 0x3fffL);
  int nodeSizeX = sceneX - this.baseCoordinates.x;
  int sceneY = (int)(groundItems.pointer >> 14 & 0x3fffL);
  int nodeSizeY = sceneY - this.baseCoordinates.y;
  if (this.scene != null) {
    if (nodeSizeX >= 0 && nodeSizeY >= 0 && nodeSizeX < this.sceneSizeX && nodeSizeY < this.sceneSizeY && nodeSizeX < this.scene.sceneDimensionX && nodeSizeY < this.scene.sceneDimensionY) {
      if (this.scene.tiles != null)
        this.scene.removeGroundItem(rotation, nodeSizeX, nodeSizeY);
    } else if (this.currentLoadingType != RegionLoadingType.INSTANCE_LARGE_NO_RELOAD_UPDATEZONES)
      groundItems.unlink();
  }
}

Copy link
Owner

@GregHib GregHib left a comment

Choose a reason for hiding this comment

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

Thank you for the information! Yes to my knowledge Dungeoneering was the first and only usage of instances that were bigger than 128x128/2x2 (at least in our revisions), so a lot of what you said about re-sending makes a lot of sense. Not clearing npcs also makes a lot of sense if you're changing the map without moving it's a nice optimisation to have

@GregHib GregHib merged commit af9f9ac into GregHib:main Feb 24, 2025
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants