Skip to content

Commit 259934b

Browse files
authored
Merge pull request #287 from orkaboy/dev/sphere_grid
Sphere grid memory mapping
2 parents 66f2aec + 93f8748 commit 259934b

File tree

3 files changed

+265
-0
lines changed

3 files changed

+265
-0
lines changed

Diff for: memory/sphere_grid.py

+215
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
import logging
2+
from enum import Enum
3+
4+
from memory.main import read_val
5+
from players import Player
6+
7+
logger = logging.getLogger(__name__)
8+
9+
10+
class SphereNodeType(Enum):
11+
Lvl3Lock = 0x00
12+
Empty = 0x01
13+
Strength1 = 0x02
14+
Strength2 = 0x03
15+
Strength3 = 0x04
16+
Strength4 = 0x05
17+
Defense1 = 0x06
18+
Defense2 = 0x07
19+
Defense3 = 0x08
20+
Defense4 = 0x09
21+
Magic1 = 0x0A
22+
Magic2 = 0x0B
23+
Magic3 = 0x0C
24+
Magic4 = 0x0D
25+
MagicDef1 = 0x0E
26+
MagicDef2 = 0x0F
27+
MagicDef3 = 0x10
28+
MagicDef4 = 0x11
29+
Agility1 = 0x12
30+
Agility2 = 0x13
31+
Agility3 = 0x14
32+
Agility4 = 0x15
33+
Luck1 = 0x16
34+
Luck2 = 0x17
35+
Luck3 = 0x18
36+
Luck4 = 0x19
37+
Evasion1 = 0x1A
38+
Evasion2 = 0x1B
39+
Evasion3 = 0x1C
40+
Evasion4 = 0x1D
41+
Accuracy1 = 0x1E
42+
Accuracy2 = 0x1F
43+
Accuracy3 = 0x20
44+
Accuracy4 = 0x21
45+
HP200 = 0x22
46+
HP300 = 0x23
47+
MP40 = 0x24
48+
MP20 = 0x25
49+
MP10 = 0x26
50+
Lvl1Lock = 0x27
51+
Lvl2Lock = 0x28
52+
Lvl4Lock = 0x29
53+
# Abilities
54+
DelayAttack = 0x2A
55+
DelayBuster = 0x2B
56+
SleepAttack = 0x2C
57+
SilenceAttack = 0x2D
58+
DarkAttack = 0x2E
59+
ZombieAttack = 0x2F
60+
SleepBuster = 0x30
61+
SilenceBuster = 0x31
62+
DarkBuster = 0x32
63+
TripleFoul = 0x33
64+
PowerBreak = 0x34
65+
MagicBreak = 0x35
66+
ArmorBreak = 0x36
67+
MentalBreak = 0x37
68+
Mug = 0x38
69+
QuickHit = 0x39
70+
Steal = 0x3A
71+
Use = 0x3B
72+
Flee = 0x3C
73+
Pray = 0x3D
74+
Cheer = 0x3E
75+
Focus = 0x3F
76+
Reflex = 0x40
77+
Aim = 0x41
78+
Luck = 0x42
79+
Jinx = 0x43
80+
Lancet = 0x44
81+
Guard = 0x45
82+
Sentinel = 0x46
83+
SpareChange = 0x47
84+
Threaten = 0x48
85+
Provoke = 0x49
86+
Entrust = 0x4A
87+
Copycat = 0x4B
88+
DoubleCast = 0x4C
89+
Bribe = 0x4D
90+
Cure = 0x4E
91+
Cura = 0x4F
92+
Curaga = 0x50
93+
NulFrost = 0x51
94+
NulBlaze = 0x52
95+
NulShock = 0x53
96+
NulTide = 0x54
97+
Scan = 0x55
98+
Esuna = 0x56
99+
Life = 0x57
100+
FullLife = 0x58
101+
Haste = 0x59
102+
Hastega = 0x5A
103+
Slow = 0x5B
104+
Slowga = 0x5C
105+
Shell = 0x5D
106+
Protect = 0x5E
107+
Reflect = 0x5F
108+
Dispel = 0x60
109+
Regen = 0x61
110+
Holy = 0x62
111+
AutoLife = 0x63
112+
Blizzard = 0x64
113+
Fire = 0x65
114+
Thunder = 0x66
115+
Water = 0x67
116+
Fira = 0x68
117+
Blizzara = 0x69
118+
Thundara = 0x6A
119+
Watera = 0x6B
120+
Firaga = 0x6C
121+
Blizzaga = 0x6D
122+
Thundaga = 0x6E
123+
Waterga = 0x6F
124+
Bio = 0x70
125+
Demi = 0x71
126+
Death = 0x72
127+
Drain = 0x73
128+
Osmose = 0x74
129+
Flare = 0x75
130+
Ultima = 0x76
131+
PilferGil = 0x77
132+
FullBreak = 0x78
133+
ExtractPower = 0x79
134+
ExtractMana = 0x7A
135+
ExtractSpeed = 0x7B
136+
ExtractAbility = 0x7C
137+
NabGil = 0x7D
138+
QuickPockets = 0x7E
139+
140+
141+
class SphereGridNode:
142+
_NODE_TYPE_OFFSET = 0 # 1 byte value, matches the SphereNodeType Enum
143+
_ACTIVATED_BY_OFFSET = 1 # 1 byte value, bitfield where each bit indicates that a character has grabbed the node
144+
# The bitfield should match the character IDs, so bit[0] = Tidus, bit[1] = Yuna etc.
145+
146+
def __init__(self, offset: int) -> None:
147+
self.offset = offset
148+
149+
def get_node_type(self) -> SphereNodeType:
150+
"""Return an enum of type SphereNodeType that can be compared, or printed with .name"""
151+
node_type = read_val(self.offset + self._NODE_TYPE_OFFSET, 1) # 2
152+
return SphereNodeType(node_type)
153+
154+
def get_activated_by(self) -> int:
155+
"""This returns a one byte bitfield, where each bit indicates that a character has activated this node."""
156+
return read_val(self.offset + self._ACTIVATED_BY_OFFSET, 1)
157+
158+
def is_activated_by(self, player: Player) -> bool:
159+
"""This checks if a specific bit in the bitfield is set or not."""
160+
activated_by = self.get_activated_by()
161+
bitmask = 1 << player.id
162+
return (activated_by & bitmask) != 0
163+
164+
def __repr__(self) -> str:
165+
node_type = self.get_node_type()
166+
return node_type.name
167+
168+
169+
class SphereGrid:
170+
_SPHERE_GRID_NODES_OFFSET = (
171+
0x00D2EC7C # Base offset to sphere grid (taken from CSR)
172+
)
173+
_SPHERE_GRID_NODE_SIZE = 2 # Size in bytes of a single node (taken from CSR)
174+
_NUM_NODES = 860 # Should be 860 for standard grid
175+
176+
_TIDUS_POS_OFFSET = 0x012BE93C
177+
_PLAYER_POS_SPACING = 0x50
178+
179+
_CURRENT_NODE_OFFSET = 0x012BEB6C
180+
181+
def __init__(self) -> None:
182+
"""Load up all sphere grid nodes and put them in self.nodes"""
183+
self.nodes = [
184+
SphereGridNode(self._get_node_offset(node_idx))
185+
for node_idx in range(self._NUM_NODES)
186+
]
187+
188+
def _get_node_offset(self, node_idx: int) -> int:
189+
return self._SPHERE_GRID_NODES_OFFSET + self._SPHERE_GRID_NODE_SIZE * node_idx
190+
191+
def _get_current_node_offset(self) -> int:
192+
return self._CURRENT_NODE_OFFSET
193+
194+
def _get_player_node_offset(self, index: int) -> int:
195+
return self._TIDUS_POS_OFFSET + index * self._PLAYER_POS_SPACING
196+
197+
def get_player_node_idx(self, player: Player) -> int:
198+
"""Get the index of the node where a specific player is located"""
199+
return read_val(self._get_player_node_offset(player.id), 4)
200+
201+
def get_current_node_idx(self) -> int:
202+
"""Get the index of the currently selected node in the sphere grid"""
203+
return read_val(self._get_current_node_offset(), 4)
204+
205+
def get_current_node(self) -> SphereGridNode:
206+
"""Get the currently selected node"""
207+
return self.nodes[self.get_current_node_idx()]
208+
209+
def get_node_at(self, index: int) -> SphereGridNode:
210+
"""Get a specific node given an index"""
211+
return self.nodes[index]
212+
213+
214+
# Instantiation of sphere grid. Can be used to read out nodes
215+
sphere_grid = SphereGrid()

Diff for: players/__init__.py

+2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from players.anima import Anima
22
from players.auron import Auron
33
from players.bahamut import Bahamut
4+
from players.base import Player
45
from players.current_player import CurrentPlayer
56
from players.ifrit import Ifrit
67
from players.ixion import Ixion
@@ -16,6 +17,7 @@
1617
from players.yuna import Yuna
1718

1819
__all__ = [
20+
"Player",
1921
"Tidus",
2022
"Lulu",
2123
"Wakka",

Diff for: sphere_grid_reader.py

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Libraries and Core Files
2+
import memory.main
3+
from memory.sphere_grid import SphereNodeType, sphere_grid
4+
from players import Auron, Kimahri, Lulu, Rikku, Tidus, Wakka, Yuna
5+
6+
"""
7+
NOTE! This file is intended as an example of how to use the sphere grid API,
8+
it's a test file with its own main, and should not be imported into the TAS.
9+
10+
Use the sphere_grid object imported from memory.sphere_grid when implementing
11+
the functionality in the TAS.
12+
"""
13+
14+
15+
if __name__ == "__main__":
16+
# Wait for memory to finish loading
17+
while not memory.main.start():
18+
pass
19+
20+
# Example code to read a particular node at an arbitrary position
21+
node_123 = sphere_grid.get_node_at(123)
22+
print(f"Node at idx=123 is {node_123}")
23+
24+
last_node = 0
25+
while True:
26+
# ID of the node currently selected by the GUI
27+
cur_node = sphere_grid.get_current_node_idx()
28+
if cur_node != last_node:
29+
# The actual node selected by the GUI
30+
node = sphere_grid.get_current_node()
31+
# Print the node indices of all the players
32+
print(
33+
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)}]"
34+
)
35+
# Print the currently selected node and a bitfield of what characters have activated it
36+
print(
37+
f"New node: {cur_node}, type {node}, activation: {hex(node.get_activated_by())}"
38+
)
39+
40+
# Example check for activation by Yuna
41+
if node.is_activated_by(Yuna):
42+
print(f"Node {node} is activated by Yuna")
43+
44+
# Example comparison of current node to the enum list
45+
if node.get_node_type() == SphereNodeType.Flare:
46+
print("The currently selected node is the Flare spell!")
47+
# Just to prevent spam, only print the info once
48+
last_node = cur_node

0 commit comments

Comments
 (0)