CCCPaste

rePosition a NoMansSky Corvette

#!/usr/bin/python
"""rePosition: read a NMS PersistentPlayerBases JSON array entry
    and add an offset to every Position entry in its Objects[] array.

    This is to re-align a Corvette ship to the station hangar's grid
    (which somehow got shifted in an update Sep 2025...?)

    Python Versions tested: 3.9

    Host OS Dependencies:

"""

#==============================================================================
# Imports and definitions
from __future__ import print_function
import sys
import argparse
import json


#==============================================================================
# Args
def handle_args():
    """Parse args and return the resulting Namespace object."""

    parser = argparse.ArgumentParser(
        description="rePosition items in an Objects array in a JSON file.",
        formatter_class=lambda prog: argparse.RawDescriptionHelpFormatter(
            prog, max_help_position=35),
        # NB: use add_help=False to prevent auto -h/--help
        epilog='''
This is to re-position an off-center Corvette in the hangar's Build area
so that the ship's landing gear is located correctly on the grid it snaps to.

It reads from standard input (or -f FILE) and writes to standard output.

The Position property in Objects[] is in the order (y,z,x), where
positive x is in the direction toward the station entrance portal and
(0,0,0) is the yellow central crosshairs on the deck.

To use this you need to know the (y,z,x) position of the object you want
positioned at exactly the center so it can be subtracted from every object.
To find this:
   1. First name your ship something besides the procedurally-generated name.
   2. Open the Raw JSON using NMSSaveEditor or NomNom.
   3. Open BaseContext/PlayerStateData
   3. Look in ShipOwnership for the entry with your name.  Remember the index.
   4. Look in PersistentPlayerBases for the entry with "UserData":.
   5. Look for Objects entries with ObjectID = ^B_LND_xx; these are the
     landing gear.
      Find the one closest to the center (0,0,0) and remember its Position
      (y,z,x) to use as parameters to this program.
      If you don't have one in the center, you can work out the offset:
      the grid spacing is 4.0, so you can calculate the intended
      Position of your gear, and subtract that from the actual Position
      of that gear in the JSON to get the offset you need to use as
      parameters for this program.
    6. Run this program with the -O option and the offset parameters.
    7. Replace the Objects[] array in PersistentPlayerBases with the output.
''')

    # Options
    parser.add_argument(
        '-d', '--debug', action='count',
        help="Enable debug output and DEBUG logging to stderr.")
    parser.add_argument(
        '-v', '--verbose', action='count', default=1,
        help='''Increase verbosity (default=%(default)s/WARN).
The effective verbosity is the maximum of this and any defined in the
config file.
''')
    parser.add_argument(
        '-q', '--quiet', action='count', default=0,
        help="Decrease verbosity.")
    parser.add_argument(
        '-n', '--norun', action='store_true',
        help="Display what would be done instead of doing it.")
    parser.add_argument(
        '-D', '--dump', action='store_true',
        help="Dump the active args and config and exit.")
    #parser.add_argument('-h', '--help', action='help')
    parser.add_argument('-f', '--file', dest='file',
                        default='-',
                        help="Read JSON from FILE instead of stdin.")
    parser.add_argument(
        '-O', '--objects', action='store_true',
        help="Only output the Objects array, ready to paste.")

    # Required positional args; NB: NMS Position order is (y, z, x)
    parser.add_argument('y', type=float )
    parser.add_argument('z', type=float )
    parser.add_argument('x', type=float )


    args = parser.parse_args()

    # fix verbose/quiet
    args.verbose -= args.quiet

    return args

#==============================================================================
def main():
    """The main program.  Parse args, initialize and run."""
    args = handle_args()

    if args.dump:
        print("args:")
        print(json.dumps(vars(args), indent=4))
        # sys.exit(0)


    # Read JSON
    with open(args.file, 'r') if args.file != "-" else sys.stdin as f:
        data = json.load(f)

    if args.dump:
        print("data:")
        print(json.dumps(data, indent=4))
        sys.exit(0)


    # Process Objects[]
    for obj in data['Objects']:
        pos = obj['Position']
        # re-Position it
        pos[0] = pos[0] - args.y
        pos[1] = pos[1] - args.z
        pos[2] = pos[2] - args.x


    # Write JSON
    with sys.stdout as f:
        if args.objects:
            objstr = json.dumps({"Objects": data['Objects']}, indent=4)
            objstr = objstr[2:-2]   # get rid of enclosing curly braces and nl
            objstr += ",\n"         # ready to paste
            sys.stdout.write(objstr)
        else:
            sys.stdout.write(json.dumps(data, indent=4))


#==============================================================================
if __name__ == "__main__":
    try:
        main()
    except KeyboardInterrupt:
        sys.exit("caught interrupt")