[MountBatten] Aesop's Fables | Live2Dで動くイソップ寓話 (English) - EDI

:framed_picture: Preview

5ee43b709163f

:information_source: About Game

Interactive Masturbation Support Game: Transforming a Pure Heroine into a Woman while battling the Sensation of Ejaculation

:scroll: Mod Info

A Tyranoscript mod that turned out to be a bit more challenging, since the game uses Live2D elements for some of the animations.
Big thanks to everyone who contributed over at [Game Integration] Modding Tyranoscript Games, especially @affqprow who provided essential parts of the code and @Falafel for making the scripts :sparkles:.
The mod also utilizes @dimnogro’s EDI.

Here are some more details, for those interested in the integration / using EDI with similar games:

Integration Details

First the definitions.csv and scripts have to be created. Dimnogro already explains this step well in the original Topic post: Easy Device Integration for Games. EDI Update [12/2023]. The Topic also contains a lot of instructions and helpful details, so definitely check that out first!

Using the EDI was actually fairly similar to how you would use the FunscriptPlayer integration, since it also utilizes HTTP calls.
To start with, we’ll need to define an “EDIPlayer” Object, which for Tyranoscript games (in the “kag.tag_ext.js”), and JavaScript integrations in general, would look something like this:

// ---- ENVIRONMENT & LOGGING SETUP----
var fs = require('fs');

var currentAnimation = null;
var AnimationPlaying = false;

// Polyfill for Object.fromEntries()
if (!Object.fromEntries) {
  Object.fromEntries = function (entries) {
    var obj = {};
    for (var i = 0; i < entries.length; i++) {
      var key = entries[i][0];
      var value = entries[i][1];
      obj[key] = value;
    }
    return obj;
  };
}
// Function to log messages to "EDI-Debug.txt" with local machine's time
var logBuffer = '';
var logTimer = null;

function logToFile(message) {
  var timestamp = new Date(); // Gets the current time
  var localTimestamp = timestamp.toLocaleString(); // Converts to local timezone format
  var milliseconds = timestamp.getMilliseconds(); // Get milliseconds
  var logMessage = '[' + localTimestamp + '.' + milliseconds + '] ' + message + '\n';
  logBuffer += logMessage;

  // Write the logs at certain time intervals
  if (!logTimer) {
    logTimer = setTimeout(function () {
      fs.appendFile('EDI-Debug.txt', logBuffer, function (err) {
        if (err) throw err;
      });
      logBuffer = '';
      logTimer = null;
    }, 50); // Increase when "enableGalleryNameLogging" is turned on, to reduce load on game
  }
}

// ---- DEFINE EDIPLAYER OBJECT ----
var EDIPlayer = {
  // Configuration:
  baseUrl: 'http://localhost:5000/Edi/', // base URL for the EDI server
  enableLogging: true, 					 // Set logging for "EDI-Debug.txt"
  enableGalleryNameLogging: true, 		 // Option to set additioal gallery-name logging

  // Helper function to extract gallery name from a full file name
  filterGalleryName: function (galleryName) {
    return galleryName.substr(0, galleryName.lastIndexOf('.')) || galleryName; // remove file extension
  },

  // Make an HTTP request
  makeRequest: function (url, options) {
    if (!this.enableLogging) {
      options.logMessage = false;
    }
	try {
	var requestStartTime = new Date();
	var xhr = new XMLHttpRequest();
      xhr.open(options.method || 'POST', url);
      for (var header in options.headers) {
        if (options.headers.hasOwnProperty(header)) {
          xhr.setRequestHeader(header, options.headers[header]);
        }
      }	
      xhr.onreadystatechange = function () {
        if (xhr.readyState === XMLHttpRequest.DONE) {
          var requestEndTime = new Date();
          var requestTimeMs = requestEndTime - requestStartTime;
          if (options.logMessage !== false) {
            var logMessage = '[EDIPlayer.makeRequest] URL: ' + url + '\n';
            logToFile(logMessage);
            var requestInfo = 'Request Method: ' + (options.method || 'POST') + '\n' +
              'Request Headers: ' + JSON.stringify(options.headers || { 'accept': '*/*' }) +
              (options.body ? 'Request Body: ' + JSON.stringify(options.body) + '\n' : '');
            logToFile(requestInfo);
            var responseInfo = 'Response Status: ' + xhr.status + ' ' + xhr.statusText + '\n' +
              'Response Headers: ' + JSON.stringify(xhr.getAllResponseHeaders()) + '\n';
            if (xhr.responseText) {
              logToFile(responseInfo);
              logToFile('Response Body: ' + xhr.responseText + '\n');
            } else {
              logToFile(responseInfo);
            }
            if (xhr.status >= 200 && xhr.status < 300) {
              var successMsg = 'Success: ' + xhr.statusText + ' (Request completed in ' + requestTimeMs + 'ms)\n\n';
              console.log(successMsg);
              logToFile(successMsg);
            } else {
              var errorMsg = 'Failed: ' + xhr.status + ' ' + xhr.statusText + ' EDI-Server not reachable \n';
              console.error(errorMsg);
              logToFile(errorMsg);
            }
          }
        }
      };
      xhr.send(options.body || null);
    } catch (error) {
      if (options.logMessage !== false) {
        var errorMsg = 'Error: ' + error + '\n';
        console.error(errorMsg);
        logToFile(errorMsg);
      }
    }
  },

  // Functions to control gallery playback
  playGallery: function (galleryName, seek) {
	if (!AnimationPlaying || currentAnimation !== galleryName) {
		if (!seek) seek = 0;
		var filteredName = this.filterGalleryName(galleryName);
		var url = this.baseUrl + 'Play/' + filteredName + '?seek=' + seek;
		this.makeRequest(url, { method: 'POST', logMessage: true });
		AnimationPlaying = true;
		currentAnimation = galleryName;
	}	
  },
  stopGallery: function () {
	if (AnimationPlaying) {  
		var url = this.baseUrl + 'Stop';
		this.makeRequest(url, { method: 'POST', logMessage: true });
		AnimationPlaying = false;
		currentAnimation = null;
	}	
  },
  pauseGallery: function () {
    var url = this.baseUrl + 'Pause';
    this.makeRequest(url, { method: 'POST', logMessage: true });
  },
  resumeGallery: function () {
    var url = this.baseUrl + 'Resume';
    this.makeRequest(url, { method: 'POST', logMessage: true });
  }, 
};

After defining the Object, we can call any functions within it at the right part of the code and reference the corresponding “gallery” variable. Example from this game:

// Play the scipt when the animation is called in the code
r.addEventListener("play", function(event) { 
	EDIPlayer.playGallery(e.storage); // where "e.storage" is used to call the animation
});
r.addEventListener("seeked", function(event) {
	EDIPlayer.playGallery(e.storage);
});
// Stop script after animation is finished
r.addEventListener("pause", function(event) {
	EDIPlayer.stopGallery();
});

I’ve implemented a simple logging function that writes the current requests and galleryname into a text file.
This will enable us to check if the integration is working and what galleryname is being pulled from the code, which should be especially helpful for integration in other games/engines.
The “EDI-Debug.txt” file will the saved in the same folder the EDI.exe is in.


Live2D animations will be handled differently in most cases, so we need to add some additional code if the game has them as well. In this case the motions where called in the Live2D “index.js”.
Since the game is calling multiple Live2D elements at the same time, we have to filter only the ones we want the EDI to detect.
I also changed the GalleryName that the game calls, by replacing certain parts, since the original one was quite long and contained a few file extensions. You could pretty much adjust the name however you like, as long as it is the same as what’s set in the definitions.csv:

// EDI-Intecept Live-2D Animation
var modelFileName = this._modelSetting.getModelFileName();
// Define all the scenes we'd like to use / skip all unwanted element calls
const keepStrings = ['Doris_blowjob', 'Doris_ride'];
if (keepStrings.some(str => modelFileName.startsWith(str))) {
	// Remove all unwanted parts from the filename
	const cleanedFileName = keepStrings.reduce((acc, str) => acc.replace(new RegExp(str, 'g'), ''), modelFileName);
	n = n.replace("motion/", "").replace(".motion3", "").replace(/[\\\/]/g, '');
	// Play script based on filename
	EDIPlayer.playGallery(`${cleanedFileName.replace(".moc3", "")}${n}`);
}
// ___END___

That’s pretty much the gist of it. EDI should now be able to intercept the animation calls and receive the corresponding gallery name to play the script.


I also made a full English translation for the game, including all the UI-Elements, and subtitles for the voice lines.
A small bug the original game had, where it was only possible to change one part of the settings at a time, was also fixed.
This is all already included in the mod file.

:memo: Notes

  • I tried to translate the game as well as possible, but since I don’t speak Japanese, some of the translation might not be entirely accurate.
    The game features more than 91 unique voice lines, accompanied by over 500 lines of dialogue that had to be integrated into the TyrenoBuilder code. If you notice any issues, please provide feedback.

  • Tested with the Handy on game v1.23

  • If you enjoyed this game, you might like the previous entry as well:


Big thanks to @Falafel for providing scripts for all the scenes:

https://discuss.eroscripts.com/t/mountbatten-aesops-fables-live2d-scripts-for-modding/120670

and @dimnogro for the EDI program:


:computer: How to use

  • Step 0. Get the game - [Live2Dで動くイソップ寓話 favicon-2]

  • Step 1. Download and extract the Mod into the game’s main directory (the patch file “Aesops_Fables.tpatch” should be next to the .exe)
    image

  • Step 2. On first launch, the game might load for a while (don’t panic) Capoo Sweating GIF - Capoo Sweating Bugcat GIFs.
    Afterwards, you should see the following message, meaning the patch was applied successfully! (the .tpatch file will also automatically get deleted) Capoo Bugcat GIF - Capoo Bugcat Cute GIFs
    Important: Wait a few more minutes (~5min) before pressing “OK”, as the game is still generating files in the background and won’t be able to launch otherwise.

  • Step 3. Launch “Edi.exe” and use your Handy key to connect over WIFI.
    You can also use the Intiface Bluetooth connection for the Handy and other devices as well.
    image

:link: Download Links

:card_file_box: Integration Mod:

:film_strip: If you want to modify the funscripts, here are the reference videos

:game_die: Game:

Changelog

  • 25.09.2023 - Added Japanese version of the Mod
  • 24.09.2023 - v1.0 Initial release
43 Likes

Thank you!!! I’ve been waiting for this :smile:

1 Like

Strangely, I have a problem here. I use bluetooth and Intiface to connect to Handy, but EDI close itself after the game has been running for a while. I also have this problem when using WIFI connection.

Can i get non-translation version?

1 Like

Sure, I’ll upload one later today.

Thanks you :heart_eyes:

1 Like

For some reason when i install the patch file the game wont start at all even after trying again and waiting 20 minutes for the patch to install
Any Ideas?

Check the size of your \resources\app.asar file. It should be around ~843MB.
If not, try again with a completely clean version of the game and when applying the patch, make sure to wait a few minutes before clicking ok, as described in Step2.
Since the game is re-generating the app.asar file in the background, so you’ll need to wait a bit for it to get back to the original size.

Thank you for sharing this

1 Like

I had issues with the resources/app.asar being very small (~1kb) after applying the patch but seems to have been from having brackets in the file path.

After renaming the folder it worked for me

3 Likes

Could someone share their version with the mod installed already? I tried english and japanese version, neither seems to work.

What game version are you using?
The mod was made for the latest v1.23. Earlier releases might not work, especially v1.00 since the game had a different structure back then.

Yeah I’ve got 1.0, I’ll update it.

Thank you for this! I really appreciated your work to get it working!

Does anyone know if this working with Tempest OSR2 or SSR2? I tried it using old Intiface Desktop (since new central doesn’t support it yet) and it’s not working.

Do I launched the game in correctly? I launched in following order:

  1. Intiface Desktop first
  2. game (already patch and waited over 10 mins)
  3. EDI - press reload galleries and reconnect
  4. OSR2/SSR2 both not responding
1 Like

Don’t think OSR2 is supported yet.
I usually open EDI (+Intiface) first and get everything connected, then load the game.

Last night I was using (for like the 4th time, it’s pretty great) and was noticing that a lot of the time the script was just stopping randomly in the middle of an animation or doing one movement and stopping or when the girl stopped sometimes the script also stopped but then in the middle it kept going while she was standing still.

From looking at the code it’s hard to say for me but from the logs I think I can see why the first case might happen at least:

[1/27/2024, 10:39:02 PM.931] [EDIPlayer.makeRequest] URL: http://localhost:5000/Edi/Stop

[1/27/2024, 10:39:02 PM.931] Request Method: POST
Request Headers: {"accept":"*/*"}
[1/27/2024, 10:39:02 PM.931] Response Status: 200 OK
Response Headers: "content-length: 0\r\ndate: Sat, 27 Jan 2024 22:39:02 GMT\r\nserver: Kestrel\r\n"

[1/27/2024, 10:39:02 PM.931] Success: OK (Request completed in 8ms)


[1/27/2024, 10:39:02 PM.932] [EDIPlayer.makeRequest] URL: http://localhost:5000/Edi/Play/4_2_2_1_a?seek=0

[1/27/2024, 10:39:02 PM.933] Request Method: POST
Request Headers: {"accept":"*/*"}
[1/27/2024, 10:39:02 PM.933] Response Status: 200 OK
Response Headers: "content-length: 0\r\ndate: Sat, 27 Jan 2024 22:39:02 GMT\r\nserver: Kestrel\r\n"

[1/27/2024, 10:39:02 PM.933] Success: OK (Request completed in 2ms)

It’s not 100% clear for me but would it be possible for the Stop request to have actually be executed right after the Play because it took a few milliseconds more? Most of the time the gap between the Stop and Play is bigger but I’m seeing a lot of sub-10ms differences which is probably the random stops after 1 action that I was seeing. It was also weird in how I pressed stopped, it continued stopped but when the animation resumed it did one action again and then stopped, but if I set it to speed up then it’d mostly be fine. I don’t see any Pause/Resume on the logs so I don’t think it’d be an issue with those.

Another thing I was thinking about which I’m not sure if it’d be related, does it matter if the game is on normal or fast? I think on fast I never had issues but I do prefer to play on normal (but seems like desyncs happen more often), this also in part because if I tell her to speed up sometimes it’ll reach a point where it slows down into normal motion and then speeds up again kinda at random, this makes me think it’s how the game handles her climaxing quicker in fast mode but I’m not sure how that’d mess with the commands.

[1/27/2024, 10:23:40 PM.433] [EDIPlayer.makeRequest] URL: http://localhost:5000/Edi/Play/idle?seek=0
[1/27/2024, 10:25:00 PM.80] [EDIPlayer.makeRequest] URL: http://localhost:5000/Edi/Play/stay?seek=0

This is another message which I’m not sure would be relevant to the issues, as there’s no “idle” or “stay” funscripts, so what does it try to do here?

3 Likes

So I was testing it a bit more while paying attention the log and can confirm that

[1/28/2024, 3:42:17 PM.301] [EDIPlayer.makeRequest] URL: http://localhost:5000/Edi/Stop

[1/28/2024, 3:42:17 PM.301] Request Method: POST
Request Headers: {"accept":"*/*"}
[1/28/2024, 3:42:17 PM.301] Response Status: 200 OK
Response Headers: "content-length: 0\r\ndate: Sun, 28 Jan 2024 15:42:16 GMT\r\nserver: Kestrel\r\n"

[1/28/2024, 3:42:17 PM.301] Success: OK (Request completed in 6ms)


[1/28/2024, 3:42:17 PM.302] [EDIPlayer.makeRequest] URL: http://localhost:5000/Edi/Play/4_2_2_1_b?seek=0

[1/28/2024, 3:42:17 PM.302] Request Method: POST
Request Headers: {"accept":"*/*"}
[1/28/2024, 3:42:17 PM.302] Response Status: 200 OK
Response Headers: "content-length: 0\r\ndate: Sun, 28 Jan 2024 15:42:16 GMT\r\nserver: Kestrel\r\n"

[1/28/2024, 3:42:17 PM.302] Success: OK (Request completed in 3ms)

Did not actually disrupt normal function.

And this time it was actually fine for over 20 minutes of the starting part from what I heard (had the game on another screen and was just listening to the handy in the background).

What I did find is that actually trying to reconnect EDI in an attempt to fix desyncs (in this case I didn’t have any so I just clicking to see what effect it had) just made it worse and it seems like it starts being random and shifting in and out of scripts.

I suppose the issues I had last time were just a combination of not starting EDI properly and trying to reconnect without completely restarting it.

For the issues I was able to simulate today:

  • The game can stay running
  • Close EDI
  • Press the power button on the handy and shifting between wifi and manual mode to be sure
  • Reopen EDI and wait for it reconnect

There was still one time where a script randomly did not play for a webm but pretty minor when testing for 40 minutes in total.

2 Likes

Thanks for the detailed feedback!

The occasional stutters could be caused by the game itself, since the Live2d animations are generated in real time and aren’t always all that flawless, from what I saw.

There might still be some bugs related to connectivity, or the EDI program.
You could try updating to the latest EDI Player version and see if that helps.

Please let me know if you have the same issues with the latest version of EDI.

2 Likes