|
Post by ViniLaranj on Apr 20, 2019 13:37:08 GMT -5
How Can I change the PhyCharm language to portuguese?
|
|
|
Post by mikakost on Jun 20, 2019 6:08:45 GMT -5
After runnig example_mod.py, I receive followin error: ModuleNotFoundError: No module named 'sims4' I can't even add module sims4 from decompiled EA folder
|
|
|
Post by mintsweettea on Jul 7, 2019 13:32:28 GMT -5
So after completing the tutorial, I decided to make my own cheat, because I figured that would be an easy next step. So I copied the first line from the example mod: import sims4.commands to load the sims4.commands module from EA's game scripts. Then I went into sims_commands and copied the code for the motherlode cheat: @sims4.commands.Command('motherlode', command_type=sims4.commands.CommandType.Live, console_type=sims4.commands.CommandType.Cheat) def motherlode(_connection=None): tgt_client = services.client_manager().get(_connection) modify_fund_helper(50000, Consts_pb2.TELEMETRY_MONEY_CHEAT, tgt_client.active_sim)
and I changed each "motherlode" to "momloan" and the "50000" after modify_fund_helper to "800" After I save and compile it and everything, it still shows up in my mods folder and as a script file when I load up the game, but when I start playing and try to use the cheat, nothing happens. I have turned testingcheats on, and tested that "motherlode" still works and "myfirstscript". Am I totally wrong in thinking that this would work? Or is there just an issue in my code? I edited your post: swapped 'quote' for 'code' tags so the Python would render correctly.
|
|
|
Post by fufu508 on Jul 13, 2019 18:01:26 GMT -5
Hi andrew , I finally got around to trying your updated script. It worked beautifully! It's plenty fast enough too. I altered it to output the mods clips first, then output the game clips, sorting each separately. I also changed it case-insensitive sort.
I also generate IDs in base36 so that they are at most 3 characters long instead of base10 where it's 5 digits long. That allows all custom clips (up to 1297 of them ) to have an ID of at most two characters. I anticipate perhaps using the ids to define the sequence of the playlist. {Spoiler}# Based on a version Andrew posted that parses the animation clips in files in the mods folder # http://sims4studio.com/post/121791/thread
import fnmatch import io import os import os.path import struct import zlib import sims4.resources import sims4.commands import sims4.log import paths from typing import List, Iterator, Tuple, Callable, Set
logger = sims4.log.Logger('Clip Test')
def read_package(filename: str, type_filter: set) -> Iterator[Tuple[int, int, int, Callable[[], bytes]]]: type_filter = set() if not type_filter else type_filter with open(filename, 'rb') as stream: def u32() -> int: return struct.unpack('I', stream.read(4))[0]
tag = stream.read(4).decode('ascii') assert tag == 'DBPF' stream.seek(32, io.SEEK_CUR) index_entry_count = u32() stream.seek(24, io.SEEK_CUR) index_offset = u32() stream.seek(index_offset, io.SEEK_SET) index_flags: int = u32() static_t: int = u32() if index_flags & 0x1 else 0 static_g: int = u32() if index_flags & 0x2 else 0 static_i: int = u32() << 32 if index_flags & 0x4 else 0 static_i |= u32() if index_flags & 0x8 else 0
for _ in range(index_entry_count): t = static_t if index_flags & 0x1 else u32() g = static_g if index_flags & 0x2 else u32() instance_hi = static_i >> 32 if index_flags & 0x4 else u32() instance_lo = static_i & 0xFFFFFFFF if index_flags & 0x8 else u32() i = (instance_hi << 32) + instance_lo offset: int = u32() sz: int = u32() file_size: int = sz & 0x7FFFFFFF stream.seek(4, io.SEEK_CUR) compressed: bool = sz & 0x80000000 > 0 compression_type: int = 0 if compressed: compression_type = struct.unpack('H', stream.read(2))[0] stream.seek(2, io.SEEK_CUR)
if compression_type not in (0x0000, 0x5A42): continue
def load_func() -> bytes: pos = stream.tell() stream.seek(offset, io.SEEK_SET) data = stream.read(file_size) stream.seek(pos, io.SEEK_SET) return zlib.decompress(data) if compression_type == 0x5A42 else data
if len(type_filter) == 0 or t in type_filter: yield t, g, i, load_func
def get_clips_from_folder(folder: str) -> Tuple[Set[str], int]: folder_clips: Set[str] = set() pattern = '*.package' failures: int = 0 for root, dirs, files in os.walk(folder): for filename in fnmatch.filter(files, pattern): package_path = str(os.path.join(root, filename)) try: package_clips, package_failures = get_clips_from_package(package_path) folder_clips |= package_clips failures += package_failures except Exception as ex: logger.exception(f'Failed to read package {package_path}', exc=ex) failures += 1 return folder_clips, failures
def get_clips_from_package(p) -> Tuple[Set[str], int]: package_clips: Set[str] = set() failures: int = 0 for t, g, i, load_func in read_package(p, type_filter={sims4.resources.Types.CLIP_HEADER}): try: with io.BytesIO(load_func()) as resource: package_clips.add(get_clip_name(resource)) except Exception as ex: logger.exception(f'Bad key: {t:X}:{g:X}:{i:X}', exc=ex) failures += 1 return package_clips, failures
def get_clip_name(resource: io.BytesIO) -> str: resource.seek(0x38, io.SEEK_SET) name_length: int = struct.unpack('i', resource.read(4))[0] name: str = resource.read(name_length).decode('ascii') return name
# convert to base35 (omit the letter l which is too similar to the digit 1) def to_base_n(n, base=35, d="0123456789abcdefghijkmnopqrstuvwxyz"): return (to_base_n(n // base, base) + d[n % base]).lstrip("0") if n > 0 else "0"
@sims4.commands.Command('clips2file', command_type=sims4.commands.CommandType.Live) def clips2file(_connection=None): output = sims4.commands.CheatOutput(_connection) clip_names_file = 'c:/tmp/ts4-anim-clips.txt'
mods_folder = os.path.expanduser( os.path.join('~', 'Documents', 'Electronic Arts', 'The Sims 4', 'Mods') ) game_folder = os.path.dirname(os.path.dirname(os.path.dirname(paths.APP_ROOT)))
output(f'Reading mods animation clips.') mods_clips, mods_failures = get_clips_from_folder(mods_folder) output(f'Reading game animation clips.') game_clips, game_failures = get_clips_from_folder(game_folder)
bad_keys: int = game_failures + mods_failures if bad_keys > 0: output(f'Number of clips with keys that failed to read: {bad_keys}')
mods_clip_names: List[str] = sorted(list(mods_clips), key=str.lower) game_clip_names: List[str] = sorted(list(game_clips), key=str.lower)
with open(clip_names_file, 'w+') as f: f.write(f'id_num clip_name\n') id_num = 1 output(f'Writing {len(mods_clip_names)} mods clip names to {clip_names_file}') for index in range(len(mods_clip_names)): f.write(' {0:<7}{1}\n'.format(to_base_n(id_num), mods_clip_names[index])) id_num += 1 output(f'Writing {len(game_clip_names)} game clip names to {clip_names_file}') for index in range(len(game_clip_names)): f.write(' {0:<7}{1}\n'.format(to_base_n(id_num), game_clip_names[index])) id_num += 1 output(f'Done!')
I am now going to work on designing a script together that parses the list Question: Can I include the clip name that appears in the Pose Player GUI?
The script outputs the generic-looking name like this, now:
h aom_death_pose_pack_plain_dead i aom_death_steamRoom_longer_x ... x fufu508:PosePack_201510060636431124_set_2 y fufu508:PosePack_201510060636431124_set_20 z fufu508:PosePack_201510060636431124_set_21 10 fufu508:PosePack_201510060636431124_set_22 11 fufu508:PosePack_201510060636431124_set_23
|
|
|
Post by fufu508 on Jul 13, 2019 18:59:30 GMT -5
Hi mintsweettea , I would assume your modification to the motherlode cheat should work. I added a couple diagnostic lines and proved your script is being called.
import sims4.commands
@sims4.commands.Command('momloan', command_type=sims4.commands.CommandType.Live, console_type=sims4.commands.CommandType.Cheat) def momloan(_connection=None): output = sims4.commands.CheatOutput(_connection) output("Mom loaned the selected Sim 800 Simoleons!") tgt_client = services.client_manager().get(_connection) modify_fund_helper(800, Consts_pb2.TELEMETRY_MONEY_CHEAT, tgt_client.active_sim)
Not sure why the call to the money cheat doesn't work, however. Note: I find working in a post here with the code and spoiler tags to be easier in 'BBCode' view versus 'Preview'. So after completing the tutorial, I decided to make my own cheat, because I figured that would be an easy next step. So I copied the first line from the example mod: to load the sims4.commands module from EA's game scripts. Then I went into sims_commands and copied the code for the motherlode cheat: andI changed each "motherlode" to "momloan" and the "50000" after modify_fund_helper to "800" After I save and compile it and everything, it still shows up in my mods folder and as a script file when I load up the game, but when I start playing and try to use the cheat, nothing happens. I have turned testingcheats on, and tested that "motherlode" still works and "myfirstscript". Am I totally wrong in thinking that this would work? Or is there just an issue in my code?
|
|
|
Post by fufu508 on Jul 20, 2019 8:21:07 GMT -5
I see this function appears to use byte offset +0x38.
Would I be correct in assuming the "friendly" name saved in Sims 4 Studio for each clip in a pose pack can simply be read by specifying a different offset than 0x38?
def get_clip_name(resource: io.BytesIO) -> str: resource.seek(0x38, io.SEEK_SET) name_length: int = struct.unpack('i', resource.read(4))[0] name: str = resource.read(name_length).decode('ascii') return name
|
|
|
Post by andrew on Jul 28, 2019 13:42:41 GMT -5
fufu508 Getting the names used in the pose player should be possible, but not very straight-forward. You will need to read the through the pose pack tuning which has the string table references to the names used in the UI and the clip name. global PosePackLookup def load_data_into_class_instances_load_posepack_names(instance_manager): for key in instance_manager.types: snippet = instance_manager.types[key] if hasattr(snippet, 's4s_mod_type') and str(snippet.s4s_mod_type).upper() == 'POSE_PACK': for pose in snippet.pose_list: PosePackLookup[pose.pose_name] = (pose, snippet)
services.get_instance_manager(sims4.resources.Types.SNIPPET) \ .add_on_load_complete(load_data_into_class_instances_load_posepack_names)
The code above would simply loop through the snippet tunings after they are loaded and make a dictionary that will let you look up the pose pack and pose information from the tuning. The values on the pose and snippet variables would have the same values as the xml in a given pose pack. mintsweettea It looks like you are missing some import statements that were used in the place that you copied the money cheat from. PyCharm should underline things that are missing an import and have a context action(either alt+enter or click the red light bulb that pops up when you have your cursor on the red underlined part of the code) to add it for you. import services import sims4.commands from protocolbuffers import Consts_pb2 from server_commands.sim_commands import modify_fund_helper
@sims4.commands.Command('momloan', command_type=sims4.commands.CommandType.Live, console_type=sims4.commands.CommandType.Cheat) def momloan(_connection=None): output = sims4.commands.CheatOutput(_connection) output("Mom loaned the selected Sim 800 Simoleons!") tgt_client = services.client_manager().get(_connection) modify_fund_helper(800, Consts_pb2.TELEMETRY_MONEY_CHEAT, tgt_client.active_sim)
ViniLaranj unfortunately I do not think that it is available in Portuguese. mikakost the decompiled sims 4 modules are not perfect enough to run. If you need to run some code outside of the game, it would be best to separate the parts that need the sims 4 module from the parts that do to test with. If you are just trying to run it in the game, run the compile.py instead of example_mod.py and it will create the script package so that you can run it in the game.
|
|
|
Post by nought on Aug 10, 2019 10:14:52 GMT -5
I just looked at the game files and couldn't find the defined, specific traits, are those not stored in a .py file? Does a trait.py exist, I only have traits.py with s.
|
|
|
Post by fufu508 on Sept 2, 2019 15:55:16 GMT -5
Thanks Andrew. I tried incorporating the excerpt into the script I already have. When I run clips2file in game it's generating last exception about not being able to find the name "PosePackLookup". Any suggestions on what might be causing that? I fully expect it to be a noob mistake on my part Edit:
Here's the updated script. # Based on a version Andrew posted that parses the animation clips in files in the mods folder # http://sims4studio.com/post/121791/thread
import fnmatch import io import os import os.path import struct import zlib import sims4.resources import sims4.commands import sims4.log import paths import services from typing import List, Iterator, Tuple, Callable, Set
logger = sims4.log.Logger('Clip Test')
def read_package(filename: str, type_filter: set) -> Iterator[Tuple[int, int, int, Callable[[], bytes]]]: type_filter = set() if not type_filter else type_filter with open(filename, 'rb') as stream: def u32() -> int: return struct.unpack('I', stream.read(4))[0]
tag = stream.read(4).decode('ascii') assert tag == 'DBPF' stream.seek(32, io.SEEK_CUR) index_entry_count = u32() stream.seek(24, io.SEEK_CUR) index_offset = u32() stream.seek(index_offset, io.SEEK_SET) index_flags: int = u32() static_t: int = u32() if index_flags & 0x1 else 0 static_g: int = u32() if index_flags & 0x2 else 0 static_i: int = u32() << 32 if index_flags & 0x4 else 0 static_i |= u32() if index_flags & 0x8 else 0
for _ in range(index_entry_count): t = static_t if index_flags & 0x1 else u32() g = static_g if index_flags & 0x2 else u32() instance_hi = static_i >> 32 if index_flags & 0x4 else u32() instance_lo = static_i & 0xFFFFFFFF if index_flags & 0x8 else u32() i = (instance_hi << 32) + instance_lo offset: int = u32() sz: int = u32() file_size: int = sz & 0x7FFFFFFF stream.seek(4, io.SEEK_CUR) compressed: bool = sz & 0x80000000 > 0 compression_type: int = 0 if compressed: compression_type = struct.unpack('H', stream.read(2))[0] stream.seek(2, io.SEEK_CUR)
if compression_type not in (0x0000, 0x5A42): continue
def load_func() -> bytes: pos = stream.tell() stream.seek(offset, io.SEEK_SET) data = stream.read(file_size) stream.seek(pos, io.SEEK_SET) return zlib.decompress(data) if compression_type == 0x5A42 else data
if len(type_filter) == 0 or t in type_filter: yield t, g, i, load_func
def get_clips_from_folder(folder: str) -> Tuple[Set[str], int]: folder_clips: Set[str] = set() pattern = '*.package' failures: int = 0 for root, dirs, files in os.walk(folder): for filename in fnmatch.filter(files, pattern): package_path = str(os.path.join(root, filename)) try: package_clips, package_failures = get_clips_from_package(package_path) folder_clips |= package_clips failures += package_failures except Exception as ex: logger.exception(f'Failed to read package {package_path}', exc=ex) failures += 1 return folder_clips, failures
def get_clips_from_package(p) -> Tuple[Set[str], int]: package_clips: Set[str] = set() failures: int = 0 for t, g, i, load_func in read_package(p, type_filter={sims4.resources.Types.CLIP_HEADER}): try: with io.BytesIO(load_func()) as resource: package_clips.add(get_clip_name(resource)) except Exception as ex: logger.exception(f'Bad key: {t:X}:{g:X}:{i:X}', exc=ex) failures += 1 return package_clips, failures
def get_clip_name(resource: io.BytesIO) -> str: resource.seek(0x38, io.SEEK_SET) name_length: int = struct.unpack('i', resource.read(4))[0] name: str = resource.read(name_length).decode('ascii') return name
# convert to base35 (omit the letter l which is too similar to the digit 1) def to_base_n(n, base=35, d="0123456789abcdefghijkmnopqrstuvwxyz"): return (to_base_n(n // base, base) + d[n % base]).lstrip("0") if n > 0 else "0"
# read pose pack pose name http://sims4studio.com/post/128689/thread global PosePackLookup def load_data_into_class_instances_load_posepack_names(instance_manager): for key in instance_manager.types: snippet = instance_manager.types[key] if hasattr(snippet, 's4s_mod_type') and str(snippet.s4s_mod_type).upper() == 'POSE_PACK': for pose in snippet.pose_list: PosePackLookup[pose.pose_name] = (pose, snippet)
services.get_instance_manager(sims4.resources.Types.SNIPPET) \ .add_on_load_complete(load_data_into_class_instances_load_posepack_names)
@sims4.commands.Command('clips2file', command_type=sims4.commands.CommandType.Live) def clips2file(_connection=None): output = sims4.commands.CheatOutput(_connection) clip_names_file = 'c:/tmp/ts4-anim-clips.txt'
mods_folder = os.path.expanduser( os.path.join('~', 'Documents', 'Electronic Arts', 'The Sims 4', 'Mods') ) game_folder = os.path.dirname(os.path.dirname(os.path.dirname(paths.APP_ROOT)))
output(f'Reading mods animation clips.') mods_clips, mods_failures = get_clips_from_folder(mods_folder) output(f'Reading game animation clips.') game_clips, game_failures = get_clips_from_folder(game_folder)
bad_keys: int = game_failures + mods_failures if bad_keys > 0: output(f'Number of clips with keys that failed to read: {bad_keys}')
mods_clip_names: List[str] = sorted(list(mods_clips), key=str.lower) game_clip_names: List[str] = sorted(list(game_clips), key=str.lower)
with open(clip_names_file, 'w+') as f: f.write(f'id_num clip_name\n') id_num = 1 output(f'Writing {len(mods_clip_names)} mods clip names to {clip_names_file}') for index in range(len(mods_clip_names)): f.write(' {0:<7}{1}\n'.format(to_base_n(id_num), mods_clip_names[index])) id_num += 1 output(f'Writing {len(game_clip_names)} game clip names to {clip_names_file}') for index in range(len(game_clip_names)): f.write(' {0:<7}{1}\n'.format(to_base_n(id_num), game_clip_names[index])) id_num += 1 output(f'Done!')
And here is the top of the lastException output (parsed): IME: 2019-08-31 09:00:16 GAME VERSION: 1.54.120.1020 (Windows 64-bit) SCRIPT FILE: clips2file.py, line 118 EXCEPTION: [manus] Error during initialization of service tuning_instance_manager. This will likely cause additional errors in the future. (NameError: name 'PosePackLookup' is not defined)
|
|
|
Post by redrum on Sept 2, 2019 18:31:23 GMT -5
For some reason it keeps giving me the same error that says "no module named 'sims4'" which is weird because it works on my laptop, but not my desktop?
|
|
|
Post by fufu508 on Sept 2, 2019 19:18:56 GMT -5
Hi redrum is this seen when trying to use PyCharm? Or are you seeing it in game lastException file?
|
|
|
Post by redrum on Sept 3, 2019 18:36:35 GMT -5
fufu508 It was showing in Pycharm when I compiled it, and when I imported it to my Mods folder to try it, it didn't work. I got it working now, but I don't know what I did or how lol. Now I'm having trouble with creating a pie menu on the computers. I can add categories and interactions to the phone, but when I attempt to change anything with the computers, ALL the interactions go away.
|
|
|
Post by fufu508 on Sept 7, 2019 9:29:20 GMT -5
redrum Glad to hear you got the script to run in game. I haven't done enough with computer interactions (or with Sims interacting with other objects) yet to offer suggestions beyond the usual: does the lastException file contain any clues?
|
|
|
Post by redrum on Sept 7, 2019 15:21:37 GMT -5
fufu508 it never gives me a LE which is weird.
|
|
|
Post by nikette on Sept 22, 2019 1:34:35 GMT -5
okay so im a newbie and i want to make my career .py files into a PYC file. i just don't understand how, pycharm im finding to be confusing as all hell. i used neia's career builder to make my custom careers. how do i turn my py file i get from that into a pyc? do i copy n paist i mean im completely lost at this currently. do i put my py under scripts? but then what??
|
|