#Help with modding
1 messages Β· Page 2 of 1
Yeah, not working before and after breakthrough
No error messages in devmode's log, and nothing unusual in combat log
Not sure if its correct, but the buff displays only in breakthrough bonuses, not in the art itself
Just like stats do
I have Multiple technique crystals, is there a better way to add fallback techniques then to just copy paste them from the previous ones?
Sadly no
urg, thats gona look so awkward in tho code... well I only need to see it once..
is there a limit of techniques that can go in to the fallback?
there seams to be, I can put in the 7 from my T1 Crystal but if I try to put in the ones from my T2 Crystal then my inventory imediatly fades to black and throw me out when I try to open it
it was, my smooth brain forgot to add one of the the techniques... odd thing is the crystal where that technique is in was fine
posted it
another question, can we reach current game flags with modAPI? I've seen how the mundane ascension mod does it, using a .tsx with ModScreenFC to reach ModReduxAPI that can select stuff like player current realm
If you want to actively read, so run some code, then I has to be attached to a screen like the example mod does
Otherwise just use them in conditions
ah, got it
so, I can like use an invisible screen after a trigger like combat end to update stuff, for example?
There's after combat end hooks
yeah, I'm imagining using them to summon the screen that has the code
just not sure how it would interact with current context, if you need to set another screen after it
ah, thats cool
thanks again
I'll make a little pause in modmaking to experience new cloud, thanks for the update
is there a way to make a image aper in front of the char? the overlay only shows the image where the char is but I want to create a bubble around it
Currently no but it's an effect I want to add too so feature request it!
I feel that
hello every one I'm starting up on trying to make a mod,
might need help so I'm just saying hi first π .
Good luck!
Looking forward to the result π
is the SimpleWelcomeScreen example outdated or im missing something?
Can you give more context ?
sorry trying to figure out screens so i thought ill just give the example a try so i copied 1:1 from the example to index.ts
copied from docs\advanced-mods\adding-screens.md
i assumed it was outdated because 'Click' is not a valid arg for playSfx() at least from my editor might be wrong though not familiar with js and ts
inb4 the project setup was allg
no errors or anything
with 'Click', I think it's just a bad example generated by AI. And the error with the return value is because your file is ts and not tsx
you can check the list of acceptable values, you even have part of it displayed in the warning
yeah im aware of that
oh wow okay
Also it needs to be in a .tsx fike
Not a .ts
Bug it, I'll update the docs when I'm back
literally what I wrote π
also in the example inside the return statement player.name doesnt exist and GameButton expects 2 args but only got 1
already changed the player.name to player.forename so thats good
'GameButton' cannot be used as a JSX component.
Its type 'GameButtonFC' is not a valid JSX element type.
Type 'ForwardRefRenderFunction<HTMLButtonElement, ButtonOwnProps & Omit<ButtonBaseOwnProps, "classes"> & CommonProps & Omit<...> & { ...; } & { ...; }>' is not assignable to type '(props: any) => ReactNode | Promise<ReactNode>'.
Target signature provides too few arguments. Expected 2 or more, but got 1.ts(2786)
Okay, it should be better to look at the sources for the example mod mundane ascension
That has a screen
alright thank you!
Contribute to Lyeeedar/AfnmExampleMod development by creating an account on GitHub.
Oh btw is it possible to modify an existing screen using the api? i gave a quick look through docs it seems to suggest only creating new screens.
if yes,
do you have an example of modifying existing screen? if no, if you dont mind could you point to me the folder or file that handles the screen for crossroads or other exploring locations,
inb4 been scouring node_modules cant seem to find it prolly cus im blind
been scouring the non-minified code too prolly and cant find it prolly cus im definitely blind
You can't currently modify an existing screen no
ah dang hahah welp
For the exploring locations, that's SelectedLocationDialog opening an event, so EventScreen
What did you want to do?
automating the explore button xd
was thinking of adding another button or modify the current explore button
So currently that isn't possible through the mod api
Put in a feature request though, I'll add more API extension points
Add new buttons to location dialogs maybe
Alrighty thanks!
alright already made a request, thanks for answering my questions so openly btw if i knew you were this cool i wouldve bought this game at full price xd looking foward to full release!
No truer words have been spokenπ―
Yo yo, is there a way to check if a certain item has been consumed? Like adding a flag when you consume it from the inventory, or something?
Currently not. ATM the only thing we do that with is the stat pills and those write their own flags directly
Is there a particular one you'd like to track? Feature request it
I wanted to lock breakthroughs behind the consumption of a pill, but I could just make it an advancement material or check if the player has a certain amount of stats
Thanks :)
On that note, where could we possibly find portraits and such in the style of AFNM?
Currently you'll have to use models like nano banana or qwen image (or sdxl) with a reference image for style transfer
I don't have a easy to share workflow atm
why does this happen and how do i resolve it?
Got a GitHub repo I can look at?
Sorry, just got back home
I also changed the names to include both .png and without, didn't change anything
Also tried the example vigorpill from the mod documentation and it didn't work, same error
I think you have it set to private
should be public now
yep i see it, ill ake a look now
ah i see the issue
your relative path was wrong
given you moved it to a subfolder, you need to path out one extra level
np, happy modding π
Are player sprites hardcoded?
Here you go, thanks for the quick response π
Is the only way to upgrade items by swapping them to new ones with the different stats? I'm thinking of condensation arts that can evolve, but adding more arts increase the QC to CF breakthrough art list, flooding it with unknown arts
There is also the issue of unequipping current art and force equipping the new that I'm not sure about how to do, even if that is the only way
you can hide condensation art from bt list
I asked Lyeeedar for this feature and he added it iirc
Neat
This should solve part of my problems, now only lost about force equipping something and deleting the previous art, need to dig some more
I'm also having a strange hover bug/glitch when I hover over a custom art tooltip for some reason, but this only happens in 1 line of the breakthrough text
I'm not sure about that, need to wait for Lyee
I'll post the log in devmode here in a few min, could be something common
here, its a test condensation art I'm using as a base , when I hover over the top one (below Qi Vessel) the game blinks black and returns this in the devlog
when you hover over the art itself in the slot
its mostly to test, and for some reason it also has one of its effects hidden, it gives ripple/round
it probably should be combat buff and not cond art effect btw
i'm about defense
smth like: on combat start gives defensePerRippleBuff
and defensePerRippleBuff contains defense buff per ripple
i'm not sure if this is related to the problem, but the current version looks weird
ah, it's already combat effect, sry
i'm blind
Current effect inbattle seems to be working as intended as well
I created a buff with 3 effects that this cond art adds
The max hp, def scaling on ripple and ripple/ round
It seems to be complaining about this buff stat field when I added it to the art (or not lol, changed this and nothing changed, idk idk)
this
Ah, its the same thing I placed at the other buff that is hidden
Make a bug and share your mod. I will fix
btw, after rework all combat buffs should be protection, not defense, iirc
I'm still not very sure about difference, but it seems like defense is something you get in various ways before the fight, and protection is something you get during the fight
Defense is a flat number, so can be used to provide realm specific damage reduction (e.g. on equipment). Protection is a percentage so works the same no matter the realm, so best for techniques that aren't supposed to be realm gated
so protection is more correct in this option?
and does +X% defense work the same as before?
+X% of power to defense i mean
It's better now because the damage reduction is best on realm, not hp
and now it makes sense to invest in defensive staff; previously, reaching the cap was too easy
done
also, it seems that condensation art isn't stored as an item (equipped) like others (clothing,flame,artifact,talisman...)
maybe thats why we can't directly equip it, but it changes when we change it in breakthrough screen
managed to hide the other arts breakthroughs (for the evolved higher realm versions) by changing the created breakthrough hidden property, but to swap an art it seems I need to change the state of the breakthrough, wherever it is stored at
there is also the issue of transferring enchantment, even if it is done
is it possible to reuse assets already in the game and how to do so? was trying to add a test item using an existing icon.
yeah, gimme a sec
u can use smth like this
ah great, thank you!
get the object you're interested in from gameData and use its properties with an image
Hi, i tried adding a Healer to the Yinying Mine, but the healer icon does not appear. the mod loads without errors.
i used this code to do this: window.modAPI.actions.addBuildingsToLocation('Yinying Mine',[{ kind: 'healer' }]);
what am i doing wrong?
i guess the name is wrong, how do i find the real name of a location?
i found the real name in the non minified code, its called 'Spirit Ore Mine'.
is there a way to find these information in the ingame dev console?
Currently no. You'd have to just look at the non minified code or print the locations from your mod to inspect
ok thank you π
what image formats are accepted by the mod api? it seams not to like webp.
Webp should work. Gif, PNG, jpeg also should work
I have a image as png and webp in the same folder, the png works without problem but it cannot find the webp
Raise a bug about that please
The mod api now handels webp's in the code editor but when I try to build I et this error for all my webp's
any solutions?
I added a fix to the main branch
You might want to fully update yours to ensure it works
If that doesn't work, get the changes to webpack
worked after manualy updating the webpack file
also is there a way to create a new tab for techniques?
just throwing them all in neutral makes it a bit cluttered
Currently no, but I may add something
Is there something specific I need to do with a location to use it as a root location?
const Root_test: RootLocation = {
locationName: 'Essence Chamber (WIP)',
condition: '1',
}
That alone dos not put it on the map for me.
its already added and acceseble with a tp
when inside that location, pressing travel sends me back to the sect
Oh it's on the map, but there's a bug
That'd different
Bug it, please provide the mod
kk
It seems like when using custom sprites, (re)starting the game is missing the sprite from the main screen overview. I'm not sure if I'm doing something wrong with my mod (which gives neither errors when building, nor problems when starting a new character or when actually in the game or returning to the main menu after mods were loaded) or if its a basic problem with the game loading the mod after the sprite of the last continue option gets displayed when starting up for the first time.
Is there a way to refresh the sprite after loading mods perhaps? Or to somehow affect the loading order.
ah, probably load order issues (its caching before the mod is loaded). Bug it
ok thank you
Just tried uploading my mod. It does seem to have troubles with the image paths.
The build .zip file works fine if I add it locally in the mod folder, but does not find the images when going through steam/mod uploader.
I'm importing the images in index.ts like this:
import { PlayerSpriteImages, PlayerSprite } from 'afnm-types';
//importing female sprite images
import fopFemale1base from '../assets/female/fopFemale1base.png';
import fopFemale1agg from '../assets/female/fopFemale1agg.png';
import fopFemale1def from '../assets/female/fopFemale1def.png';
import fopFemale1hit from '../assets/female/fopFemale1hit.png';
import fopFemale1off from '../assets/female/fopFemale1off.png';
import fopFemale1sup from '../assets/female/fopFemale1sup.png';
import fopFemale1uti from '../assets/female/fopFemale1uti.png';
Then using them:
window.modAPI.actions.addPlayerSprite({
id: 'fopFemale1',
name: 'FOP Female 1',
gender: 'female',
sprites: {
base: fopFemale1base,
aggressive: fopFemale1agg,
defensive: fopFemale1def,
hit: fopFemale1hit,
offensive: fopFemale1off,
support: fopFemale1sup,
utility: fopFemale1uti,
}
});
Which seems to work just fine locally.
The mod uploader tells me the following, which I find odd:
[22:56:30] Selected ZIP: faces-of-plenty-1.0.0.zip
[22:56:30] Extracting mod information from ZIP...
[22:56:30] No mod.js found in ZIP file or could not extract metadata
The inagme description of what it loads / paths seem to be identical with both adding the build .zip file locally into the mod folder and with subscribing to steam, except that one shows the sprite, the other is missing the sprite as if it could not find the image.
Any ideas?
Can you share the repo?
holy moly, I try π
Can confirm, the mod itself is missing all it's content
Not sure why, but I'd need the repo to trace it through from the start
https://github.com/MelaireGIT/afnmfop
Made a repo, I think, it's uploading things to this new branch.
Oh the mod you've uploaded is fine, the issue is game end
but locally it works? O.o is it using steam mods differently than local mods?
Yeh that's what I need to look into
Given the other workshop mods work fine
So something about your mod is breaking the laoder
But I'll fix it
In the morning π
take your time. I'm already happy that you provide so much help β€οΈ
I have a fix, will push it to the beta branch in the morning
Thanks
its on the beta branch
will do a proper release (and you can make your mod public) later this week
tried uploading with Mod Uploader 1.4.4, which still fails. There is a version 1.4.5 showing up in the modloader bar at the top, but when I select download it fails hanging at 0% permanently. Not sure if that would even fix the issue, but is there a way to manually grab 1.4.5?
the download just never goes beyond 0%, no error or even timeout.
as for the mod itself it gives the following output at the debug console
[12:52:56] Editing workshop item: Faces of Plenty
[12:52:57] Opening file selector for ZIP...
[12:53:00] Selected ZIP: faces-of-plenty-1.0.0.zip
[12:53:00] Extracting mod information from ZIP...
[12:53:00] No mod.js found in ZIP file or could not extract metadata
Found the GitHub release, thanks. Same error though sadly.
No mod.js found, that's not ideal
Mind sharing the mod zip you are trying to upload?
Btw I love your mod, I'm using it for my test playthroughs π
https://limewire.com/d/662bn#0Aa04Vv8K3
still the same as from the github branch, but I uploaded it here directly
Ty, definitely the uploader thats at fault. I'll look into it
hi, just checked again, while the mod uploader still gives me the same error, in the game it seems to work now. At least I no longer have the local version added and it's properly under the loaded folder from steam. Might just be an odd thing with the loader
the thing I noticed that's different compared to other mods, is that icons etc. in other mods are loaded with a file:///.. path
while my images load with a mod://faces-of-plenty-... path
As I have no other sprite changing mods to compare with, not sure if that's the difference.
I will test some more ingame if everything is working as expected and if I see no other problems ingame, I turn the mod from Unlisted to public later today. Thanks again.
The mod path is correct
@crimson gazelle Download 1.5.1 (https://github.com/Lyeeedar/ModUploader/releases/tag/v1.5.1) and it should fix the uploader
Around when will the updated non minified Code be relesed? I am interested to 'borrow' some things from the new weapon system
after lf releases
but you can use all the new mecahnics, they are in the types
guess I will have to actually try and not just 'borrow' from your code
happy to share the stuff you want to see if you request
but i wont psot the whole zip as its riddled with spoilers
I mean If you can share some example code for the new puppets and the tempering, that would be great
here is how its hooked up. Very simple really, its a trigger + internal state
what is the curent afnm-types version? 0.6.38?
got it now, thx
and may have some unstable automaton code : D
if you have time of course
thank you : D
This will work well with my summon techniques, once I compleatly remade them that is
is there a hard cap for the amount of guardian bufs?
Nope
good to know, I will try to not over do it
Should be fine, they stack multiplicatively so unless you give them insane hp shouldn't be op
just took a look, is the 1.5.1 version supposed to have mod.uploader-1.4.5 files?
has different hashes, so maybe just forgot to rename them?
works like a charm now in the debug console β€οΈ
1:38am here so I test it properly tomorrow, looks good though
@outer jay Is there a way I can use base-game ingredients in modded recipes? Looking through exposed data and it doesn't seem like they're available unless I'm missing them.
Yeh, look then up by name from the items in the mod.data
So like mod.data.items["Lesser Spirit Grass"]
Cool, that looks to work, thanks.
Note that goes for all base game assets
And mod assets for that matter
Just look then up from the data maps
For mods if you want to use another mods resources you have to ensure yours is loaded after the other one
Where are data maps for base game stuff? I was just going to load up game and look for the item, is it a devmode thing?
@outer jay For the onCompleteCombat hook, is it possible to get the creature (more specifically the drop table) that was being fought? As far as I can tell that doesn't seem to get exposed.
ah yeh, currently you get given the event that spawned it (which contains the enemies) but not the specific enemy entities you fought. Request it, should be an easy add
Cool, thanks, will do. Also, do you have any plans to implement a way for mods to add (Or add to an existing options area) an in-game config menu (Like for the user to tweak variable values to how they want them) or can I request that too?
oh thats a good idea. Yeh request it
@outer jay Can you confirm you managed to get the registerOptionsUI working? Trying to test it just now with the documentation example and tried a few tweaks but all result in same console error:
Error occurs when hitting the cog icon, screen goes full black with no controls.
Darn no I didn't test
Mind sharing your mod?
I'll take a look
Ideally, the mod code you have too
This is the mod, don't actually have the raw code now as I've moved onto testing the onCompleteCombat stuff, but it was 1:1 with the jsdoc example with some additional imports + changing the index to .tsx to get it to compile.
I think also I should've been more clear in my feature request for the onCompleteCombat stuff since it doesn't do what I needed still, I needed to get the items that were dropped from the fight, but with this I can just get what can be dropped. Can still technically do what I wanted to do but would be good if we could get the exact items dropped from the combat.
ill take a tstab
ah feature request THAT too π
again, and easy add
Just thinking on from what I was testing, the technique shards aren't part of any drop tables but are of course dropping from combat, didn't include that in the request but if those can be included as well that'd be good, unsure how those drops are decided.
yeh add it to the request too
Fix to the config will be in the nightly
@outer jay Just want to confirm, I'm trying to add a new breakthrough for Mundane realm, but I can't get the new breakthrough item I've added to be accepted for the Pill slot. Looking at the non-minified code for 0.6.22 it looks like it's hardcoded to only accept the True Awakening Pill so i think that might be the issue, but unsure if it's changed since that update.
thers a new field:
export const trueAwakeningBreakthrough: Breakthrough = {
allowedSlotItems: {
awakeningPill: [trueAwakeningPill.name],
},
name: 'True Awakening',
description:
'The first step that any mortal must take on the path to immortality. With the use of the True Awakening Pill, reshape your body and soul to accept Qi into its very essence.',
requirements: [
(args) => ({
done: args.breakthrough.mundane?.pill === trueAwakeningPill.name,
preview: (
<GameTooltip
provider={() => <ItemTooltipWithLocation item={trueAwakeningPill} location={``} />}
>
<Box display="flex">
<Typography fontSize="120%">
{parseTooltipLine(t('<itm>{itemName}</itm> in the Pill slot', { itemName: trueAwakeningPill.name }))}
</Typography>
</Box>
</GameTooltip>
),
}),
],
totalRequirements: 1,
getNumDone: (args: RequirementArgs): number => {
return args.breakthrough.mundane?.pill === trueAwakeningPill.name ? 1 : 0;
},
physicalStats: {
flesh: 1,
},
socialStats: {
lifespan: 30,
charisma: getBreakthroughCharisma('bodyForging', 1),
},
};
allowed slot items
add that to your breakthrough and it should populate
Did that, even threw it in all of them to just make sure, but it still doesn't show up.
by doesnt show up, what do you mean?
Breakthrough is there, but pill doesn't show as an option.
have you added it to the inventory?
It is in inventory as well.
Raised now, just did a quick test with switching breakthrough to body forging and it does seem to work, so guessing it a hardcode thing.
@outer jay Does the API expose what the enemy stances are during combat rounds? Trying to make a technique that has conditional logic based on what the next technique the enemy is going to do but don't think I can grab that information.
No there's no way to read that atm
Ah, alright. I'll throw a request in.
I would be interested in making new schools if we can get an addElement method exposed on the ModAPI.
Any ideas on why my technique isn't loading? Trying to just get a proof of concept on body forging.
Ah, I'm not auto-generating the technique item
Raise a bug
It should still be addable if you do add all techniques
Confirmed learning all techniques makes it available for testing.
@outer jay How are request boards setup? Trying to make a mod that increases the amount of quests that can be taken at once but, unless I'm missing it, that's not currently possible with the API?
Ah currently you need to override the building itself. Throw in a request I'll add an api
is there a way to see the structure of backgrounds ?
They are in the newgame.ts file in the non minified code
i cant seem to find that file
data/newGame.ts
i dont have a data folder
can somne help me on why birth background appears twice ?
it seems that the child background did not appear to work
or is the child background only meant to apply physical stats with a limit to each ?
They are keyed off their name. Because you have the same name it appears twice
Likely the same issue for the next one. You need to give it a unique name
oh thx
had an idea for a classic wuxia pill where you get a powerful boost for a while but you pay for it dearly afterwards. the buff/debuff cycle comes out pretty easy so far. but can't find a way to reduce lifespan as well.
is combatItem only temporary buffs or there's a way to make the change permanent?
how/where can I do that? fairly new here
thank you, much appreciated
is there anyone that can explain how to add custom player character sprites for people not good with modding 
you don't need a mod for this
ngl ive never clicked this tab
in hooks.onReduxAction it says I can't modify a read only property of some inner state, how do I even modify the state if I can't do that?
hmm had to create a new object from existing one with just the specific field modified... so much for trying to keep the callback simple
well, it' s a shallow copy I think so shouldn't be that big of a deal
Yeh redux you always need to make shallow copies
That's how it determines that you changed something
I can't modify gamedata? It doesn't seem to have any effect, ig it's returning a copy?
for (const [key, value] of Object.entries(window.modAPI.gameData.crops)) {
window.modAPI.gameData.crops[key as Realm] = value.map(it => ({ ...it, growthDays: 1 }))
}
for gamedata you need to modify the object itself, so like:
window.modAPI.gameData.crops.bodyForging.forEach(e => { e.growthDays = 1 })
otherwise all the existing references wont see your new version
I see, thanks π
That's what I'd have usually done, mutating the objects but I got into the thinking of creating new objects because of the whole redux thing
yeh redux is the only place you should mutate because thats how it works internally
generally outside that editing the object itself is best
why's access to redux action's payload not provided in the onReduxAction api? Would be useful to know what's actually being changed
{
"type": "inventory/removeItem",
"payload": {
"name": "Blueprint: Compendium Room",
"stacks": 1
}
}
What do you mean, looks like the payload is there?
where?
onReduxAction: (interceptor: (actionType: string, stateBefore: RootState, stateAfter: RootState) => RootState) => void;
payload isn't in RootState either
I'm asking for payload being made available to the callback passed to onReduxAction if it wasn't clear before
would be even better to be able to directly modify the payload for the action, instead of messing with stateBefore and stateAfter
in some cases
done
it doesn't look like location links (LinkBase variants) are available for modification via .gameData?
well, can't even get a readonly copy of them, let alone modify
unless I missed it
They are on locations themselves
So you find the location, look at unlocks, that's where they are
oh yeah, really missed it, it was right there
thanks
ig I didn't look at field types but only the names, welp
is there a way I can check which UIs are opened currently? Like I want to know whether the equipment upgrade dialog is opened or not at a given moment
there's no element id that I could target, and class names are mangled as css-*
ok, in a bit
what does multiple interceptors here mean exactly?
// When multiple interceptors are chained, each one receives the payload returned by the previous one.
onReduxActionPayload: (interceptor: (actionType: string, payload: unknown) => unknown) => void
So if multiple mods intercept, the value is chained along so each one can modify it
In mod load order
ah yeah, quite obvious, I was sleepy and I remember thinking that it's talking about being able to pass a chained callback like (which doesn't even make sense)
(action, ((action, payload) => payload)) => payload // and so on lol
admittedly not the clearest of comments
not sure how mod translations with afnm-extract-translations work, but can I exclude some strings from being included in the template.json? Since they're not really display strings and must not be changed.
"arrays": {
"src/modContent/index": {
"[string] Empowered": "",
"[string] Incandescent": "",
"[string] Mundane": "",
"[string] Qi Touched": "",
"[string] Resplendent": "",
"[string] Transcendent": ""
}
}
So they won't be changed unless you actually try to render them
Where do those strings appear in your mod?
rarity prefix strings for enchantment type item names (id)
Tbh you can probably ignore it
That part of the string extraction is there to try to catch shared stuff
Again, if you never actually render them they will never be used
(as in, pass it to the t() function)
ah it's example of some tea related mod lol, nvm, this is what I get for skimming
I was starting to wonder where's tea in afnm...
The true British cultivation
moving over the discussion here from: #1497938529869627392 message
Oh and for TS/IDE support, just keep the global.d.ts (the one with the modAPI types) at the root, include it in a tsconfig.base.json, and have your child mods extend that base config with a one-liner
@jagged parcel it's not working, so I'll just have the tsconfig.json be a symlink, wouldn't be able to customize individual package's tsconfig that way but I don't see my self needing that rn anyways
as for package.json, it still has some duplicate stuff but ig I'll live with it
{
"name": "mythical-tweaks",
"version": "0.0.1",
"description": "",
"author": {
"name": "loremaster"
},
"main": "dist/mod.js",
"scripts": {
"extract-translations": "afnm-extract-translations",
"prebuild": "afnm-extract-translations",
"build": "webpack --config ../../webpack.base.config.js --mode=production",
"postbuild": "node ../../scripts/copy-translations.js && node ../../scripts/zip-dist.js"
}
}
Yeah, that makes sense. I think my earlier tsconfig.base.json suggestion was incomplete.
The annoying bit with TS config inheritance is that include, exclude, rootDir, outDir, etc. are path-sensitive, and inherited relative paths stay relative to the config file they came from, not magically relative to each package. So if the base config has something like:
"rootDir": "./src",
"include": ["src/**/*"]
and a package extends it, those paths can still effectively point at the wrong place. That is probably why it βdidnβt work.β
I would avoid symlinking tsconfig.json if possible. Itβll work until you need one package to differ, then it becomes annoying again.
I think the better split is:
Root tsconfig.base.json should only contain shared compiler options:
{
"compilerOptions": {
"target": "ES2019",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"sourceMap": true,
"inlineSourceMap": false,
"preserveConstEnums": true,
"removeComments": false,
"jsx": "react-jsx",
"lib": ["DOM", "ES2023"]
}
}
Then each mod has a tiny real tsconfig.json:
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./dist"
},
"include": [
"src/**/*",
"../../global.d.ts"
],
"exclude": [
"node_modules",
"dist"
]
}
That keeps package customization open, but still avoids duplicating all the actual TS options.
(once second discord char limit holding me back lmao)
I think I should just bite the bullet and have bun run build only be available in the root and just be fine with every build command building all the packages/mods
won't have to include scripts property in each packages package.json that way
For package.json, Iβd also avoid putting build scripts in every mod package. Thatβs where most of the duplication is coming from. Since AFNMβs example config already uses package.json for metadata injection and outputs dist/<package-name>/mod.js, Iβd keep the per-mod package.json as metadata only, then make the root build script accept a mod name.
Per mod:
{
"name": "mythical-tweaks",
"version": "0.0.1",
"description": "",
"author": {
"name": "loremaster"
},
"main": "mod.js"
}
Root package:
{
"private": true,
"workspaces": ["packages/*"],
"scripts": {
"build:mod": "node scripts/build-mod.cjs",
"package:mod": "node scripts/package-mod.cjs"
}
}
Usage:
pnpm build:mod mythical-tweaks
# or
bun run build:mod mythical-tweaks
Then the root webpack config receives the target explicitly instead of relying on __dirname from a symlink:
And scripts/build-mod.cjs:
const { spawnSync } = require("node:child_process");
const modName = process.argv[2];
if (!modName) {
console.error("Usage: build:mod <mod-name>");
process.exit(1);
}
const result = spawnSync(
"webpack",
[
"--config",
"webpack.base.config.cjs",
"--mode",
"production",
"--env",
`mod=${modName}`
],
{
stdio: "inherit",
shell: true
}
);
process.exit(result.status ?? 1);
So instead of this inside every package:
"scripts": {
"extract-translations": "afnm-extract-translations",
"prebuild": "afnm-extract-translations",
"build": "webpack --config ../../webpack.base.config.js --mode=production",
"postbuild": "node ../../scripts/copy-translations.js && node ../../scripts/zip-dist.js"
}
you move that orchestration to the root and parameterize it by mod name.
For translations/zip, same idea:
pnpm package:mod mythical-tweaks
and the root script knows to operate on:
packages/mythical-tweaks/
That gives you:
- no symlinked webpack config
- no symlinked tsconfig
- one-mod-at-a-time builds
- mostly metadata-only package files
- still compatible with the AFNM example shape:
src/mod.tsβdist/<mod-name>/mod.js - room for per-mod tsconfig overrides later
Yeah I can't really think of a clean solution, that was the best I could come up with.
hmm
so... i'm on the basic level of modding. I've created my environment, pulled the example mod, and completed a successful build (of the example).
Which part of the documentation should i review if i have two goals for the mod?
Add a small calendar window to the screen. ideally top left, that shows the current year, day, and month (and information based on the date)
utilize the current day, year, and month - to adjust character stats like luck, probability to complete crafts, and crit chance?
P.S. This is in shell/arch/bash
Yeah honestly, thatβs a pretty sensible tradeoff.
If the main goal is removing duplicate scripts from every package, then Iβd make builds root-only and let:
bun run build
build every mod under packages/.
That keeps each packageβs package.json basically metadata-only, which is much cleaner.
Iβd still structure the root build script so it can optionally take a mod name later, even if you donβt use that path much right now:
bun run build
bun run build mythical-tweaks
So the root script could behave like:
- no argument = build all mods
- argument = build only that mod
That way you get the simple workflow you want now, but youβre not locked into βalways build everythingβ forever.
Something like:
// scripts/build.cjs
const fs = require("node:fs");
const path = require("node:path");
const { spawnSync } = require("node:child_process");
const packagesDir = path.resolve(__dirname, "../packages");
const requestedMod = process.argv[2];
const mods = requestedMod
? [requestedMod]
: fs.readdirSync(packagesDir).filter((name) => {
return fs.existsSync(path.join(packagesDir, name, "package.json"));
});
for (const mod of mods) {
console.log(`Building ${mod}...`);
const result = spawnSync(
"webpack",
[
"--config",
"webpack.base.config.cjs",
"--mode",
"production",
"--env",
`mod=${mod}`
],
{
stdio: "inherit",
shell: true
}
);
if (result.status !== 0) {
process.exit(result.status ?? 1);
}
}
Then root package.json only needs:
{
"scripts": {
"build": "node scripts/build.cjs"
}
}
And each mod package can drop the scripts block entirely.
So yeah, Iβd say root-only commands are the right call. Just make the root build script accept an optional target mod internally, because that costs almost nothing and saves you later if the monorepo gets bigger.
Thatβs a good next step after getting the example mod building. We can open up a seperate thread if you want more help with this. Here's my take.
Iβd look at the docs in roughly this order:
-
Core Concepts β ModAPI Reference
This is the base layer. Youβll want to understandwindow.modAPI,gameData,actions, and especiallyhooks. -
Core Concepts β Flags System
This is probably the most important part for the date logic. AFNM exposes time-related flags likeyear,yearMonth,day, andmonth.
Small warning: I believe month is total months elapsed, not the display month from 1-12. So for a calendar UI, you probably want year, yearMonth, and day. For elapsed-time calculations, use month.
-
Advanced Mods β Adding Screens
For a small calendar in the top-left, donβt start with a full custom screen unless you want to replace the whole UI. Look specifically at the Persistent Overlays section. Thatβs closer to what you want: a little always-visible UI mounted to the page. -
Advanced Mods β Lifecycle Hooks
This is the part for changing gameplay based on date.
For crafting, look at onDeriveRecipeDifficulty. That lets you adjust recipe completion/perfection/stability, which is probably the cleanest way to make certain dates better or worse for crafting.
For combat stats/crit-style effects, look at combat hooks like onBeforeCombat, onCreateEnemyCombatEntity, or onCalculateDamage. Iβm not sure if crit chance itself is directly exposed, so you may need to approximate it by adjusting combat stats or damage on certain dates.
Iβd probably build it in this order:
function getDateModifier(gameFlags: Record<string, number>) {
const year = gameFlags.year;
const month = gameFlags.yearMonth;
const day = gameFlags.day;
// Example idea:
// lucky on first day of each month
const luckyDay = day === 1;
return {
year,
month,
day,
craftMultiplier: luckyDay ? 0.9 : 1,
damageMultiplier: luckyDay ? 1.05 : 1,
};
}
Then use that same helper in both places:
window.modAPI.hooks.onDeriveRecipeDifficulty((recipe, recipeStats, gameFlags) => {
const date = getDateModifier(gameFlags);
return {
...recipeStats,
completion: recipeStats.completion * date.craftMultiplier,
perfection: recipeStats.perfection * date.craftMultiplier,
};
});
And for combat:
window.modAPI.hooks.onCalculateDamage((attacker, defender, damage, damageType, gameFlags) => {
const date = getDateModifier(gameFlags);
if (attacker.entityType === "Player") {
return Math.floor(damage * date.damageMultiplier);
}
return damage;
});
Short version would be..
- calendar display = Advanced Mods β Adding Screens β Persistent Overlays
- current date values = Flags System
- crafting changes = Lifecycle Hooks β onDeriveRecipeDifficulty
- combat/stat changes = Lifecycle Hooks β Combat hooks
- date-based events/content = Events System β Calendar Events, but thatβs more for festivals/events than an always-on UI/stat modifier
modAPI.injectUI() for your calendar UI, requires you to know some react/mui
And to get the date you can use either window.modAPI.getGameStateSnapshot()?.calendar.(day/month/year) or game flags
that'll certainly get me started.
Is there a way to add a modifier to character stats that changes based on the calendar information?
I'm not sure when the calculation happens
depends on what kind of modifier
for boost during recipe crafting, there's modAPI.hooks.onDeriveRecipeDifficulty
for others a ready made hook may not be available
I'm mostly interested in luck statistics , crit chance, craft chance, accuracy, maybe power, damage reduction.... but for the most part, this is intended as a globally affective mod that will adjust stats (only slightly) based on the date
so you'll have to use hooks.onReduxAction or onReduxActionPayload for when ready made hooks aren't available, which triggers when game state changes or is about to be changed
hmm so temporary changes to stats... do you want them to be shown in in-game UI or your own UI?
the specifics wouldn't be shown, but the little calendar i intend to implement would explicitly tell the player whether the day is "auspicious" or not, on various levels... as a warning or a blessing
I see something like window.modAPI.getGameStateSnapshot()?.player.player.monthBuffs, ig you could have your own custom buffs that you add in there and remove when you want
though I'm not sure how to actually modify game state (RootState) outside of onReduxAction* apis
snapshot only gives a read only copy
i can't expect your average joe to know whether it's a good day or not, but the readout should be helpful
so currently I see two ways, utilizing monthBuffs, or hooking for "combat initialize", "crafting initialize" redux actions, etc (onReduxAction*) and increase/lower the stats there by returning a modified RootState or payload, and then restore your modifications at the end of battle/crafting
that makes sense. crafting(etc) takes more than one day after a little while. there'd need to be a new calculation, or new base-stat-modifier for each day, whatever it happens to be
you can see what redux actions types are available for you to "hook" on with:
function deepDiff(prev, next) {
const result = {};
const keys = new Set([...Object.keys(prev), ...Object.keys(next)]);
keys.forEach(key => {
if (typeof prev[key] === 'object' && typeof next[key] === 'object') {
const nested = deepDiff(prev[key] || {}, next[key] || {});
if (Object.keys(nested).length) {
result[key] = nested;
}
} else if (prev[key] !== next[key]) {
result[key] = {
prev: prev[key],
next: next[key]
};
}
});
return result;
}
window.modAPI.hooks.onReduxAction((action, prevState, state) => {
console.log("[REDUX STATE CHANGED] ", { action: action, diff: deepDiff(prevState, state), state: state });
return state;
});
window.modAPI.hooks.onReduxActionPayload((action, payload) => {
console.log("[REDUX PAYLOAD]", {action: action, payload: payload});
return payload;
});
and then observe the logs in the devtools console
I think crafting days can be changed in hooks.onDeriveRecipeDifficulty()
is there any documentation for AI assistance? I know it likes to over-complicate things
that could be an excellend stat to look at
I've no idea, ask mim, he uses ai a lot it seems
@jagged parcel i remember seeing documentation for AI assistants and mods. do you remember where?
it's a hook
If you're wanting to use an AI assistant: https://github.com/lemon07r/AfnmAgentModTemplate
If you have any issues with it let me know
Psst, don't forget to signup for the competition here: #mod-contest-sign-up
I'll add more hooks to make this mod easier
btw, how'd I mutate RootState outside of something like onReduxAction*?
Don't think I could just grab reference to it and mutate freely, probably has to do that via existing redux reducers that are in place, so then how to emit my own action payloads?
You have to do it via a dispatch
with this?
import { useDispatch } from 'react-redux';
not sure what that means
And in a react component
seems quite limiting
So from inject UI or add screen
ATM you can't just do it globally
What are you trying to do?
nothing rn, was just curious
Note, both of those contexts already pass through a dispatch function to use
I see, so don't need to create my own dispatch
Yep. Generally there's 2 ways to modify state:
Do it via a user action in a UI (via dispatch)
Do it by injecting event steps after something happened (by setting flags)
is there a toast/notification component in-game that I could reuse? or do I either make my own or reuse the modal dialgos?
Currently I don't have one
I can add one though
You can make you own with inject UI
Or just attach it globally
would be nice if there's a common component in-game that others mods could also use, just for consistency, but if you don't want to maintain one since it's not being used in game that's understandable too
why's it showing old mods? As in mod zips that had previously -{version} suffix in filename, and this is after I deleted the -{version} suffixed mod folders from mods/loaded/
its gone from the root folder and mods/loaded?
yeah
.
β afnm-craftbuddy.zip
β afnm-lucky-all-around.zip
β debug-0.0.1.zip
β mythical-tweaks.zip
β quicksaves.zip
β stat-boosts-0.0.2.zip
β stats-boost-stability-1.1.0.zip
β
ββββloaded
ββββafnm-craftbuddy_923e1c220444ab0f
β β mod.js
β β mod.js.LICENSE.txt
β β package.json
β β
β ββββtranslations
β en.json
β
ββββafnm-lucky-all-around_15c9322be32fe424
β mod.js
β package.json
β
ββββdebug-0.0.1_4be8d1cb11a965ba
β mod.js
β
ββββmythical-tweaks_6cce2d3e07483454
β β mod.js
β β mod.js.map
β β
β ββββtranslations
β template.json
β
ββββquicksaves_a7d319e944b07cfc
β β mod.js
β β mod.js.map
β β
β ββββtranslations
β template.json
β
ββββstat-boosts-0.0.2_5d68390288ba00bd
β mod.js
β mod.js.map
β
ββββstats-boost-stability-1.1.0_8a2c397edbc547fb
mod.js
mod.js.map
done
Btw, does it make sense to commit the translations/template.json? I'm not sure
helps you track when things changed i guess
lyeedar maybe add these dev deps to the example mod
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
couldn't even get the actions.registerOptionsUI tsx example working without it throwing type errors
Good shout. Mind requesting
EDit: actually, just did it π
I had this issue before, I forget how I fixed it. Let me check.. I probably documented it somewhere.
Is there an api file I can dig into? Is it part of the game-files (or a list of all of the available commands/hooks... Or a way to generate a list π€ )?
I'm trying to figure out the available uses/triggers and see what parts of the game stats are exposed for pre-calculations before character actions - so I can start planning my mod out. 
Yes, look at the types exposed through the afnm-types package
Or just the modapi type for what is directly exposed
You can do that in vscode
Thanks. Guess I should get that installed again. I've been using Vim like a crazy person π€£
you can have lsp and everything in vim too but you have to configure it
the one reason I don't use it for everything, to lazy to configure and manage it... so I just use vscode with vim motions
I'm not that crazy π€£
I plan to install vscode and run an instance of SonarQube. Do we have a schema doc I can look at?
why sonar qube?
Having an ide linter is convenient
you have typescript, you'll get enough static analysis if you use it properly
typescript type checking will do like 90% of that for you
I don't see the point of sonarqube etc
(and I'm familiar with it)
Static code analysis
what do you mean 'schema doc'?
An example of tabs and formatting. Can probably make one from existing mods 
ah, theres no standards yet π
But if it's just TS, probably don't need anything special
?? so styling convention?
just slap prettier in
That's the word "style guide"
i think the example mod has prettier preconfigured?
it does
Nice
I read TS at my last job, but I was the CI/CD devsecops guy, so I mostly handled the build pipelines and SonarQube integration. Writing it will be new π
ahhhh thats why sonar π
Yea. It slapped a lot of wrists and pull requests
typescript will make it hard to go wrong. if it has an underline, fix it! π
Nice 
I'll have to take some time and work through the docs before I have more questions. Don't know what I don't know yet
thats fair
This has been most helpful so far 
but yeh if you open the example mod, go to index.ts, right click on the modApi and go to type definition you can see the whole mod api
oh, i should document my super awesome new image generation plugin
plugin for what?
lyeeder I don't think there's a way to have persistent mod data not coupled with a save outside of global flags? Need that for settings, flags are fine when it's a number, but if it has to be a string, I don't think there's anything in place?
you can just use local storage then
Any system dependencies? Like would I need to install some weird drivers from all over the place? Could be useful if I ever decided to make a content mod
also global flags are save file linked
you need comfyui set up is all
theres a docs page on it
under guides
I thought they weren't, I saw global_mod_flags.json in appdata, so didn't really seem tied to any save
hmmm, maybe I forgot i implemented something
lemme go check π
oohhhhh
yes, i mad global flags
why did i do that, thats weird π
anyway, yeh local storage is the shout
Any, comfyui setup: https://lyeeedar.github.io/AfnmExampleMod/guides/comfyui-setup.html
How to install ComfyUI Desktop and use the provided AI image generation workflows
if you were going to just serialize to json, why even restrict data type to numbers only π
its because they are merged into the game flags
git rebase breaks symlinks on windows... they're turned into plain files with the path to the target file... lovely
probably some mess with git for windows
I want to add some GameIconButtons in here, how do I know in a reliable way when this layout changes from this:
to this:
screen in rootstate is same so can't use that
the bottom is inside events
the other is the rest of the time
screen should be event
it's location
the top one is 'playercomponent'
printing game state after this has "location" in screen
could this be done better?
window.modAPI.injectUI('location', (api, element, inject) => {
return inject(
'#backgroundImage',
<Stack
id="quicksaveButtons"
position="absolute"
zIndex={9999}
left="230px"
bottom="75px"
direction="row"
spacing={1}
>
<api.components.GameIconButton
title="Quicksasve"
onClick={() => {
if (api.hasSave) QuickSaves.makeQuickSave();
}}
>
<SaveIcon />
</api.components.GameIconButton>
<api.components.GameIconButton
title="Load Last Quicksasve"
onClick={() => {
if (api.hasSave) QuickSaves.loadLastQuickSave();
}}
>
<LoadIcon />
</api.components.GameIconButton>
</Stack>,
'inline'
);
});
btw, I wasn't expecting I'll be messing around with absolute positions again... lol
Slot names for dialogs are the id (e.g. 'combat-victory'). You can find these by inspecting the DOM in dev mode. Slot names for screens match the ScreenType value (e.g. 'combat').
There's no id for this player UI inlocationand other screens I believe? I could just target that then, ininjectUI
lol, the buttons appear over even dialogs
instead of having injectUI for location, house, herbField, etc...
Not ATM. Request it, I didn't think people would want to inject there π
z-index 10 100 seems to work, I won't think about it much lol
Speaking of injecting ui elements? What page is that on π
Actually, that code block might be enough 
page?
In the wiki(git page?) , or the file
how to use the function? In the editor, just hover over it
it's scrollable
How does it handle the player scaling slider for size? Do I need specific image sizes or does the ui do that on its own based on the bounds of the element?
depends on where you're inserting your element, generally you'd scale images yourself
with css properties
Player scaling slider?
The one in the graphics settings that changes the scale of ui elements (and blocks two needles in the legs of breakthrough)
It affects the ui elements in the corners too, probably through font, but it ends up covering a couple needle locations when you do
You can override the theme if you want to
much better here
lyeeder how do I use game's styling for the tooltips?
lol quick sasve
Maybe an a future iteration I'll change ui colors based on the numerology 
Now I just want to add a drawer style element that opens up and shows the numerology calendar and it's affects, and can close again to make it go away
Ah I don't expose it ATM. Request
I'll probs do a release tomorrow with all these new apis
also api.components.GameIconButtons are using a faded color, compared to other icon buttons I see
The player component override the default styke
They make the buttons gold
Everything else should be using the default more faded style
oh I see
lyeeder, the afnm-extract-translations is not able to extract these string into the template.json
Wrote template: C:\Users\user\repos\afnm-mods\packages\quicksaves\translations\template.json
β Extraction complete!
does it not support tsx maybe?
oh
I thought it'd do it automatically, given it tracked those strings in the array before
It tracks arrays specifically on the assumption that those are a more 'useful' source for the string context
E.g. in the main game the realm array tells it 'this string is a realm and here's the others' Vs scattered usages throughout the codebase
still doesn't work
title={window.modAPI.utils.t('Load Last Quick-save')}
wait... is this script "extract-translations": "afnm-extract-translations", in package.json important... I had it removed lol
afnm-extract-translations was in two places, so I thought one at prebuild would be enough π
hmm not sure how'd I make it work for my mono repo setup, the build script is only present in the root package.json
What's calling extract-translations? Can I do it manually for the package?
manually somewhere in here
const buildPromises = packageDirs.map(async (packageDir) => {
console.log(`Building ${path.basename(packageDir)}...`);
await fs.promises.rm(path.join(packageDir, 'dist'), {
recursive: true,
force: true,
});
await spawnAsync('afnm-extract-translations', [], {
stdio: 'inherit',
cwd: packageDir,
});
await spawnAsync(
'webpack',
['--config', '../../webpack.base.config.js', '--mode', 'production'],
{
stdio: 'inherit',
cwd: packageDir,
},
);
await copyTranslations(packageDir);
const zipPath = await zipDist(packageDir);
if (args.values.copyMod) {
const package = require(`${packageDir}/package.json`);
const target = path.join(
process.env.AFNM_MODS_PATH,
`${package.name}.zip`,
);
console.log(`Copying mod "${path.basename(zipPath)}" to "${target}"`);
await fs.promises.copyFile(zipPath, target);
}
});
The pre-build calls it
how? https://github.com/Lyeeedar/AfnmExampleMod/blob/fd46121d7793c0a7fa5170fc097dacfe0b5392ef/package.json#L10-L11
prebuild is calling afnm-extract-translations again no?
Oh, the standalone script isn't useful no
Just the pre-build
So removing that line was fine
It's only there so the user can trigger it early if they want to test
The script itself is part of the types package
so I'm running the extraction script here (in the build script for root package.json)
await spawnAsync('afnm-extract-translations', [], {
stdio: 'inherit',
cwd: packageDir,
});
extraction still isn't working properly though for some reason, 0 strings extracted
does it expect some env or something idk
Shouldn't do
Afaik
I guess first question
Does it work in the normal setup?
Is it only broken in the spawnasync
hmm let me add back in scripts in the package.json
no
"scripts": {
"prebuild": "afnm-extract-translations",
"build": "webpack --config ../../webpack.base.config.js --mode production"
}
Okay well
Found 5 source file(s) to scan.
Pass 1: Building string export registry...
Pass 2: Building import registry...
Pass 3: Building name export registry...
Pass 4: Building realm map registry...
Pass 5: Extracting strings...
Pass 5.5: Extracting from xxxToName maps...
Found 0 strings in xxxToName maps.
Total extracted strings: 0
Template-ready strings : 0
Generating translation template...
42 unique translatable entries found.
is it possible to not include that _patterns junk?
in the tool itself
maybe? feature request it. The whole set of scripts are a vibecoded mess so who knows π
or #1241787390687711313 ?
technically the main game needs it
but both forums get scraped by my script so it doesnt matter which it goes in to
threw it in requests
is the crossroads window a static asset with a title at the top? how do i use that theme for my injected UI element?
yeah likely an image, look in devtools
how do i find out the workshop ID?
I found the window information i was looking for
(also i ran out of tokens. i can't vibe-code any more
)
what workshop id...?
the one in package.json
"name": "auspicious-days",
"version": "0.1.0",
"description": "An AFNM Mod that changes character stats based on Numerology",
"author": {
"name": "tristen-sinanju"
},
"gameVersion": "0.6.52",
"packageManager": "[email protected]",
"afnmWorkshop": {
"workshopId": "", //This ID
"title": "Auspicious Days",
"description": "This mod is intended to implement some basic numerology modifiers to the character. Luck, crafting, and other effects.",
"tags": [
"Gameplay"
],
"visibility": "private",
"previewImagePath": "src/modContent/imagePreview.jpg"
},
workshopId doesn't exist before you upload the mod first, it's there for updating the mod after the initial upload.
Also this whole "afnmWorkshop" thing in package.json is mim's own abstraction over the https://github.com/Lyeeedar/ModUploader. So if you've any questions related to this, you should ask him
personally I don't like it, as having a whole ass workshop description in json is annoying, I'd personally keep the description in a markdown file and use a markdown to steam markup converter in the upload pipeline (or make your own if a good one doesn't exist)
once you've uploaded the mod though, id is is in the link for the workshop page, e.g. 3689709015 for:
https://steamcommunity.com/sharedfiles/filedetails/?id=3689709015
thanks for the info. i'm not really a code writer. i can read it, and generally understand what it's doing, but i'm no good at writing my own π€£
i write cybersecurity policy, not code 
Part me being lazy and wanting to keep things simple cause I was getting tired of manually pasting descriptions in. I made a PR to lyeedars workshop uploader that worked headless and that was the quick solution I came up with. Steam uses bbcode so MD probably wouldn't make sense either. Maybe just a plain .txt file?
No shame in using a coding agent to ask questions about code you don't understand (and us). Both me and lyeeedar use God knows how much ai π.
i'm used to seeing big json configuration files, so it didn't even catch my eye that there might be a different method. .. but a flat file that's easy to edit wouldn't be a bad idea
in linux it'd just be like touch SeamWorkshopInfo or something simple
It's a good idea. Kind of an oversight on my end since the template was worked from my existing mods I threw together and yk, those mods are already near completion and were kind of in a "don't break what already works" stage
as I said I want md so I can also use it as a package/mod description in the github
and it's not hard to write a converter to steam's markup
Is there a MD to bbcode converter/transpiler thing?
Would make it easier to have one source of truth for both
I don't fancy the idea of having to maintain both a MD file and bbcode
i understand those words 
I never checked, I was about to write it as part of this
https://github.com/nozwock/tale-of-immortal-tool/
for making publishing mods to workshop easier, already wrote a converter for markdown -> TOI's markup
just got into doing other stuff, so it's catching dust rn
i've, thus far, been able to copilot the entire project 
That means the template is doing its job π
Yk my very first mod was me coming here and trying to ai generate a mod as a joke?
yeah that sounds bad
It didn't work.. at all
and look how far we've come π
Then I actually rewrote a working mod from scratch. Which sorta worked! But it was crap. So I did it again. And improved the docs and such I had every iteration. π
Craftbuddy had at least 4 full rewrites. I spent much time banging my head against things till they worked
Im hindsight I should have started with a simpler mod

it's not like mine is exatcly simple either. at least it doesn't need to make decisions or recommendations
Right! Eldergpt spirit ring also started as a meme mod to see if I could entirely ai generate a mod using a template built from what I learned. It used a very early version of the template you're using now, the first few versions were actually entirely ai generated. Then I started sticking my hand in places to improve things, and give it a good context engine, compacting etc. Basically built it like an ai agent, like you would with a coding agent or build your own claw. Still I was surprised how far ai got with the mod on its own
slightly offtopic... but i wonder how AIRogueLite is doing these days
That's a thing?

I believe in you
Your mod will find greatness
Introducing AI Roguelite, the world's first text-based RPG where every location, NPC, enemy, item, crafting recipe, and game mechanic is 100% determined by artificial intelligence.
If you've ever played other AI "games" such as AI Dungeon and were frustrated by how they didn't feel like a game, then AI Roguelite might be the perfect game for youβ¦
$14.99
564
i registered my novelaiAPI token with it
network issue
oh yea, i remember the auth loop taking a while earlier . forgot discord was on fire
i'd have to look, but i think it can connect to almost any AI api
oh so that's why I had ! icon on some of the server icons and was wondering if my network is having issues lol
the ultimate slop
yep. looks like it was resolved a little while ago
also, yes. slop+brainrot. good to have a laugh at how rediculous some of the game is π€£
I'm surprised (but maybe not too much lol) some people bought it and actually thought to spend their time on it
i get bored, and try games. it's not that expensive
that's the price of pickles the bbq place down the road... and i got way more replayability than the pickles 
surely there are better games you could play instead
i play those too
how do i update my mod on the workshop (replace it with the current build)
Use the mod uploader, it has an option to update
it's still private on steam, but i'm sure the mods can see it if they want https://steamcommunity.com/sharedfiles/filedetails/?id=3716230953
If you're using an agent with the template you can ask your agent or to do it for you. Then ask it to update your agents MD and docs to make it easy for future agents. The mod uploader has headless features to make it easy to automate
I got the upload to work from lyeeedar uploader. I need to dig into the script in the mod example to see why it's failing. Made a lot of process once I fixed the game instance path π
The template uses that uploader
The agent would have prompted you to download it otherwise
https://github.com/lemon07r/AfnmAgentModTemplate/blob/main/AGENTS.md#release-workflow
## Release Workflow
1. Finish code and docs.
2. Run `bun run release:validate`.
3. Upload to Workshop: `bun run workshop:upload -- --change-note "vX.Y.Z - ..." --allow-create`
4. Commit and push to `main`.
5. Tag with `git tag vX.Y.Z && git push origin vX.Y.Z` to trigger the GitHub Release workflow.
The sibling ../ModUploader-AFNM repo must exist. Run scripts/setup.sh if missing.
From the skill file ^
It did, it just doesn't run through the process. I think moving the workshop info to a new file might have done some things. I pointed it back to the file though, so idk.
afnm-workshop.json on my git
Oh yeah, as you change things around you have to modify things to work together a little
It's probably called a few places I need to find
It is just a template, so if you figured out a way that works for you that's all that matters
I'm going to be updating the template today for the new afnm-types. I'll probably address the workshop description thing
Nice. I'll have to update my fork and... I think just replacing /src and adding my new root files, and updating package.json should be all I need, right?
Lot's of improvements now. Including having workshop description in it's own place as a single source of truth, updated for 0.6.53, and lot's of other fixes, improvements, and cleaning up:
dd6c3ca β docs: deduplicate, consolidate, and soften guidelines across all docs and skills
- Added Working Principles section (code design, debugging, comments) to AGENTS.md
- Added lean-docs/single-source-of-truth principle and user notification to doc stewardship
- Softened rules to suggestive tone where strictness isn't required
- Removed 4 redundant stub sections from SUPPLEMENTARY_GUIDE.md, renumbered
- Trimmed afnm-modding skill from 120 to 43 lines
- Replaced duplicated facts across 7 skill files with cross-references
- Merged AFNM_MODDING.md changelog into categorized sections
- Slimmed README agent list, removed EOL entries
- 11 files, -166 net lines
68008c5 β chore: upgrade afnm-types to 0.6.53 and document new ModAPI features - Upgraded afnm-types from 0.6.52-v2 to 0.6.53
- Documented new hooks (onCreatePlayerCombatEntity, onBeforeCraft, etc.), registerKeybinding, showToast, tooltip components, injectUI sub-slots
- Fixed inaccurate API paths (addScreen, setGlobalFlag, listSaves)
- Updated stale version references across all docs
- 13 files, +69 net lines
0b89e4f β feat: add DESCRIPTION.md as canonical mod description source - Added DESCRIPTION.md as single source of truth for public mod copy (Markdown on GitHub, auto-converted to BBCode for Workshop)
- Added scripts/markdown-to-bbcode.ts zero-dep converter
- Workshop upload resolves description via priority chain: CLI flag > DESCRIPTION.md > package.json (legacy)
- Release workflow composes body from DESCRIPTION.md + auto-generated commit notes
- 8 files, +316 net lines
Including a no dependancy MD -> BBCode converter, specifically for steam workshop bbcode.
Carried over some guideline stuff to improve agent effectiveness from some of my more serious projects.
love me some regex
I stole a script once so I wouldn't have to do regex any more π
I think regex is fine here though, the output is lossy. Steam Workshop BBCode supports a tiny subset of Markdown, a full AST parser would be structural overkill for a target format that can't even represent half of what Markdown can.. Plus I kept the regexes are individually simple. Each one matches a single, well-defined pattern. None of them attempt recursive, context-sensitive parsing, or anything crazy.
I also made sure unsupported Markdown passes through as plain text
I couldn't find a good tool to do MD -> BBCode, at least what steam supports so I ended up making that
sometimes I can't tell whether you're writing the post or is it pasted from llm, because I thought the script is spitted out by llm (it very much seems to be)
but yes, regex for md should be fine for many cases, it's just a mess to read through it
or debug
It is 100% generated by LLM, but I had kimi make me one and it was kinda bad. So I scrapped it, and gave it more specific criteria to make a good one with opus. Originally I was going to use someone else npm package but steam bbcode is different from full bbcode.
If you mean the post, parts of it were copy and pasted from the specs I gave it, lol
The code it self is 100% LLM
yess steam markup is different from bbcode, which is why I never called it bbcode
that's what I'm confused about, why do you write like this then?
Plus I kept the regexes are individually simple
what "I"?
yeh same π
I used to finetune LLMs, I became one too somewhere along the road
how can I add a jsx element to HtmlElement?
if I want to directly mess with the element in injectUI instead of using inject() since I want to add my stuff in two places, not just one
with difficulty π you need to render it, then re-parent it later
I don't think game's react dom is available for rendering to mods?
probably better to just use fixed positioning to move it ot he same place
should be? its just react after all
hmm so you mean I just add the react-dom to dev deps (and use it), but don't actually ship it? that'd work?
add one button each to topBarLeftButtons and topBarRightButtons in combat screen 
should've mentioned that early lol
why not inject to each?
hmm true...
would need different injectUI but that'd work
hmm it's still not good, it doesn't work if I wanted the button to be the first sibling in the container element
ideally this would be the first sibling
window.modAPI.injectUI('combat', (api, element, inject) => {
return inject('#topBarRightButtons', loadQuickSaveButton(api));
});
ah i see. youd have to reparent and add it to the actual dom probably
or grab the element for the row with some sort of element path selector and reverse its order
is the injection done on inject() call? can I just store the return value, and do the order manipulation there and return the ReactNode later?
probably not, injection likely happens after the callback has returned the node
the inject is a function called every time it rerenders
hmm
well I did this, it works (as long as ui doesn't change in the future) but the element is not accepting any pointer clicks for some reason?
window.modAPI.injectUI('combat', (api, element, inject) => {
return inject(
'#topBarRoundInfo',
<Box paddingTop="4px" marginLeft="8px">
{loadQuickSaveButton(api)}
</Box>,
'inline',
);
});
you might need 'pointer events: all'
I can't seem to set it on Box, there doesn't seem to be any style property either?
sx={{ pointerEvents: "all" }}
works
I was going to do the tooltips soon, so that's helpful
maybe in the future if inject could allow for setting the injected element (inside the targeted element in non-inline mode) as the first sibling as well, it'd be nice (or any nth position)
feature request it
its a LOT more complex to do though so probably wont be as quick to add
will write it out in a bit
I'm not sure why but tooltips don't seem to work, I don't get any tooltip on hover
https://github.com/nozwock/afnm-mods/blob/25c55bc33bb4b0e330c42c50aee0ea226146a37f/packages/quicksaves/src/modContent/ui.tsx#L100-L130
hmm
that was in place only in the combat screen's quick load button, didn't know tooltip needed that
it depends where this is injected
ah root level in combat probably has pointer events disabled
everywhere a quick save/load button is
so every screen that I add it to
anyways I'll try pointerEvents on box
no guarantee itll work π
i always just use inspect to check if its getting my mouse events or not
didn't work
mouse events are working since I see the hover effect and can click
oh wait
:
{
<api.components.GameTooltipBox>
<Typography fontSize="120%">{t('Quick-save')}</Typography>
</api.components.GameTooltipBox>;
}
those should be () not {}
atm you arent returning anything
lol
because {}
lovely stuff
is there a hotkey for devtools in devmode btw?
I tried all f keys I think
and the usual inspect hotkeys from browsers
can't access it if there's a dialog open (and I happen to have devtools closed), anyways it's not a big deal
because the button gets covered by the modal dialog
ah. yeh you need to close the dialogs
then reopen dev tools, and reopen the dialog
tbh i just permanently play with dev tools open
yeh sometimes I've been in situations where I want to inspect the dialog or whatever, but devtools was closed
well you can always request a keybinding!
yeah
idk what's up but the game is very slow with devtools opened for me
so I close it sometimes
change to the console tab
ah
if you have inspect open its CONSTANTLY rerendering the tree
in the actions
and that ui is in the options
there will be a mod sectio at the bottom is any are registered
interesting, so ModReduxAPI.useKeybinding is obsolete now
oh lol
then in your component to use the keybinding add the useKeybinding hook
I only need to target the default keybind in useKeybinding right?
yeh
heres an example if you want to namespace it too:
useKeybinding(0, { A: () => {} }, false, 'crafting');
so whatever crafting.A is mapped to is the key that run the fucntion on A
how do I do set useKeybinding once outside of registerOptionsUI (assuming I don't have settings for the mod), or injectUI, since only those give access to useKeybinding
you cant, it has to be in the react tree
so just mount it any time you mount your buttons
probably combine them into a shared function
shared component sorry
only once in one of them right, or is it expected that I set it in every react context (registerOptionsUI, and injectUI)?
itll be mounted and available when then particular component is mounted
and unmounted once its unmounted
hmm oh, damn so only available when the componenet is visible
you probably should hahve it be linked to be availabel when your new buttons are available
so have it always mount that
yeah it'll work for my case here, I can just have it alongside the buttons, but if I were not having UI buttons for quick save/load but only hotkeys, no rebinding for me lol
unless I want to make make my own key rebinding UI component
something like:
const QuicksaveButton: React.FC<ModReduxAPI> = (api) => {
const doSave = () => {
window.modAPI.utils.showToast("Saved!", 10000, "success")
}
api.useKeybinding(0, { "F5": doSave })
const GameIconButton = api.components.GameIconButton
return (
<GameIconButton onClick={doSave}>
A
</GameIconButton>
)
}
oh it's that easy to make function comps... just a matter of type annotation
and heres using it:
export const injectPlayerButton = () => {
window.modAPI.injectUI("player-ui", (api) => {
return <QuicksaveButton {...api} />
});
}
just noticed im not exposing the option to set context on useKeybinding. Will be in the next release
already have it, can add the useKeybinding here, just that it's a function
function makeQuickSaveButton(api: ModReduxAPI) {
return (
// TODO: Add keybind key in tooltip `(${key})`
<api.components.GameTooltip
provider={() => (
<api.components.GameTooltipBox>
<Typography fontSize="120%">{t('Quick-save')}</Typography>
</api.components.GameTooltipBox>
)}
>
<api.components.GameIconButton onClick={() => makeQuickSave(api)}>
<SaveIcon />
</api.components.GameIconButton>
</api.components.GameTooltip>
);
}
yeh that should be fine
generally i like to use the function component syntax (React.FC) as it keeps it clearer to read
and enforces you dont mess up the return types
what "context"?
so if you want to say 'this F5 here is different from the F5 used in that other thing'
in your case probably not important
but if you are using more common keys its useful
yeah separates them from normal functions at least while reading
const SaveIcon: React.FC = () => (
<SvgIcon>
<svg fill="currentColor" viewBox="0 0 24 24" stroke="currentColor">
<path d="M18 15v3H6v-3H4v3c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2v-3zm-1-4-1.41-1.41L13 12.17V4h-2v8.17L8.41 9.59 7 11l5 5z"></path>
</svg>
</SvgIcon>
);
much nicer
window.modAPI.injectUI('event-player-ui', (api) => (
<QuickSaveLoadButtons api={api} left="3px" bottom="140px" spacing="5px" />
));
But any idea how I can make the FC props have props for top Stack so I don't have to manually define bottom etc...
const QuickSaveLoadButtons: React.FC<{
api: ModReduxAPI;
left?: string;
bottom?: string;
spacing?: string;
}> = ({ api, left, bottom, spacing }) => {
left = left ?? '62px';
bottom = bottom ?? '0px';
spacing = spacing ?? '0px';
return (
<Stack
id="quicksaveButtons"
position="absolute"
zIndex={100}
left={left}
bottom={bottom}
direction="row"
spacing={spacing}
>
<QuickSaveButton {...api} />
<QuickLoadButton {...api} />
</Stack>
);
};
So I could just do?
<Stack id="..." {...stackProps}>...</Stack>
those are StackProps
interface QuicksaveButtonProps {
api: ModReduxAPI,
stackProps?: StackProps
}
const QuicksaveButton: React.FC<QuicksaveButtonProps> = ({ api, stackProps }) => {
const doSave = () => {
window.modAPI.utils.showToast("Saved!", 10000, "success")
}
api.useKeybinding(0, { "F5": doSave })
const GameIconButton = api.components.GameIconButton
return (
<Stack {...stackProps}>
<GameIconButton onClick={doSave}>
A
</GameIconButton>
</Stack>
)
}
oh yeah I see it
{...stackProps} will overwrite preceding properties if any I think? so these as defaults should work
<Stack
id="..."
left="62px"
bottom="0px"
spacing="0px"
{...stackProps}
>
yeah it works nice
so this is what you meant by missing context crafting.*
not sure how to use registerKeybinding, what to put in .action? The example doesn't work
registerKeybinding({
action: 'myMod.specialAction',
category: 'general',
displayName: 'Special Action',
description: 'Performs a special action',
defaultKey: 'F',
allowRebind: true,
});
Since 'myMod.*' is not a valid action: KeybindingAction
action doesn't seem to make sense here, it should've been id: string or something
rebinding doesn't seem to work (well the UI works but its change is not reflected in useKeybinding), first I tried just having the default keybind F9 mapped to callback in here, but then I changed it to the action id (registering of which works because I'm calling it in a .js file) which makes the keybinding not work at all, makes sense ig since useKeybinding expects a key, and not action id
const QuickLoadButton: React.FC<ModReduxAPI> = (api) => {
const id: string = `${MOD_ID}.quickload`;
const keybindings: Record<string, () => void> = {};
keybindings[id] = () => loadLastQuickSave(api);
api.useKeybinding(0, keybindings);
/// ...
};
// imported in index.ts
window.modAPI.actions.registerKeybinding({
action: `${MOD_ID}.quickload`,
category: 'ui',
displayName: 'Quick Load',
description: '',
defaultKey: 'F9',
allowRebind: true,
});
Bug plz
in a bit
btw, is this okay? like I'm not sure what's going on with react internals, but setting the showQuicksaveButtons to true/false immediately reflects the change, with the buttons beings visible/not-visible, I just hope it's not calling this callback for the component over and over again? I was expecting that once I change the state, the buttons would disappear/reappear only on changing the screen, not this.
window.modAPI.injectUI('crafting', (api) => {
if (!getSettings().showQuicksaveButtons) return;
return (
<QuickSaveLoadButtons
api={api}
stackProps={{ left: '138px', bottom: '0px', spacing: '5px' }}
/>
);
});
When showQuicksaveButtons is false, I intend to default to window.addEventListener('keyup', ...) for keybinds
The mod settings changing cause a page rerender
hmm not sure why I even asked this, would've been faster to just put some logs in there and see for myself, sometimes I'm so stupid
wait a minute? why? why require pillar creation when I have the techniques already?
This is a manual I already have btw, Manuals tab, not like some manual preview in sect manual pavilion
oh wait I'm dumb
the number of slots lol
wait lol I posted this in modding
well... it's best that way, few people saw this
"Straight" sexuality option doesn't disable the same gender choice in "Verdigris House" reminds me... I don't think game's preferences/settings are exposed to mods?
So a mod can't make use of the player's specified sexuality, in case they wanted to know it for some reason
Is there a trigger for when you press the button to enter combat or crafting that happens before the time passes, and before onPlayerEnterCombat?
What do you want to do?
Use the date information without relying on onPlayerEnterCombat because it happens too late in the stack of commands/calculations
I think even that's too late
Because time advances when you click the explore button
Best to track it outside that entirely
Yeah. I'm not sure what to trigger on to grab the date info though. It needs to persist through the time advance and then recalculate on moves or explores (which it does easily now), but I'm losing the date information for the action because it re-does the date Calc - after the time Calc and before combat
Hmmm
I can't just minus 1 it, because if it goes from 1099/30/4 to 1099/1/5 it's totally different numerology result
1099/12/31 - > 1100/01/01 would be a more apparent example
26 VS. 4
print out redux payloads and their action types
there should be a calendar update action, you could cache the date from there in some way I think
I can probably just declare a new variable or array to save it in, then pull from until the action ends 
But the calendar update is the issue I'm trying to work around.
The way I have it now, by the time you enter combat, or travel, or whatnot, you have to grab the day before, or multiple days before in the case of 2 day crafts at the sect, the chaperone missions with the traders, multi-day crafts on your own, etc, etc
there are more than "2 day" crafts, also a 2 day increment doesn't mean there are multiple calendar updates
I know. I've got to pc before. It's just an example. I expect you know a lot more examples too 
"including, but not limited to" 
The chaperone missions are 13 days iirc
yeah the escort ones
Okay idea: add a listener to the selected location dialog that caches the current day
Then use that
Can I get player's default base stats (for new game) on mod load?
hmm... could the player character be referenced by "he/him", "she/her" based on the gender instead? and put an option for the usage of "they/them" in the settings somewhere
I feel like dialogs wouldn't need to be modified too much, or at least no need for duplicates? - "...dialog... la la la {playerPronoun1}... la la la..."???
playerPronoun1 or 2 for he/she/they and him/her/them...
or is it going to be more complex than I'm imagining?
playerPronoun can be abbreviated to pp for short 
oh yeah... they are will have to be changed to he/she is and so on
probably some other stuff too
All the gender pairs would need to become trios
Probably not crazy amounts of work though
Want to request it?
yeah, I'll write it on a while, it'd be great if it's not too much work
personally they/them language feels very bland to me, when the referenced individual is a known entity and the gender is known
hmm I'll have to lookup what trios in this context is, don't know if there's a standard way to deal with pronouns in dialogues like this
oh so just hmm "he/she/they" is probably what you meant by that, the is/are could also be abstracted away into a property that gives the appropriate one based on the current pronoun, so still not much work, just one more variable in dialogues
btw, are the export/import material icons (@mui/icons-material) or game specific? I couldn't find them here in the list https://v7.mui.com/material-ui/material-icons/
if they're game specific I'm going to add in a request to expose game icon components to mods
save alt I get, but what about the other one, the one with the upward arrow?
Huh
FileDownloadOutlined,
FileUploadOutlined,
Is what I use, but they don't appear in the icon browser
I am 2 major versions behind so maybe they removed it from the latest
damn, I was looking in filled section
should use this for searching from now on, instead of using the mui site
https://fonts.google.com/icons?icon.set=Material+Icons
btw, this is a bug right?
#general message
another thing, I'm still not able to use keybinds in mystic realm, Esc in battles etc
I thought it was fixed
...How can I replicate ModReduxAPI.hasSave with just RootState (getGameStateSnapshot)? How is the boolean value determined?
Has save is essentially 'is redux available'
Redux is only available when a save is loaded. On the main menu it's not mounted
Bug it
turns out I can just call getGameStateSnapshot, it has the base stats
in main menu and new game char creation screen
anyway I can know 'is redux available' outside of react context?
so globallly
Try to read the ReactReduxContext
But you have to be inside the react context to do that
There's no global way afaik
Youd have to request that
in these cases, i usually fall-back to proper nouns or descriptions of the individual.
i typically get confused if too many pronouns are used before a name or descriptor is re-used... i'll forget who the person, or text, is talking about eventually π
why does onReduxAction have payload now π
well still not bad, could be useful, but I still need RootState in onReduxActionPayload since I'll be modifying the payload while looking up something in the current state on how to modify the payload exactly
there's also this issue #1500017492117033000 message
I'll fix this
No it's on payload
so afnm-types is wrong?
oh wait
im a moron
ah theres other broken stuff
annoying
ill have to do some weirdness with 0.6.54-2 or something π
hmm... now let's see if mod keybind feature is working or not
registerKeybinding is still scuffed
export interface KeybindingDefinition {
action: KeybindingAction;
category: KeybindingCategory;
displayName: string;
description: string;
defaultKey: string;
allowRebind: boolean;
}
not a valid action
you mean KeybindingAction
yeah that'd work
rebinding is not working
useKeybinding still uses the key that's specified in it
not the rebinded one
api.useKeybinding(0, {
F9: () => loadLastQuickSave(api),
});
window.modAPI.actions.registerKeybinding({
action: `${MOD_ID}.quickload` as KeybindingAction,
category: 'ui',
displayName: 'Quick Load',
description: '',
defaultKey: 'F9',
allowRebind: true,
});
Okay new build it up, and use 0.6.54-3 for the types
should fix:
keybindings type
keybindings not working
scroll in options
hasSave in the wrong place
no 'previous state' on payload reducer
hmm
keybindings not working
Still not fixed
rebinded keys with useKeybinding that is
and window.modAPI.utils.getHasSaveLoaded() returns true in main menu
so kinda useless lol
Also, for some reason, useKeybinding(0, ...) doesn't work in mysticalRegion screen
works in combat
if I set priority to 100, it works
Yeh you need priority 1 or more
oh
Because the dialogue sets priority 1
oh example now uses priority 10, I swear it was 0 in there before
Yeh
Try -1
I think that should be more reliable
But no idea
All the ones I've added are 1
On mystical region
Keybindings are a finiky thing because they have to correctly enable and disable based on dialogs and stuff
-1?? doesn't work
Darn
-1 makes it not work in combat either
wait a minute, are you telling me if the priority of my useKeybinding is higher than anything else on the screen, even if there's no keybinding conflict, those lower priorty keybinding simply won't work!!?
priorty 10 disabled b for auto battle etc
Yes
So you generally use it for disabling lower stuff
And every dialog open will context the keybindings inside them with a higher priority
So you can't accidentally keypress something under what you are interacting with
Generally you want to use priority 0
at least getRegisteredKeybindValue works
so I'm going back to the simpler world of window.addEventListener
Probably sensible
mod keybinds should probably namespaced by mod name, as in a heading
is it supposed to have duplicate entries like this if the string is in multiple places?
you have keybinds in tooltips hardcoded? π
Yes
Legacy
From before you could rebind
probably before there were translations too, because that made me remove t() from my string in Typography thinking the tool is supposed to auto pickup strings in tsx lol