From 5208591c20d2056dbf254f7a961a5337ea0ba4d5 Mon Sep 17 00:00:00 2001 From: orkaboy Date: Mon, 28 Nov 2022 23:42:30 +0100 Subject: [PATCH 1/5] Initial implementation of sphere grid mapping to memory. --- memory/sphere_grid.py | 204 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 204 insertions(+) create mode 100644 memory/sphere_grid.py diff --git a/memory/sphere_grid.py b/memory/sphere_grid.py new file mode 100644 index 00000000..ab5b1b58 --- /dev/null +++ b/memory/sphere_grid.py @@ -0,0 +1,204 @@ +import logging +from enum import Enum + +from memory.main import read_val + +logger = logging.getLogger(__name__) + + +class SphereNodeType(Enum): + Lvl3Lock = 0x00 + Empty = 0x01 + Strength1 = 0x02 + Strength2 = 0x03 + Strength3 = 0x04 + Strength4 = 0x05 + Defense1 = 0x06 + Defense2 = 0x07 + Defense3 = 0x08 + Defense4 = 0x09 + Magic1 = 0x0A + Magic2 = 0x0B + Magic3 = 0x0C + Magic4 = 0x0D + MagicDef1 = 0x0E + MagicDef2 = 0x0F + MagicDef3 = 0x10 + MagicDef4 = 0x11 + Agility1 = 0x12 + Agility2 = 0x13 + Agility3 = 0x14 + Agility4 = 0x15 + Luck1 = 0x16 + Luck2 = 0x17 + Luck3 = 0x18 + Luck4 = 0x19 + Evasion1 = 0x1A + Evasion2 = 0x1B + Evasion3 = 0x1C + Evasion4 = 0x1D + Accuracy1 = 0x1E + Accuracy2 = 0x1F + Accuracy3 = 0x20 + Accuracy4 = 0x21 + HP200 = 0x22 + HP300 = 0x23 + MP40 = 0x24 + MP20 = 0x25 + MP10 = 0x26 + Lvl1Lock = 0x27 + Lvl2Lock = 0x28 + Lvl4Lock = 0x29 + # Abilities + DelayAttack = 0x2A + DelayBuster = 0x2B + SleepAttack = 0x2C + SilenceAttack = 0x2D + DarkAttack = 0x2E + ZombieAttack = 0x2F + SleepBuster = 0x30 + SilenceBuster = 0x31 + DarkBuster = 0x32 + TripleFoul = 0x33 + PowerBreak = 0x34 + MagicBreak = 0x35 + ArmorBreak = 0x36 + MentalBreak = 0x37 + Mug = 0x38 + QuickHit = 0x39 + Steal = 0x3A + Use = 0x3B + Flee = 0x3C + Pray = 0x3D + Cheer = 0x3E + Focus = 0x3F + Reflex = 0x40 + Aim = 0x41 + Luck = 0x42 + Jinx = 0x43 + Lancet = 0x44 + Guard = 0x45 + Sentinel = 0x46 + SpareChange = 0x47 + Threaten = 0x48 + Provoke = 0x49 + Entrust = 0x4A + Copycat = 0x4B + DoubleCast = 0x4C + Bribe = 0x4D + Cure = 0x4E + Cura = 0x4F + Curaga = 0x50 + NulFrost = 0x51 + NulBlaze = 0x52 + NulShock = 0x53 + NulTide = 0x54 + Scan = 0x55 + Esuna = 0x56 + Life = 0x57 + FullLife = 0x58 + Haste = 0x59 + Hastega = 0x5A + Slow = 0x5B + Slowga = 0x5C + Shell = 0x5D + Protect = 0x5E + Reflect = 0x5F + Dispel = 0x60 + Regen = 0x61 + Holy = 0x62 + AutoLife = 0x63 + Blizzard = 0x64 + Fire = 0x65 + Thunder = 0x66 + Water = 0x67 + Fira = 0x68 + Blizzara = 0x69 + Thundara = 0x6A + Watera = 0x6B + Firaga = 0x6C + Blizzaga = 0x6D + Thundaga = 0x6E + Waterga = 0x6F + Bio = 0x70 + Demi = 0x71 + Death = 0x72 + Drain = 0x73 + Osmose = 0x74 + Flare = 0x75 + Ultima = 0x76 + PilferGil = 0x77 + FullBreak = 0x78 + ExtractPower = 0x79 + ExtractMana = 0x7A + ExtractSpeed = 0x7B + ExtractAbility = 0x7C + NabGil = 0x7D + QuickPockets = 0x7E + + +class SphereGridNode: + # _NODE_TYPE_OFFSET = 6 # 2 byte value, matches the SphereNodeType Enum + # _ACTIVATED_BY_OFFSET = 33 # 1 byte value + _NODE_TYPE_OFFSET = 0 # 1 byte value, matches the SphereNodeType Enum + _ACTIVATED_BY_OFFSET = 1 # 1 byte value + + def __init__(self, offset: int) -> None: + self.offset = offset + + def get_node_type(self) -> SphereNodeType: + """Return an enum of type SphereNodeType that can be compared, or printed with .name""" + node_type = read_val(self.offset + self._NODE_TYPE_OFFSET, 1) # 2 + return SphereNodeType(node_type) + + def get_activated_by(self) -> int: + """This returns a one byte bitmask, where each bit indicates that a character has activated this node.""" + return read_val(self.offset + self._ACTIVATED_BY_OFFSET, 1) + + def __repr__(self) -> str: + node_type = self.get_node_type() + return node_type.name + + +class SphereGrid: + # TODO: Verify + # MemoryLocationData SphereGrid = 0x00D2EC7C (taken from CSR Rando) + _SPHERE_GRID_NODES_OFFSET = ( + 0x00D2EC7C # Base offset to sphere grid (taken from CSR) + ) + # _SPHERE_GRID_NODES_OFFSET = 0x012AE078 # Base offset to sphere grid (taken from Farplane) + # _SPHERE_GRID_NODE_SIZE = 40 # Size in bytes of a single node (taken from Farplane) + _SPHERE_GRID_NODE_SIZE = 2 # Size in bytes of a single node (taken from CSR) + _NUM_NODES = 857 # TODO: How many nodes are there? Verify + # _FIRST_NODE_OFFSET = 0x818 # TODO: Used? + + # int memorySizeBytes = 1714; + # byte[] SphereGridBytes = process.ReadBytes(memoryWatchers.SphereGrid.Address, memorySizeBytes); + + # TODO: Verify correct address + _CURRENT_NODE_OFFSET = 0x1130C # Offset to the currently selected node + + def __init__(self) -> None: + self.nodes = [ + SphereGridNode(self._get_node_offset(node_idx)) + for node_idx in range(self._NUM_NODES) + ] + + def _get_node_offset(self, node_idx: int) -> int: + return self._SPHERE_GRID_NODES_OFFSET + self._SPHERE_GRID_NODE_SIZE * node_idx + + def _get_current_node_offset(self) -> int: + return self._SPHERE_GRID_NODES_OFFSET + self._CURRENT_NODE_OFFSET + + def _get_current_node_idx(self) -> int: + return read_val(self._get_current_node_offset(), 4) + + def get_current_node(self) -> SphereGridNode: + return self.nodes[self._get_current_node_idx()] + + def get_node_at(self, index: int) -> SphereGridNode: + return self.nodes[index] + + +# Instantiation of sphere grid. Can be used to read out nodes +sphere_grid = SphereGrid() From 42228031a2b805cbedec8a122b327a55c62fcaa8 Mon Sep 17 00:00:00 2001 From: orkaboy Date: Tue, 29 Nov 2022 16:11:29 +0100 Subject: [PATCH 2/5] Added API to read the state of the sphere grid from memory. Contains example program. --- memory/sphere_grid.py | 32 ++++++++++++++++---------------- sphere_grid_reader.py | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 16 deletions(-) create mode 100644 sphere_grid_reader.py diff --git a/memory/sphere_grid.py b/memory/sphere_grid.py index ab5b1b58..42c918d9 100644 --- a/memory/sphere_grid.py +++ b/memory/sphere_grid.py @@ -138,10 +138,9 @@ class SphereNodeType(Enum): class SphereGridNode: - # _NODE_TYPE_OFFSET = 6 # 2 byte value, matches the SphereNodeType Enum - # _ACTIVATED_BY_OFFSET = 33 # 1 byte value _NODE_TYPE_OFFSET = 0 # 1 byte value, matches the SphereNodeType Enum - _ACTIVATED_BY_OFFSET = 1 # 1 byte value + _ACTIVATED_BY_OFFSET = 1 # 1 byte value, bitfield where each bit indicates that a character has grabbed the node + # The bitfield should match the character IDs, so bit[0] = Tidus, bit[1] = Yuna etc. def __init__(self, offset: int) -> None: self.offset = offset @@ -152,33 +151,31 @@ def get_node_type(self) -> SphereNodeType: return SphereNodeType(node_type) def get_activated_by(self) -> int: - """This returns a one byte bitmask, where each bit indicates that a character has activated this node.""" + """This returns a one byte bitfield, where each bit indicates that a character has activated this node.""" return read_val(self.offset + self._ACTIVATED_BY_OFFSET, 1) + def is_activated_by(self, character_id: int) -> bool: + """This checks if a specific bit in the bitfield is set or not.""" + activated_by = self.get_activated_by() + bitmask = 1 << character_id + return (activated_by & bitmask) != 0 + def __repr__(self) -> str: node_type = self.get_node_type() return node_type.name class SphereGrid: - # TODO: Verify - # MemoryLocationData SphereGrid = 0x00D2EC7C (taken from CSR Rando) _SPHERE_GRID_NODES_OFFSET = ( 0x00D2EC7C # Base offset to sphere grid (taken from CSR) ) - # _SPHERE_GRID_NODES_OFFSET = 0x012AE078 # Base offset to sphere grid (taken from Farplane) - # _SPHERE_GRID_NODE_SIZE = 40 # Size in bytes of a single node (taken from Farplane) _SPHERE_GRID_NODE_SIZE = 2 # Size in bytes of a single node (taken from CSR) - _NUM_NODES = 857 # TODO: How many nodes are there? Verify - # _FIRST_NODE_OFFSET = 0x818 # TODO: Used? - - # int memorySizeBytes = 1714; - # byte[] SphereGridBytes = process.ReadBytes(memoryWatchers.SphereGrid.Address, memorySizeBytes); + _NUM_NODES = 860 # Should be 860 for standard grid - # TODO: Verify correct address - _CURRENT_NODE_OFFSET = 0x1130C # Offset to the currently selected node + _CURRENT_NODE_OFFSET = 0x012BEB6C def __init__(self) -> None: + """Load up all sphere grid nodes and put them in self.nodes""" self.nodes = [ SphereGridNode(self._get_node_offset(node_idx)) for node_idx in range(self._NUM_NODES) @@ -188,15 +185,18 @@ def _get_node_offset(self, node_idx: int) -> int: return self._SPHERE_GRID_NODES_OFFSET + self._SPHERE_GRID_NODE_SIZE * node_idx def _get_current_node_offset(self) -> int: - return self._SPHERE_GRID_NODES_OFFSET + self._CURRENT_NODE_OFFSET + return self._CURRENT_NODE_OFFSET def _get_current_node_idx(self) -> int: + """Get the index of the currently selected node in the sphere grid""" return read_val(self._get_current_node_offset(), 4) def get_current_node(self) -> SphereGridNode: + """Get the currently selected node""" return self.nodes[self._get_current_node_idx()] def get_node_at(self, index: int) -> SphereGridNode: + """Get a specific node given an index""" return self.nodes[index] diff --git a/sphere_grid_reader.py b/sphere_grid_reader.py new file mode 100644 index 00000000..c0faca7f --- /dev/null +++ b/sphere_grid_reader.py @@ -0,0 +1,32 @@ +# Libraries and Core Files +import memory.main +from memory.sphere_grid import SphereNodeType, sphere_grid + +# Wait for memory to finish loading +while not memory.main.start(): + pass + + +# Example code to read a particular node +node_123 = sphere_grid.get_node_at(123) + +print(f"Node at idx=123 is {node_123}") + +last_node = 0 +while True: + cur_node = sphere_grid._get_current_node_idx() + if cur_node != last_node: + node = sphere_grid.get_current_node() + print( + f"New node: {cur_node}, type {node}, activation: {hex(node.get_activated_by())}" + ) + + # Example check for activation by Yuna + if node.is_activated_by(1): + print(f"Node {node} is activated by Yuna") + + # Example comparison of current node + if node.get_node_type() == SphereNodeType.Flare: + print("The currently selected node is the Flare spell!") + + last_node = cur_node From e40dcb7b948dbd96f3978d677b3da4e790ee610b Mon Sep 17 00:00:00 2001 From: orkaboy Date: Tue, 29 Nov 2022 18:05:19 +0100 Subject: [PATCH 3/5] Updated the sphere grid reader example for clarification. --- sphere_grid_reader.py | 52 ++++++++++++++++++++++++++----------------- 1 file changed, 31 insertions(+), 21 deletions(-) diff --git a/sphere_grid_reader.py b/sphere_grid_reader.py index c0faca7f..0d16ab7d 100644 --- a/sphere_grid_reader.py +++ b/sphere_grid_reader.py @@ -1,32 +1,42 @@ # Libraries and Core Files import memory.main from memory.sphere_grid import SphereNodeType, sphere_grid +from players import Yuna -# Wait for memory to finish loading -while not memory.main.start(): - pass +""" +NOTE! This file is intended as an example of how to use the sphere grid API, +it's a test file with its own main, and should not be imported into the TAS. +Use the sphere_grid object imported from memory.sphere_grid when implementing +the functionality in the TAS. +""" -# Example code to read a particular node -node_123 = sphere_grid.get_node_at(123) -print(f"Node at idx=123 is {node_123}") +if __name__ == "__main__": + # Wait for memory to finish loading + while not memory.main.start(): + pass -last_node = 0 -while True: - cur_node = sphere_grid._get_current_node_idx() - if cur_node != last_node: - node = sphere_grid.get_current_node() - print( - f"New node: {cur_node}, type {node}, activation: {hex(node.get_activated_by())}" - ) + # Example code to read a particular node + node_123 = sphere_grid.get_node_at(123) - # Example check for activation by Yuna - if node.is_activated_by(1): - print(f"Node {node} is activated by Yuna") + print(f"Node at idx=123 is {node_123}") - # Example comparison of current node - if node.get_node_type() == SphereNodeType.Flare: - print("The currently selected node is the Flare spell!") + last_node = 0 + while True: + cur_node = sphere_grid._get_current_node_idx() + if cur_node != last_node: + node = sphere_grid.get_current_node() + print( + f"New node: {cur_node}, type {node}, activation: {hex(node.get_activated_by())}" + ) - last_node = cur_node + # Example check for activation by Yuna + if node.is_activated_by(Yuna.id): + print(f"Node {node} is activated by Yuna") + + # Example comparison of current node + if node.get_node_type() == SphereNodeType.Flare: + print("The currently selected node is the Flare spell!") + + last_node = cur_node From 924670decfe3bbecd0db6d9ee3242bad614f82c1 Mon Sep 17 00:00:00 2001 From: orkaboy Date: Wed, 30 Nov 2022 07:37:35 +0100 Subject: [PATCH 4/5] Added readout of player positions on the sphere grid --- memory/sphere_grid.py | 14 ++++++++++++-- sphere_grid_reader.py | 18 ++++++++++++------ 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/memory/sphere_grid.py b/memory/sphere_grid.py index 42c918d9..59c51b15 100644 --- a/memory/sphere_grid.py +++ b/memory/sphere_grid.py @@ -172,6 +172,9 @@ class SphereGrid: _SPHERE_GRID_NODE_SIZE = 2 # Size in bytes of a single node (taken from CSR) _NUM_NODES = 860 # Should be 860 for standard grid + _TIDUS_POS_OFFSET = 0x012BE93C + _PLAYER_POS_SPACING = 0x50 + _CURRENT_NODE_OFFSET = 0x012BEB6C def __init__(self) -> None: @@ -187,13 +190,20 @@ def _get_node_offset(self, node_idx: int) -> int: def _get_current_node_offset(self) -> int: return self._CURRENT_NODE_OFFSET - def _get_current_node_idx(self) -> int: + def _get_player_node_offset(self, index: int) -> int: + return self._TIDUS_POS_OFFSET + index * self._PLAYER_POS_SPACING + + def get_player_node_idx(self, index: int) -> int: + """Get the index of the node where a specific player is located""" + return read_val(self._get_player_node_offset(index), 4) + + def get_current_node_idx(self) -> int: """Get the index of the currently selected node in the sphere grid""" return read_val(self._get_current_node_offset(), 4) def get_current_node(self) -> SphereGridNode: """Get the currently selected node""" - return self.nodes[self._get_current_node_idx()] + return self.nodes[self.get_current_node_idx()] def get_node_at(self, index: int) -> SphereGridNode: """Get a specific node given an index""" diff --git a/sphere_grid_reader.py b/sphere_grid_reader.py index 0d16ab7d..e4373107 100644 --- a/sphere_grid_reader.py +++ b/sphere_grid_reader.py @@ -1,7 +1,7 @@ # Libraries and Core Files import memory.main from memory.sphere_grid import SphereNodeType, sphere_grid -from players import Yuna +from players import Auron, Kimahri, Lulu, Rikku, Tidus, Wakka, Yuna """ NOTE! This file is intended as an example of how to use the sphere grid API, @@ -17,16 +17,22 @@ while not memory.main.start(): pass - # Example code to read a particular node + # Example code to read a particular node at an arbitrary position node_123 = sphere_grid.get_node_at(123) - print(f"Node at idx=123 is {node_123}") last_node = 0 while True: - cur_node = sphere_grid._get_current_node_idx() + # ID of the node currently selected by the GUI + cur_node = sphere_grid.get_current_node_idx() if cur_node != last_node: + # The actual node selected by the GUI node = sphere_grid.get_current_node() + # Print the node indices of all the players + print( + f"Positions: [Tidus={sphere_grid.get_player_node_idx(Tidus.id)}] [Yuna={sphere_grid.get_player_node_idx(Yuna.id)}] [Auron={sphere_grid.get_player_node_idx(Auron.id)}] [Kimahri={sphere_grid.get_player_node_idx(Kimahri.id)}] [Wakka={sphere_grid.get_player_node_idx(Wakka.id)}] [Lulu={sphere_grid.get_player_node_idx(Lulu.id)}] [Rikku={sphere_grid.get_player_node_idx(Rikku.id)}]" + ) + # Print the currently selected node and a bitfield of what characters have activated it print( f"New node: {cur_node}, type {node}, activation: {hex(node.get_activated_by())}" ) @@ -35,8 +41,8 @@ if node.is_activated_by(Yuna.id): print(f"Node {node} is activated by Yuna") - # Example comparison of current node + # Example comparison of current node to the enum list if node.get_node_type() == SphereNodeType.Flare: print("The currently selected node is the Flare spell!") - + # Just to prevent spam, only print the info once last_node = cur_node From 93f874855e10c71987900c270cf4497ade592f63 Mon Sep 17 00:00:00 2001 From: orkaboy Date: Thu, 1 Dec 2022 15:59:50 +0100 Subject: [PATCH 5/5] Updated sphere grid code to use new Player classes. --- memory/sphere_grid.py | 9 +++++---- players/__init__.py | 2 ++ sphere_grid_reader.py | 4 ++-- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/memory/sphere_grid.py b/memory/sphere_grid.py index 59c51b15..f2c6fa9c 100644 --- a/memory/sphere_grid.py +++ b/memory/sphere_grid.py @@ -2,6 +2,7 @@ from enum import Enum from memory.main import read_val +from players import Player logger = logging.getLogger(__name__) @@ -154,10 +155,10 @@ def get_activated_by(self) -> int: """This returns a one byte bitfield, where each bit indicates that a character has activated this node.""" return read_val(self.offset + self._ACTIVATED_BY_OFFSET, 1) - def is_activated_by(self, character_id: int) -> bool: + def is_activated_by(self, player: Player) -> bool: """This checks if a specific bit in the bitfield is set or not.""" activated_by = self.get_activated_by() - bitmask = 1 << character_id + bitmask = 1 << player.id return (activated_by & bitmask) != 0 def __repr__(self) -> str: @@ -193,9 +194,9 @@ def _get_current_node_offset(self) -> int: def _get_player_node_offset(self, index: int) -> int: return self._TIDUS_POS_OFFSET + index * self._PLAYER_POS_SPACING - def get_player_node_idx(self, index: int) -> int: + def get_player_node_idx(self, player: Player) -> int: """Get the index of the node where a specific player is located""" - return read_val(self._get_player_node_offset(index), 4) + return read_val(self._get_player_node_offset(player.id), 4) def get_current_node_idx(self) -> int: """Get the index of the currently selected node in the sphere grid""" diff --git a/players/__init__.py b/players/__init__.py index 0d29f749..f3d63691 100644 --- a/players/__init__.py +++ b/players/__init__.py @@ -1,6 +1,7 @@ from players.anima import Anima from players.auron import Auron from players.bahamut import Bahamut +from players.base import Player from players.current_player import CurrentPlayer from players.ifrit import Ifrit from players.ixion import Ixion @@ -16,6 +17,7 @@ from players.yuna import Yuna __all__ = [ + "Player", "Tidus", "Lulu", "Wakka", diff --git a/sphere_grid_reader.py b/sphere_grid_reader.py index e4373107..9ae0c1c4 100644 --- a/sphere_grid_reader.py +++ b/sphere_grid_reader.py @@ -30,7 +30,7 @@ node = sphere_grid.get_current_node() # Print the node indices of all the players print( - f"Positions: [Tidus={sphere_grid.get_player_node_idx(Tidus.id)}] [Yuna={sphere_grid.get_player_node_idx(Yuna.id)}] [Auron={sphere_grid.get_player_node_idx(Auron.id)}] [Kimahri={sphere_grid.get_player_node_idx(Kimahri.id)}] [Wakka={sphere_grid.get_player_node_idx(Wakka.id)}] [Lulu={sphere_grid.get_player_node_idx(Lulu.id)}] [Rikku={sphere_grid.get_player_node_idx(Rikku.id)}]" + f"Positions: [Tidus={sphere_grid.get_player_node_idx(Tidus)}] [Yuna={sphere_grid.get_player_node_idx(Yuna)}] [Auron={sphere_grid.get_player_node_idx(Auron)}] [Kimahri={sphere_grid.get_player_node_idx(Kimahri)}] [Wakka={sphere_grid.get_player_node_idx(Wakka)}] [Lulu={sphere_grid.get_player_node_idx(Lulu)}] [Rikku={sphere_grid.get_player_node_idx(Rikku)}]" ) # Print the currently selected node and a bitfield of what characters have activated it print( @@ -38,7 +38,7 @@ ) # Example check for activation by Yuna - if node.is_activated_by(Yuna.id): + if node.is_activated_by(Yuna): print(f"Node {node} is activated by Yuna") # Example comparison of current node to the enum list