-
Notifications
You must be signed in to change notification settings - Fork 5
Sphere grid memory mapping #287
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
Changes from 2 commits
5208591
4222803
e40dcb7
7e40fd4
924670d
7f58dc0
93f8748
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 = 0 # 1 byte value, matches the SphereNodeType Enum | ||
_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 | ||
|
||
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 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: | ||
_SPHERE_GRID_NODES_OFFSET = ( | ||
0x00D2EC7C # Base offset to sphere grid (taken from CSR) | ||
) | ||
_SPHERE_GRID_NODE_SIZE = 2 # Size in bytes of a single node (taken from CSR) | ||
_NUM_NODES = 860 # Should be 860 for standard grid | ||
|
||
_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) | ||
] | ||
|
||
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._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] | ||
|
||
|
||
# Instantiation of sphere grid. Can be used to read out nodes | ||
sphere_grid = SphereGrid() |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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(): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's probably worth wrapping most of this stuff in a function. The act of importing this file would do bad things right now. Lol. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I added a comment clarifying that this file is not meant to be integrated into the TAS, only serve as an example on how to use the API. |
||
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: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we want this to actually run forever? Because this doesn't have a break or a return. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See comment below. |
||
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): | ||
orkaboy marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we lean into the new world here. Instead of passing in
character_id: int
, I'd rather seecharacter: Player
, then we either implement the bitwise operator for the class or grab the id off the player object further down.Thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm inclined to agree with you. I'll make some tweaks and test.