-
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
Merged
Merged
Changes from 5 commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
5208591
Initial implementation of sphere grid mapping to memory.
orkaboy 4222803
Added API to read the state of the sphere grid from memory. Contains
orkaboy e40dcb7
Updated the sphere grid reader example for clarification.
orkaboy 7e40fd4
Merge branch 'main' into dev/sphere_grid
orkaboy 924670d
Added readout of player positions on the sphere grid
orkaboy 7f58dc0
Merge branch 'main' into dev/sphere_grid
orkaboy 93f8748
Updated sphere grid code to use new Player classes.
orkaboy File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,214 @@ | ||
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 | ||
|
||
_TIDUS_POS_OFFSET = 0x012BE93C | ||
_PLAYER_POS_SPACING = 0x50 | ||
|
||
_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_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()] | ||
|
||
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() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
# Libraries and Core Files | ||
import memory.main | ||
from memory.sphere_grid import SphereNodeType, sphere_grid | ||
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, | ||
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. | ||
""" | ||
|
||
|
||
if __name__ == "__main__": | ||
# Wait for memory to finish loading | ||
while not memory.main.start(): | ||
pass | ||
|
||
# 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: | ||
# 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())}" | ||
) | ||
|
||
# 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 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 |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.