NokiMo
Tinon
Tinon

patreon


Dev Log — June 2025

Hi everyone!

I hope everyone who's had a chance to play since the last update are enjoy the changes to the game and the new desire system!

Between bug- and hot-fixes, I've been busy working on the Steam client for the game and a new Favor system that will play into the PvE overhaul.

Steam Client

The Steam client is now virtually finished, and the only thing I need before a Steam release is missing features like the PvE content I want to be in the game before it is presented to a broader audience, and game audio that players will expect from a standalone game (maybe some achievements too).

The client also works as a DRM-free client without the Steamworks API, which I plan on sharing through the website and possibly itch.io as well.

NW.js

The Steam client is developed using NW.js, which is a combination of Chromium + Node.js for creating standalone HTML5-based applications. Any game developed in RPG Maker is HTML5-based and uses NW.js for distribution.

The game is embedded inside the NW.js application using an iframe, similar to how the game launcher for logging into the game is embedded on the front page of the website, and the game runs inside the client much like it would from any other browser, except, the client exposes exclusive standalone-features, which can be accessed through the game.

In essence, the same code is used for running the game on any platform and browser, including standalone clients. The game will then recognize when it is running inside a standalone client, and can access Steam- and NW.js-specific features when they are available to it.

Standalone Settings

Window controls are normally beholden to the browser, and no web-based application can change the size of its container tab, nor minimize the browser, etc.

Through NW.js, we can define proper minimum and maximum sizes for the window, and whether it can be resized or is displayed in fullscreen-mode. By default, the game will appear in a window the same size as on the website, but players are then able to resize the game. A new setting that only appears in standalone clients has been added that allows the game to run in proper fullscreen-mode (Alt + Enter can also be used to enter and exit fullscreen).

Another standalone-exclusive feature is the ability to quit the application, which will appear from the menu dropdown, during character selection, and from the new game menu that appears when pressing the Escape button. The game menu now also appears on the website when you press Escape.

Platform Support & Build Tests

While I have only tested the Windows build so far, NW.js supports building for Windows, Mac, and Linux, and I have successfully built for all platforms and uploaded to Steam, from which I have played the game on my Windows PC!

Steamworks API

One of the most challenging tasks was to implement all the necessary Steam features, in particular, the Steam overlay.

Players are now able to link and unlink their accounts to/from Steam. When you open the game through Steam for the first time, you can either create an account, play without registering, or log into an existing account, either of which will link the Battle Mages account to your Steam account, so you can access Steam features. The next time you open the game through Steam, it will log into the Battle Mages account linked to your Steam account.

When you access the shop through the Steam client, it will open inside the Steam overlay, where you will be able to use your Steam wallet. The Steam overlay and Chromium, however, are not good friends, and it required a lot of toiling to make the Steam overlay render properly.

The issue is that despite the Steam overlay being rendered on top of the application, it is being rendered by the application. So if the application doesn't redraw the window or parts of the window, the Steam overlay won't be redrawn either. This is usually not an issue inside a standard game application, where the window is rendered pixel by pixel, frame by frame. But that is not how web technology works. This is why most RPG Maker and other HTML5 games don't support the Steam overlay.

Browsers use rectangles or "bounding boxes" for tracking when and where changes occur on a webpage, and only redraw the pixels inside the changed or "dirty" rectangles when necessary. Since the vast graphics of most websites are static, this saves a lot of rendering calls and processing! It is the same principle that will sometimes cause a smeared-out version of a Steam pop-up to remain, often during a loading screen or in a black spot: the pixels are simply not being redrawn, and it will remain there until they are.

This is going to be quite technical, but I managed to support the Steam overlay with only a trivial increase in rendering processing by utilizing the iframe that was already used for running the game inside the client. To redraw the entire window every frame, I used requestAnimationFrame to call a function every frame. Inside this function, I would change the opacity of the iframe, which covers the entire window, to 99.99%, trigger reflow, then immediately set the opacity back to 100% before anything is drawn. By triggering reflow, when the frame is drawn, it will think the opacity has changed since the last frame was drawn, despite nothing changing. The downside of using reflow is that it will recalculate the rendering positions of all objects within the dirty rectangles, but not inside their child iframes! Because the game is running inside an iframe, only the virtually empty client hosting it will be recalculated!

The Steam overlay also covers the title bar for dragging and closing the window, which needs to be redrawn by Chromium. I managed to do this by changing the game's title each frame, between "Battle Mages: Erotica", with spaces, to "Battle Mages: Erotica" with a different whitespace character that looks exactly like a space, but uses a different unicode; enough to trick Chromium into thinking the title changed and redrawing the title bar, without any visual difference.

WebAssembly Issues

Something that caused unforeseen issues was loading WebAssembly (WASM) inside the client! Since NW.js is a combination of standard browser technology (Chromium) and Node.js, which load modules differently, WASM modules (used for image morphing and dynamic avatars) would load for Node.js usage, despite needing to be used for browser usage!

It took me a while to fix this in a way that didn't feel like a brittle workaround, but in this time, I learned something valuable that has allowed me to greatly optimize how dynamic avatars are loaded! One of the issues was that in the Node.js scope, WASM modules would be attached to the uppermost window after being loaded, instead of their current window. For instance, the iframe hosting the game inside the client is a child window to the client, so when a module was loaded, it would be attached to the client window, and when the game attempted to access the module from the game window, it was gone!

What's clever about this approach is that when an iframe is reloaded, the modules remain! My one gripe with my implementation of dynamic avatars is that each avatar has to be hosted inside its own iframe, since Live2D was designed around a single scene per window, and this meant loading and reloading the same module over and over again each time a character would appear! I had no idea you could simply attach the modules to the parent window and then reuse them for all child windows! The game now knows to look for modules in parent windows, and this change has drastically improved the loading process of dynamic avatars, now that the module only has to be loaded and initiated once and can be shared among avatars!

Favor

While working on the new PvE system, I realized I wanted to change part of the system. Previously, I planned on creating content where players would become temporary NPCs that could be interacted with by other players without the two players being directly aware of one another. This is similar to how multiplayer is sometimes seen in games like Fire Emblem: Three Houses, or Dark Souls, where you can give or receive messages by other players, or interact with an NPC representation of their character, well aware that your interaction will in some way affect another player without your direct interaction.

But such a system would mean implementing a whole new system for players to engage with one another, which would require a lot of time to include, and these systems are most commonly used to add some multiplayer aspect to games that wouldn't otherwise be multiplayer. Since Battle Mages is already multiplayer and has ample opportunity to turn a player into a roaming French maid instead of turning them into an NPC French maid, the system seems quite redundant.

The system was also thought to give players who didn't suffer an inanimate ending a timeout similar to that of an inanimate form, by locking them into the NPC state for 15 minutes; however, as most players will know, it's pretty easy to restore your form, and instead of locking players into their current form for 15 minutes, I would like to give them an incentive to remain in their current form, which is where favor comes into play.

Favor is given to players for completing challenges and can be exchanged for items and services. Before you can spend your favor, you must claim it from the favor screen.

Additional consolation favor is given to players after failing a challenge, but all unclaimed favor is revoked if they back out of the consequences!

Favor can also be earned by defeating other players. When you defeat another player, some of their unclaimed favor is transferred to you, and vice versa.

Instead of claiming favor for yourself, you can choose to award favor to other players you enjoyed playing with.

Favor cannot be claimed while in an encounter with another, during a scenario, or while you have been recently defeated.

Unclaimed favor will appear as a third star symbol next to your character, and will also appear as a status effect when you have any unclaimed favor that another player can earn from defeating you. Each will have a counter that increments for every 100th favor, to a maximum of 20 (2,000 favor, similar to how 2,000 crowns make up 20 Sol Talismans).

Favor has multiple purposes. It can be a reward for players who stay in their bad-end form, but it can also be used to entice and reward another player for stepping on your character. Players will be able to exchange favor for many of the same rewards you might find through PvE, allowing players who frequently defeat other players to earn their rewards through PvP combat instead of PvE. It's always difficult for a game such as Battle Mages to have a healthy dom-to-subs ratio, and the hope is that favor will second as a means for subs to reward dom players for their time and effort, and mitigate the need for doms to grind PvE to be competitive and powerful, since they will be able to earn their rewards through interacting with other players instead.

Claimed favor will turn into tokens, designed to look like poker chips to match Moira's casino dealer theme. Favor, once claimed, cannot be traded with other players and must be earned by the player themselves. Favor can be moved between characters on the same account.

Sticky or "Permanent" Effects

To make sure players cannot simply restore themselves without using the Restore Self, which would simply make the process slower and tedious, players will be given a class when they are defeated in PvE. Depending on the class, players won't be able to change certain aspects about themselves or their attire, and they will continue to gravitate towards the given class whenever they aren't in an encounter with another player. This means players cannot use an alt account to restore their character either, since they will revert after the encounter, nor can it be used to limit an opponent's actions during an encounter and create a sort of pseudo-immunity through permanence.

Animate Full Transformations

I also intend to include new special actions that cause animate "full transformations" on par with those suffered during a PvE bad ending, by giving your opponent a class and appropriate traits. Similar to inanimate special actions, your opponent's traits and effects will affect what classes are available, so you must bridge some of the gap towards a given class for it to become available.

I also like the idea of animate transformations that result in becoming an item that can be added to someone's inventory, like being shrunk down and bottled, or hogtied and carried. And there are still plans for minion and pet forms in the future!

---

That's it for this month's dev log!

In the next month, I will continue working on the new PvE system. Unfortunately, I've pushed a lot of work in front of me while finishing up the latest update and fixing bugs, so I have a lot I need to catch up on, but I will still find time to improve the game whenever I can!

I also miss streaming and chatting with the community. It's been a long time since my last stream, which is because a part of my setup broke and I haven't been able to find the time to fix it between working and working on the game 💔

But now that the update is out and most bugs have been taken care of, I hope to find a time to fix my setup so I can stream again in the near future!

Thank you so much for all your support and I hope to hang out with everyone soon!❤️

Cheers,

Tinon

Dev Log — June 2025

Related Creators