A Hat in Time

A Hat in Time

Not enough ratings
Improved Hookshot Visuals Mod Support Guide
By jlintgod
This guide goes over custom mod support in my mod Improved Hookshot Visuals. By default, all modded weapons do not have hook meshes due to differences in their visuals and how much they change when using the hookshot. However, you can add your own hook meshes, and even add custom sound and particle effects when using the hookshot!
   
Award
Favorite
Favorited
Unfavorite
Prerequisties
Before you start, make sure that you have Improved Hookshot Visuals installed. Of course, you could try to add mod support without it, but I don't think you'll be able to verify that it works correctly or even see it.
https://steamhost.cn/steamcommunity_com/sharedfiles/filedetails/?id=3110999497

You'll also need:
  • A 3D model editor like Blender to make the hook mesh. You probably already have something like this when you made your weapon, but make sure that you know how to use it.
  • Some knowledge about modding A Hat in Time, specifically about storing assets in packages(you already should know this, you made a weapon mod!) and making SoundCues and ParticleSystems. You may also want to know a little about localization if you want to use the localization method to add hook definitions.
  • (Optional but recommended) The example addon. This contains all the code and localization needed to send information about your weapons and hook meshes to the hookshot mod, along with an example weapon that showcases all the features, and assets to help you make other hookshot effects. Bonus points if you can guess where the weapon is from!
    https://steamhost.cn/steamcommunity_com/sharedfiles/filedetails/?id=3119569601
  • (Optional) Any other assets and tools that you need to make a hook mesh and hookshot effects!
  • This guide! It does assume that you're reasonably good at modding, so watch out for that.
Making the Hook Mesh
Using the model editor of your choosing, make the hook mesh(es)! You can make it in a few ways:
  • Detaching the top of your weapon and adding a hook(like with the Umbrella, Gothic Umbrella, and Fishing Rod)
  • Using a different mesh(like with the Pool Noodle)
  • Modeling your own hook mesh(like with the Baseball Bat and Trumpet)
Feel free to be creative and go wild with your hook mesh(es)! However, there are few rules that you may follow if you want your meshes to look and attach correctly.

A reenactment of how I made the hook mesh for the Umbrella. I took the open umbrella mesh, removed the handle, and put a sphere with some extruded claws on it.

Origin Point
Due to how the hook mesh's position is calculated, the hookshot's chain may stick out of it, or the hook mesh may not appear to properly swing around its hook point. You can adjust the origin to hide the chain or create a good pivot point of the hook mesh, but this is up to your own personal preferences.

For my hook meshes, the claw/Umbrella-like ones have their origins set in the center of the claw region, while the Trumpet's has its origin in the center music note.

Orientation
To have your hook mesh stay parallel with the hookshot chain and the weapon shaft mesh, it needs to be going towards the X+ axis, with any geometry closer to the player going towards the X- axis.

You can orient the hook mesh with your model editor before you import it into the mod editor, or orient it within the mod editor/StaticMesh editor, which was done for all of my hook meshes to orient them forward instead of upward.

Materials
The materials for your hook mesh can be the same as your weapon, or they can be a different material. Do note that the material you use will determine a specific setting in your hook definition for the weapon, addressed in a later section.

Once you're done making your hook mesh, export it as a .fbx file, import it into the mod editor, and give it its textures and materials as you would with any other mesh. Make sure it's a StaticMesh as the hook definition only accepts them.
(Optional) Making Hookshot Sound and Particle Effects
Now that you have your hook mesh, it's time to make hookshot sound and particle effects to accompany your weapon! Whether it's water with the fishing rod and pool noodle or getting chased by one of Vanessa's puppets with the Gothic Umbrella, there's a lot of possibilities!


A showcase of all the effects you can make and add. Green indicates a sound, and blue indicates a particle effect.

There are around 3 sound effects that can be made and added:
  • Start: This sound plays when you start using your hookshot. The default sound uses the sound for a completed hat cooldown and Hat Kid's snap when she picks up a yarn ball.
  • Clang/Attach: This sound plays once the hookshot attaches to the hook point. The default sound is nearly the same as the base-game one, except with a slight metal reverb and "woosh" addon.
  • Zipline(internally called Railing): This sound plays while hooked onto a zipline. The default sound uses a better looped zipline sound, some wind and sliding sounds, and a laser sound for imitating a pull towards the zipline.

If you're curious, the alternate Zipline sound has been deprecated and removed, since the alternate Zipline sound for the Trumpet was similar to the default Zipline sound.

And also 4 particle effects:
  • Start: These particles appear when you start using your hookshot. The default particles are the ones for a completed hat cooldown, along with a few additional stars.
  • Clang/Attach: These particles appear once the hookshot attaches to the hook point. The default particles are the same as the base-game one, except with an additional shockwave and a few rainbow bits(TM).
  • Zipline(internally called Railing): These particles, you guessed it, appear while hooked onto a zipline. The default particles are the ones for sprinting, along with some sparkly stars and bokeh effects.
  • Chain: These particles are the chain that connects your weapon to the hook point/hook mesh. The default particles are a chain with an improved material and some slightly visible bokeh particles.

You may create all of your effects on your own, or you may use the template SoundCues and ParticleSystems found in the example addon(linked above.)

Do keep in mind that the hookshot is one of the player's most used abilities along with whacking with umbrellas or baseball bats, some hats, a homing attack, and a few other things. You don't want to create noisy sound effects(like the Gothic Umbrella's loudnow tolerable clang sound and the Trumpet's Heat Gustsnothing zipline sound,) or cause lag with incredibly detailed but complex particle effects. So try to test everything and try to find a good balance with your sounds and particles. Wait? You can't do that??! Nothing's happening??! Uhhh, move on to the next section...
Putting Everything Together
I forgot that everything you've made isn't in a hook definition yet! Once you have your hook mesh(es) and hookshot effects made and saved into a package, you can start adding hook definitions! Each of these contains all the information about your weapon, hook mesh, and hookshot effects that will be displayed while using the hookshot. The definitions can either be passed to the hookshot mod using Online Party commands to its GameMod (a system figured out by SamiSha and implemented in SuperInkLink's Low Health Warnings mod), or they can be passed to it using localization files (which is explained in a later section.) This section and very next one will cover the GameMod method.

NOTE: I would recommend adding the hook definitions with localization files since it's easier to implement and has some other benefits. Please see "Adding Hook Definitions (Localization Method)" for more info. However, you still may need a GameMod so you can reference your hook assets, so make sure you create it!

For your convenience, the system that stores and sends the hook definitions can be found in the following GameMod class. Copy and paste the GameMod below into a new .uc file, optionally renaming the class. If you already have a GameMod(for giving the weapon to the player or for cool weapon effects,) copy and paste the GameMod below(except for the class definition) into your pre-existing GameMod, adjusting and merging functions if necessary; you'll most likely mainly have to deal with the first four functions/events for when the mod is loaded and unloaded, when the level is loaded, and when an Online Party command is received(this is mainly used for reloading hook definitions after toggling mod support.)

GameMod for Mod Support
class Jlint_GameMod_HookshotQOL_Example extends GameMod; // A reference to the hookshot mod's GameMod, for sending commands var GameMod HookshotGameMod; // Definition of a... hook definition struct. struct HookDefinition { var Class<Hat_Weapon> WeaponClass; var StaticMesh HookMesh; var SoundCue HookshotStartSound; var SoundCue HookshotClangSound; var SoundCue RailingSound; var ParticleSystem StartParticles; var ParticleSystem ClangParticles; var ParticleSystem RailingParticles; var ParticleSystem ChainParticles; var bool SupportsMaterialInstance; }; // The list of all of our hook definitions. var Array<HookDefinition> HookDefinitions; // If you incorporate this GameMod into your own, worry mainly about the first four // functions *cough* I mean, events here. Everything else is unique enough to // not exist in your GameMod. function OnPostInitGame() { HookshotGameMod = GetJlintMod(); InitDefinitionList(); } event OnModLoaded() { if (HookshotGameMod == None) { HookshotGameMod = GetJlintMod(); if (HookshotGameMod != None) InitDefinitionList(); } } event OnModUnloaded() { DeloadDefinitionList(); HookshotGameMod = None; } // If you have Online Party functionality with your weapons, then that's pretty neat. You'll have to // figure out how to add this code in your event though. event OnOnlinePartyCommand(string Command, Name CommandChannel, Hat_GhostPartyPlayerStateBase Sender) { local string cc; local Array<string> ccSplit; local Array<string> params; cc = String(CommandChannel); ccSplit = SplitString(cc, "_"); if(ccSplit.length != 2) return; // Invalid command length if(ccSplit[0] != "JlintHookshotQOL") return; CommandChannel = Name(ccSplit[1]); // Isolates the command switch(CommandChannel) { case 'ReloadDefinitions': if (HookshotGameMod != None) { DeloadDefinitionList(); // Deload the definitions just in case InitDefinitionList(); } break; } } // The rest of these functions are important, but more specific. Don't worry about // merging them into your own GameMod functions because you probably don't have them. function InitDefinitionList() { local HookDefinition Definition; foreach HookDefinitions(Definition) { ParseDefinition(Definition); } } function DeloadDefinitionList() { local HookDefinition Definition; local string command; if (HookshotGameMod != None) { foreach HookDefinitions(Definition) { command = string(Definition.WeaponClass); HookshotGameMod.OnOnlinePartyCommand(command, 'JlintHookshotQOL_RemoveHookDefinition', None); } } } function ParseDefinition(HookDefinition Definition) { local string command; local int i; if (HookshotGameMod == None) return; command = Definition.WeaponClass $ "|"; command $= (Definition.HookMesh != None) ? (Definition.HookMesh.GetPackageName() $ "." $ Definition.HookMesh $ "|") : "|"; command $= (Definition.HookshotStartSound != None) ? (Definition.HookshotStartSound.GetPackageName() $ "." $ Definition.HookshotStartSound $ "|") : "|"; command $= (Definition.HookshotClangSound != None) ? (Definition.HookshotClangSound.GetPackageName() $ "." $ Definition.HookshotClangSound $ "|") : "|"; command $= (Definition.StartParticles != None) ? (Definition.StartParticles.GetPackageName() $ "." $ Definition.StartParticles $ "|") : "|"; command $= (Definition.ClangParticles != None) ? (Definition.ClangParticles.GetPackageName() $ "." $ Definition.ClangParticles $ "|") : "|"; command $= (Definition.RailingSound != None) ? (Definition.RailingSound.GetPackageName() $ "." $ Definition.RailingSound $ "|") : "|"; command $= (Definition.RailingParticles != None) ? (Definition.RailingParticles.GetPackageName() $ "." $ Definition.RailingParticles $ "|") : "|"; command $= (Definition.ChainParticles != None) ? (Definition.ChainParticles.GetPackageName() $ "." $ Definition.ChainParticles $ "|") : "|"; command $= string(Definition.SupportsMaterialInstance ? 1 : 0) $ "|"; HookshotGameMod.OnOnlinePartyCommand(command, 'JlintHookshotQOL_AddHookDefinition', None); } function GameMod GetJlintMod() { local GameMod mod; if (HookshotGameMod != None) return HookshotGameMod; foreach AllActors(class'GameMod', mod) { if(!mod.IsA('Jlint_GameMod_HookshotQOL')) continue; return mod; } return None; }
Adding Hook Definitions (Old Method)
You may have noticed two things:
  • We're in a new section, mainly because of section text limits or whatever.
  • DefaultProperties was missing in the GameMod.

This section will go over actually adding your hook definitions into DefaultProperties, similarly to the example mod/template:
defaultproperties { HookDefinitions(0) = {( WeaponClass = class'Jlint_Weapon_FireworkRocket', HookMesh = StaticMesh'Jlint_HookshotQOL_Example_Pack.FireworkRocket_HookshotEnd', HookshotStartSound = SoundCue'Jlint_HookshotQOL_Example_Pack.Hookshot_ChainStart_Firework', HookshotClangSound = SoundCue'Jlint_HookshotQOL_Example_Pack.Hookshot_Clang_Firework', RailingSound = SoundCue'Jlint_HookshotQOL_Example_Pack.Hookshot_Railing_Firework', AltRailingSound = SoundCue'Jlint_HookshotQOL_Example_Pack.Hookshot_Railing', StartParticles = ParticleSystem'Jlint_HookshotQOL_Example_Pack.Hookshot_StartParticles_Firework', ClangParticles = ParticleSystem'Jlint_HookshotQOL_Example_Pack.Hookshot_ClangParticles_Firework', RailingParticles = ParticleSystem'Jlint_HookshotQOL_Example_Pack.Hookshot_RailingParticles_Firework', ChainParticles = ParticleSystem'Jlint_HookshotQOL_Example_Pack.Hookshot_ChainParticles_Firework', SupportsMaterialInstance = true )} }

Each variable/property in the definition corresponds with a specific object, and some are optional, allowing you to remove from the definition to use the default settings:
  • WeaponClass(Class): This is the class of your weapon. Obviously, this is required for a hook definition to be identified by the hookshot mod.
  • HookMesh(StaticMesh): This is your hook mesh. Technically optional, but what would be the point?
  • HookshotStartSound, HookshotClangSound, RailingSound, AltRailingSound(SoundCue): All of these are your hookshot sounds. All of them are optional. If you want to use the defaults, remove these.
  • StartParticles, ClangParticles, RailingParticles, ChainParticles(ParticleSystem): All of these are your hookshot particles. Again, all of them are optional.
  • SupportsMaterialInstance(Bool): This isn't important in the above example, but it's important for other weapons. For example, this is used for the Umbrella to give the hook mesh the right visuals, including stickers and Bow Kid's umbrella color. If the hook mesh uses the same material as the weapon and has ideal UVs, this should be set to true. Otherwise, remove it or set it to false.

If you have or need more than one hook definition, create a new one by duplicating the first definition, increment the index at the beginning of it(the first one should be 0, then the second should be 1, etc.), and change your variables with new values.

Once you've added all the hook definitions, compile and cook the mod. If you see your hookshot's new visuals and sounds in-game, congratulations!
Adding Hook Definitions (Localization Method)
Instead of using GameMods and Online Party messages, you may also use the newer localization file method to add hook definitions. There are several advantages to using them:
  1. They're easy to add: Instead of adding and merging all sorts of functions to your GameMod class(or creating one if you don't have one), you can simply create a localization file. You will still need to reference your assets, but you can do it in a easier and less specific way.
  2. They're resistant to updates: If a new feature is added or removed, you don't have to update your mod with new content that utilizes it. You don't even have to update your localization file, unlike the previous method where you would have to update your GameMod with new functions that send the right Online Party messages!
  3. It can reference other mods: It depends on the weapon mod(s) and what new content you need for them, but you can make a mod that adds hook definitions to other weapon mod(s)! Since your assets will be directly loaded from text rather than an asset path converted to text, you can reference nearly everything just as long as it's already referenced in a script class and you know the exact name of the asset.

To get started, create a "hookshotqol(dot)int" file in your localization directory(Localization\INT) and add this to it:
[Jlint_HookshotQOL_Example.Jlint_Weapon_FireworkRocket] HookMesh = Jlint_HookshotQOL_Example_Pack.FireworkRocket_HookshotEnd HookshotStartSound = Jlint_HookshotQOL_Example_Pack.Hookshot_ChainStart_Firework HookshotClangSound = Jlint_HookshotQOL_Example_Pack.Hookshot_Clang_Firework RailingSound = Jlint_HookshotQOL_Example_Pack.Hookshot_Railing_Firework StartParticles = Jlint_HookshotQOL_Example_Pack.Hookshot_StartParticles_Firework ClangParticles = Jlint_HookshotQOL_Example_Pack.Hookshot_ClangParticles_Firework RailingParticles = Jlint_HookshotQOL_Example_Pack.Hookshot_RailingParticles_Firework ChainParticles = Jlint_HookshotQOL_Example_Pack.Hookshot_ChainParticles_Firework SupportsMaterialInstance = true

The localization above will serve as a template for a single hook definition, and it can be duplicated if you need more definitions. Each key(Key = Value) represents a specific variable/object, and some keys are optional, allowing you to remove from the definition to use the default settings:
  • HookMesh(StaticMesh): This is your hook mesh. Optional if you are using other custom sounds or effects. Remove if you don't have a hook mesh.
  • HookshotStartSound, HookshotClangSound, RailingSound(SoundCue): All of these are your hookshot sounds. All of them are optional. If you want to use the defaults, remove these.
  • StartParticles, ClangParticles, RailingParticles, ChainParticles(ParticleSystem): All of these are your hookshot particles. Again, all of them are optional.
  • SupportsMaterialInstance: This isn't important in the above example, but it's important for other weapons. For example, this is used for the Umbrella to give the hook mesh the right visuals, including stickers and Bow Kid's umbrella color. If the hook mesh uses the same material as the weapon and has ideal UVs, this should be set to true. Otherwise, remove it entirely; the mod will consider it to be true if its key exists, even if set to false.
Unlike the GameMod method, there are some important things to change with your definitions:
  1. For each value in your key, remove the type at the beginning and single quotes. For example,
    StaticMesh'Jlint_HookshotQOL_Example_Pack.FireworkRocket_HookshotEnd'
    would turn into
    Jlint_HookshotQOL_Example_Pack.FireworkRocket_HookshotEnd
  2. Each definition's header should be changed to reflect its weapon class. It should be formatted like this:
    [ModFolderName.WeaponClassName]
    Note that the mod folder name before the weapon class name is important so the hookshot mod can load the right weapon class from the right mod.

After you make your localization file, one other thing that you need to do is actually reference the assets you're using. The game can only dynamically load objects that have been cooked, and the game/editor only cooks assets that are referenced in maps, script classes, and a few other things, but usually not those references as simple text in a localization file. You'll need to reference everything in class to ensure that it can be loaded in-game.

How you reference your assets is up to your own preference. You can reference them in a filler class, your GameMod class, or even your weapon class. For example, in a GameMod class:
var Array<Object> ReferencedHookshotAssets; defaultproperties { ReferencedHookshotAssets.Add(StaticMesh'Jlint_HookshotQOL_Example_Pack.FireworkRocket_HookshotEnd') ReferencedHookshotAssets.Add(SoundCue'Jlint_HookshotQOL_Example_Pack.Hookshot_ChainStart_Firework') ReferencedHookshotAssets.Add(SoundCue'Jlint_HookshotQOL_Example_Pack.Hookshot_Clang_Firework') ReferencedHookshotAssets.Add(SoundCue'Jlint_HookshotQOL_Example_Pack.Hookshot_Railing_Firework') ReferencedHookshotAssets.Add(ParticleSystem'Jlint_HookshotQOL_Example_Pack.Hookshot_StartParticles_Firework') ReferencedHookshotAssets.Add(ParticleSystem'Jlint_HookshotQOL_Example_Pack.Hookshot_ClangParticles_Firework') ReferencedHookshotAssets.Add(ParticleSystem'Jlint_HookshotQOL_Example_Pack.Hookshot_RailingParticles_Firework') ReferencedHookshotAssets.Add(ParticleSystem'Jlint_HookshotQOL_Example_Pack.Hookshot_ChainParticles_Firework') }

Once you've added all the hook definitions, compile and cook the mod. If you see your hookshot's new visuals and sounds in-game, congratulations!
Troubleshooting
This section contains some "common" troubleshooting questions. If you have any questions or suggestions of your own, feel free to DM me on Discord(@jlintgod) or leave a comment on this guide for me to figure out and add to this guide. If it's something a little more general (like importing or creating sounds and particles,) you may be able to ask the #modding-help channel on the A Hat in Time Discord Server[discord.gg].

Troubleshooting
My hook mesh/hookshot effects don't appear in-game, and I'm using the GameMod method to add definitions!
First, make sure that you launched the game directly(rather than through the mod manager) or have copied Improved Hookshot Visuals into your local mod folder. Ensure that the mod is enabled in its config menu, along with the setting for mod support.

If the hook or its effects still doesn't appear, some possible causes:
  • Actually compile and cook: You probably didn't miss this one.
  • Missing WeaponClass property: Your definition(s) might not have a proper WeaponClass variable set. Either add it in or change it to a proper class(class'MyWeaponClass'). Also, don't set it to a weapon that exists in the base game, as the definitions already in the hookshot mod have priority.
  • Malformed DefaultProperties: If you're getting compiler warnings related to variables in DefaultProperties, your hook definitions might be broken. Double-check to see if your definitions have misplaced or missing commas (each variable, after assigning it and excluding the last one, should have a comma after it) or any other typos. If you have trouble finding them, the compiler warnings should have line numbers that can help.
  • Multiple GameMods: If you already have a GameMod and simply copied the GameMod for mod support into a new file, the modding tools may not identify the correct GameMod to use. See the previous section for info about merging the GameMods.
  • "Warning: Using a non-qualified name (would have) found: StaticMesh [MeshName]": This is a rare warning that I found when I used a base-game Static Mesh in a group (PackageName.Group.MeshName) as my hook mesh. This is currently not supported (at least in the GameMod method), so if you encounter this warning, you may have to use a Static Mesh without a group.

My hook mesh/hookshot effects don't appear in-game, and I'm using the localization method to add definitions!
Again, make sure that you launched the game directly(rather than through the mod manager) or have copied Improved Hookshot Visuals into your local mod folder. Ensure that the mod is enabled in its config menu, along with the setting for mod support.

If the hook or its effects still doesn't appear, some possible causes:
  • Actually compile and cook: Although it's technically less important since you have a localization file, you still need your assets cooked so they can be loaded.
  • Localization errors: If the hookshot mod finds localization errors when it attempts to load each definition's keys, then it will be unable to load them. You can check this by enabling debug logging in the config (coming in a later update) and checking the console for your weapon. If you see something like this...
    (screenshot placeholder, it's not like you can even see the errors anyway)
    ...then you have localization errors. Check that everything is typed and spelled correctly, and most importantly, check the header of the definition to see that it's formatted correctly.

My hook mesh appears, but not some or all of my hookshot effects!
Your DefaultProperties or localization may be malformed(see above) or your mod configs may be set to use generic hookshot effects. Another possible cause is other mods that change the sounds and/or particles of the hookshot independently of the hookshot mod.

My weapon uses a hook mesh and/or hookshot effects from another weapon I have!
You might have not...
  • Changed the definition index or localization header when duplicating definitions
  • Made sure each definition has a unique WeaponClass value(in the GameMod method)
  • Kept each localization header separate, causing a definition to have multiple duplicate keys
...resulting in definitions getting overridden.

My hook mesh doesn't look right! It faces the wrong way or doesn't attach to the hook point!
This can have a variety of causes, but your hook mesh is misplaced or is facing the wrong way, you might have to adjust its origin point and/or rotation, as seen in the previous section about making it.

My hook mesh seems to come out of nowhere and/or my hookshot chain is missing!
This is most likely caused by a missing socket (specifically 'HookshotPoint') on your weapon mesh that both the hook mesh and hookshot chain rely on for positioning. While the hookshot mod can detect if the socket is missing and use the player position instead for the hook mesh, you should still create a socket (mainly for the chain). Try adding it in the Skeletal Mesh editor yourself, or consult a weapon making guide like the one below.
https://steamhost.cn/steamcommunity_com/sharedfiles/filedetails/?id=1778166971

Something else not listed here!
See the intro paragraph about asking me or others for help.
The End
Thank you for following along with this guide! I hope to see people utilize the mod addon support in their own weapons, and maybe create some cool stuff with it!