
/**
* Logs all method calls of the object
*
@param {*} obj
*/
hook_all_prototypes: function(obj) {
// Hook all prototypes of the object to log the method calls
for (let method_name of Object.getOwnPropertyNames(obj.__proto__)) {
const original = obj.__proto__[method_name];
obj.__proto__[method_name] = function(...args) {
console.log(`Calling ${method_name} with arguments:`, args);
return original.apply(this, args);
}
}
},
/**
* Some functions may throw errors and thus crash the game. This function
* insulates the function by catching the error and logging it instead.
*
* This is not a substitute for proper error handling, but it can be useful
* for quick debugging.
*
* A default return value can be provided in case the function would normally return something.
* @param {*} obj
* @param {string} method_name
* @param {*} default_return
*/
make_safe: function(obj, method_name, default_return) {
// Insulate a function, using prototype pollution to make it safe
const original = obj.__proto__[method_name];
obj.__proto__[method_name] = function(...args) {
try {
return original.apply(this, args);
} catch (err) {
console.error(err);
// If this returned something normally we now have a problem, maybe.
return default_return;
}
}
},
/**
* Once the game is loaded, call the callback(s) to execute.
* The callbacks will only be executed ONCE
* @param {function} callback
*/
once_loaded: function(callback) {
this.loaded_callbacks = this.loaded_callbacks || [];
this.loaded_callbacks.push(callback);
console.log('Added callback to loaded_callbacks:', callback);
},
call_loaded_callbacks: function() {
console.log('Calling loaded_callbacks:', this.loaded_callbacks);
this.loaded_callbacks = this.loaded_callbacks || [];
for (var callback of this.loaded_callbacks) {
callback(this);
}
}
};
// Make the modloader object available in the global scope
window.tModLoader = new tModLoader();
// Log that the modloader is loaded
console.log('Modloader loaded:', window.tModLoader);
})();
/*
Example usage of the modloader
------------------------------
Patch a couple of problematic functions once the game is loaded.
These patches are purely bug fixes and do not add any new functionality.
*/
window.tModLoader.once_loaded(function(modloader) {
console.log('The game has been loaded, patching some functions now...');
// Patch some functions here to provide additional functionality
// tWgm.tGameItem.getItemName has a bug where it sometimes throws an error.
// Adding a default return value to make it safe
modloader.make_safe(tWgm.tGameItem, 'getItemName', 'Unknown Item');
});
window.tModLoader.once_loaded(function(modloader) {
console.log('This is from the modloader, we\'re loaded!');
// Using prototype pollution to hook into the game
modloader.hook_prototype(tWgm.tGameMenu, 'viewMenu');
modloader.hook_prototype(tWgm.tGameCharactor, 'addItem')
//hook_all_prototypes(tWgm.tGameCharactor);
// Hook tGameMenu prototypes to potentially make our own menu
modloader.hook_prototype(tWgm.tGameMessageWindow, 'viewMessageWindow', function(self, original, args) {
// Attempt to isolate the escape menu
var answers = args[0].answers;
// Check if we have an option for 'Return to title screen'
var isMainMenu = answers.find(answer => answer.message === tWgm.tGameTalkResource.talkData.system.gototitle) !== undefined;
isMainMenu &&= answers.find(answer => answer.message === tWgm.tGameTalkResource.talkData.system.itemmiru) !== undefined;
// If we have the main menu, we can potentially replace it with our own.
// For now just add a dummy button which logs a message
if (isMainMenu) {
answers.push({
message: '打开mod',
callBack: function() {
// Show a new menu
var answers = [];
// Add a dummy button
answers.push({
message: '返回',
callBack: function() {
console.log('Dummy button pressed');
tWgm.tGameRoutineMap.setFrameAction(tWgm);
}
});
answers.push({
message: '改数的调用窗口,无效果',
callBack: function() {
tWgm.tGameSoundResource.play('select');
tWgm.tGameNumWindow.viewNumWindow({
label: tWgm.tGameTalkResource.talkData.system.ikutsuoku,
startNum: 1,
minNum: 1,
maxNum: 999999,
isSelectSound: false,
selectCallBack: function(num) {
console.log('Selected number:', num);
tWgm.tGameRoutineMap.setFrameAction(tWgm);
},
cancelCallBack: function() {
console.log('Cancelled number selection');
tWgm.tGameRoutineMap.setFrameAction(tWgm);
}
});
}
});
answers.push({
message: '获得物品',
callBack: function () {
// Go through the object `tWgm.tGameItem.masterData.items` to get all items
var all_items = [];
// Structure of `tWgm.tGameItem.masterData.items`:
// { item_id: [ some_data, some_data, ... ], ... }
for (var item_id in tWgm.tGameItem.masterData.items) {
var master_data = tWgm.tGameItem.masterData.items[item_id];
var item_data = tWgm.tGameItem.createItem({
itemId: item_id,
isShikibetsu: true, // Is identified item
isNoroi: false // Is cursed item
});
all_items.push(item_data);
}
tWgm.tGameItemWindow.viewItemWindow({
label: "这个是对话栏标签,随便写点什么",
items: all_items,
isSelectClear: !1,
isViewPrice: !1,
isSelectSound: !1,
isSell: !1,
selectItemCallBack: function(index) {
console.log('Selected item index:', index);
var item = all_items[index];
// Add the item to the player inventory?!
// tWgm.tGameCharactor.addItem("player", item, 1);
tWgm.tGameNumWindow.viewNumWindow({
label: tWgm.tGameTalkResource.talkData.system.ikutsuoku,
startNum: 1,
minNum: 1,
maxNum: 999999,
isSelectSound: false,
selectCallBack: function(num) {
console.log('Selected number:', num);
// If it's a money item, we need to use addMoney
if (item[1] >= 99999990001) {
tWgm.tGameCharactor.addMoney(tWgm.tGameCharactor.charas.player, num, -1, item[1]);
} else {
item[10] = num;
tWgm.tGameCharactor.addItem("player", item, 1);
}
tWgm.tGameItemWindow.clear();
tWgm.tGameRoutineMap.setFrameAction(tWgm);
},
cancelCallBack: function() {
console.log('Cancelled number selection');
}
});
},
cancelCallBack: function() {
console.log('Cancelled item selection');
tWgm.tGameRoutineMap.setFrameAction(tWgm);
},
bottomData: tWgm.tGameItemWindow.getCharactorBottomData(tWgm.tGameCharactor.charas.player)
});
}
})
// Display the ModMenu
tWgm.tGameMessageWindow.viewMessageWindow({
selectedIndex: 0,
answers: answers,
viewItemMaxNum: 12,
position: [null, args[0].position[1]],
cancelCallBack: function() {
console.log('ModMenu closed due to `cancelCallBack`');
tWgm.tGameRoutineMap.setFrameAction(tWgm);
},
});
}
});
}
original.apply(self, args);
});
});
// Enable native logging
tWgm.isL = true;
// Wait for the game to load...
setTimeout((() => window.tModLoader.call_loaded_callbacks()), 1000);
