Tổng hợp các chức năng mod đơn giản trong NRO
Tổng hợp các chức năng mod đơn giản trong NRO
Đây sẽ là bài viết tổng hợp lại các mod những chức năng đơn giản, cơ bản trong NRO. Các chức năng này có thể tạo ra chỉ với vài dòng code đơn giản. Mình sẽ update liên tục nên thiếu chức năng hoặc có sai sót nào các bạn có thể comment lại mình sẽ bổ sung.
Mình sử dụng Visual Studio để mod nên ai muốn dùng cách khác mà code lỗi thì tự tìm cách sửa lỗi nhé.
Các đoạn code chỉnh sửa trong game mình sẽ trình bày dưới dạng sau:
// <Tên class>
<Thư viện khai báo thêm nếu có>
... (Mô tả doạn code nếu có)
<Hàm nhận dạng>
{
...
<Câu lệnh nhận dạng> (nếu không có câu lệnh này và dòng ... phía trên tức là chỉnh sửa ở đầu hàm)
/*Ghi chú thao tác với code (nếu có)*/
// Giải thích code (nếu có)
<Code viết thêm>
...
}
...
Các thẻ <Nội dung mô tả>
sẽ bị thay thể bởi một thứ gì đó theo nội dung mô tả trong thẻ.
Mình sẽ sử dụng kỹ thuật xử lý lệnh chat mod NRO các bạn hãy xem trước tại: https://phucprotein.blogspot.com/2021/05/ki-thuat-xu-ly-lenh-chat-mod-nro.html
Mod đổi khu bằng lệnh chat
Để đối khu ta sử dụng lệnh:
Service.gI().requestChangeZone(zone, -1);
zone
là khu ta muốn đổi sang, -1
là cái số viết cho có (admin code game làm thừa)
Mod đổi khu bằng lệnh chat
// Service
...
public void chat(string text)
{
// Đổi khu theo lệnh chat kzX
if (IsGetInfoChat<int>(text, "kz"))
{
int zone = GetInfoChat<int>(text, "kz");
// Lệnh đổi khu
requestChangeZone(zone, -1);
}
...
}
...
Mod gọi NPC nhanh
Để gọi nhanh NPC trong map phù hợp dùng lệnh:
Service.gI().openMenu(idNpc);
idNpc
: là id của NPC cần gọi mỗi NPC có một id riêng (đi hỏi người khác hoặc tự tìm)
Một số NPC có thể gọi bất cứ lúc nào.
Ví dụ mod lệnh gọi npc và lệnh gọi Ca Lích để tới tương lai.
// Service
...
public void chat(string text)
{
if (text.Equals("tl"))
{
// id NPC Ca Lích là 38
openMenu(38);
}
else if (IsGetInfoChat<int>(text, "npc"))
{
int idNpc = GetInfoChat<int>(text, "npc");
openMenu(idNpc);
}
...
}
...
Mod tốc độ game và hiển thị thông báo
Cài đặt tốc độ game
Ta cần khai báo thư viện UnityEngine
và sử dụng lệnh sau để thay đổi tốc độ game:
Time.timeScale = speed;
speed
là tốc độ game có kiểu dữ liệu float
giá trị càng lớn tốc độ game càng nhanh.
Cài đặt tốc độ game mặc định khi khởi động là 2:
// Main
using UnityEngine; /*Nếu đã có không cần khai báo lại*/
...
private void Start()
{
Time.timeScale = 2f;
...
}
...
Hiển thị thông báo
Sử dụng lệnh sau để hiển thị thông báo:
GameScr.info1.addInfo(<Nội dung thông báo>, 0);
Thêm lệnh chat cài đặt tốc độ game và thông báo tốc độ game hiện tại:
// Service
using UnityEngine;
...
public void chat(string text)
{
// Cài đặt tốc độ theo lệnh chat spdX
if (IsGetInfoChat<float>(text, "spd"))
{
float speed = GetInfoChat<float>(text, "spd");
// Lệnh chỉnh tốc độ game
Time.timeScale = speed;
// Lệnh gọi thông báo
GameScr.info1.addInfo("Tốc độ game: " + speed, 0);
}
...
}
...
Mod tốc độ chạy
Để chỉnh tốc độ chạy ta sẽ dùng lệnh sau:
Char.myCharz().cspeed = X;
X
là tốc độ chạy nhân vật có kiểu dữ liệu int
mặc định ban đầu là 4, giá trị càng lớn tốc độ chạy càng nhanh.
Do admin game đã fix nếu di chuyển nhanh tốc độ chạy sẽ bị hạ về 1 rồi trở về bình thường nên ta cần chỉnh sửa tốc độ chạy liên tục bằng cách bỏ lệnh trên vào một hàm update nào đó (thường là void GameScr.update()
)
Mod lệnh chat chỉnh tốc độ chạy:
// GameScr
...
public override void update()
{
Char.myCharz().cspeed = tdc;
...
}
// Tạo một biến lưu giá trị tốc độ chạy
public static int tdc = 8;
...
// Service
...
public void chat(string text)
{
if (IsGetInfoChat<int>(text, "tdc"))
{
GameScr.tdc = GetInfoChat<int>(text, "tdc");
GameScr.info1.addInfo("Tốc độ chạy: " + GameScr.tdc, 0);
}
...
}
...
Mod hiển thị ID NPC cùng với tên NPC
Thay tất cả (g, this.template.name
thành (g, this.template.name + "(" + this.template.npcTemplateId + ")"
trong void paintName(mGraphics g)
của class Npc
.
Mình không muốn vì muốn ngắn mà lại chơi chiêu đổi tên NPC, sau này lỡ muốn dùng tên NPC thì mệt lắm. Các bạn thì có thể tự tìm và sử dụng cách đổi tên NPC
// Npc
...
public new void paintName(mGraphics g)
{
...
if (this.template == null)
{
return;
}
/*Từ đây đến cuối hàm thay lại bằng code phía dưới*/
if (this.template.npcTemplateId == 3)
{
if (global::Char.myCharz().npcFocus != null && global::Char.myCharz().npcFocus.Equals(this))
{
mFont.tahoma_7_yellow.drawString(g, this.template.name + "(" + this.template.npcTemplateId + ")", this.cx, this.cy - this.ch - mFont.tahoma_7.getHeight() - 5, mFont.CENTER, mFont.tahoma_7_grey);
}
else
{
mFont.tahoma_7_yellow.drawString(g, this.template.name + "(" + this.template.npcTemplateId + ")", this.cx, this.cy - this.ch - 3 - mFont.tahoma_7.getHeight(), mFont.CENTER, mFont.tahoma_7_grey);
}
this.dyEff = 60;
}
else if (this.template.npcTemplateId != 4)
{
if (this.template.npcTemplateId == 50 || this.template.npcTemplateId == 51)
{
if (this.duahau != null)
{
int num = 0;
if (SmallImage.imgNew[this.duahau[this.duaHauIndex]] != null && SmallImage.imgNew[this.duahau[this.duaHauIndex]].img != null)
{
num = mGraphics.getImageHeight(SmallImage.imgNew[this.duahau[this.duaHauIndex]].img);
}
if (global::Char.myCharz().npcFocus != null && global::Char.myCharz().npcFocus.Equals(this))
{
mFont.tahoma_7_yellow.drawString(g, this.template.name + "(" + this.template.npcTemplateId + ")", this.cx, this.cy - this.ch - mFont.tahoma_7.getHeight() - num, mFont.CENTER, mFont.tahoma_7_grey);
}
else
{
mFont.tahoma_7_yellow.drawString(g, this.template.name + "(" + this.template.npcTemplateId + ")", this.cx, this.cy - this.ch - 8 - mFont.tahoma_7.getHeight() - num + 16, mFont.CENTER, mFont.tahoma_7_grey);
}
}
}
else if (this.template.npcTemplateId == 6)
{
if (global::Char.myCharz().npcFocus != null && global::Char.myCharz().npcFocus.Equals(this))
{
mFont.tahoma_7_yellow.drawString(g, this.template.name + "(" + this.template.npcTemplateId + ")", this.cx, this.cy - this.ch - mFont.tahoma_7.getHeight() - 16, mFont.CENTER, mFont.tahoma_7_grey);
}
else
{
mFont.tahoma_7_yellow.drawString(g, this.template.name + "(" + this.template.npcTemplateId + ")", this.cx, this.cy - this.ch - 8 - mFont.tahoma_7.getHeight(), mFont.CENTER, mFont.tahoma_7_grey);
}
}
else
{
if (TileMap.mapID != 51)
{
int num2 = 15;
if (this.template.npcTemplateId == 47)
{
num2 = 47;
}
if (global::Char.myCharz().npcFocus != null && global::Char.myCharz().npcFocus.Equals(this))
{
if (TileMap.mapID != 113)
{
mFont.tahoma_7_yellow.drawString(g, this.template.name + "(" + this.template.npcTemplateId + ")", this.cx, this.cy - this.ch - mFont.tahoma_7.getHeight() - num2, mFont.CENTER, mFont.tahoma_7_grey);
}
}
else
{
num2 = 8;
if (this.template.npcTemplateId == 47)
{
num2 = 40;
}
if (TileMap.mapID != 113)
{
mFont.tahoma_7_yellow.drawString(g, this.template.name + "(" + this.template.npcTemplateId + ")", this.cx, this.cy - this.ch - num2 - mFont.tahoma_7.getHeight(), mFont.CENTER, mFont.tahoma_7_grey);
}
}
}
this.dyEff = 65;
}
}
}
...
Mod kích thước khi khởi động
Trong hàm void Start()
của class Main
ta chú ý các dòng sau:
// Main
...
private void Start()
{
...
if (Main.isPC)
{
this.level = Rms.loadRMSInt("levelScreenKN");
if (this.level == 1)
{
// Cài đặt kích thước 720x320 nếu cấu hình màn hình nhỏ (this.level == 1)
Screen.SetResolution(720, 320, false);
}
else
{
// Cài đặt kích thước 1024x600 cho cấu hình màn hình lớn
Screen.SetResolution(1024, 600, false);
}
}
}
...
Screen.SetResolution(width, height, false);
: lệnh cài đặt kích thước cho cửa sổ.width
: chiều rộng.height
: chiều cao.false
viết theo người ta chứ mình cũng không biết.
Mod cố định kích thước màn hình khi khởi động là 240x320 (không bị ảnh hưởng bởi cấu hình màn hình lớn hay nhỏ):
// Main
...
private void Start()
{
...
if (Main.isPC)
{
/* Xoá tất cả những thứ này
this.level = Rms.loadRMSInt("levelScreenKN");
if (this.level == 1)
{
Screen.SetResolution(720, 320, false);
}
else
{
Screen.SetResolution(1024, 600, false);
}
*/
Screen.SetResolution(240, 320, false);
}
}
...
Mod cấu hình game khi khởi động
Tìm đến class MotherCanvas
hàm void checkZoomLevel(int w, int h)
thay đổi giá trị biến mGraphics.zoomLevel
trong này.
mGraphics.zoomLevel
là cấu hình game mang giá trị 1 tương ứng với màn hình nhỏ (cấu hình thấp), từ 2 đến 4 tương ứng màn hình lớn (cấu hình cao, càng lớn ảnh sẽ càng rõ và đẹp).
Kinh nghiệm từng nghe của mình (chưa kiểm tra): Cấu hình cao treo sẽ đỡ lag hơn cấu hình thấp 🤣🤣 (2, 3, 4 như nhau nha không phải càng cao càng tốt).
Ví dụ mod cố định cấu hình game khi khởi động là cấu hình thấp (không phụ thuộc vào kích thước game):
// MotherCanvas
...
public void checkZoomLevel(int w, int h)
{
/* Xoá tất cả code trước đó của hàm này
if (Main.isWindowsPhone)
{
mGraphics.zoomLevel = 2;
if (w * h >= 2073600)
{
mGraphics.zoomLevel = 4;
}
else if (w * h > 384000)
{
mGraphics.zoomLevel = 3;
}
}
else if (!Main.isPC)
{
if (Main.isIpod)
{
mGraphics.zoomLevel = 2;
}
else if (w * h >= 2073600)
{
mGraphics.zoomLevel = 4;
}
else if (w * h >= 691200)
{
mGraphics.zoomLevel = 3;
}
else if (w * h > 153600)
{
mGraphics.zoomLevel = 2;
}
}
else
{
mGraphics.zoomLevel = 2;
if (w * h < 480000)
{
mGraphics.zoomLevel = 1;
}
}*/
mGraphics.zoomLevel = 1;
}
...
Mod xoá tàu vũ trụ
Các bạn thêm đoạn code sau vào hàm void update()
của class Teleport
. Không giải thích thêm.
// Teleport
...
public void update()
{
if (isMe)
{
Char.myCharz().isTeleport = false;
if (type == 0)
{
Controller.isStopReadMessage = false;
Char.ischangingMap = true;
}
Teleport.vTeleport.removeElement(this);
return;
}
...
}
...
Sử dụng capsual bay
Để sử dụng capsual bay ta dùng lệnh:
// 193 là id capsual thường, 194 là id capsual đặc biệt
Service.gI().useItem(0, 1, -1, 193); // Capsual thường
/*Hoặc*/
Service.gI().useItem(0, 1, -1, 194); // Capsual vip
Để chọn map tới ta dùng lệnh:
Service.gI().requestMapSelect(index);
index
: là thứ tự map cần đến, thứ tự hiển thị map của tab capsual được mở lên bắt đầu từ 0.
Ví dụ mod sử dụng capsual và lựa chọn map đầu tiên nhanh với phím tắt C:
// GameScr
...
public override void updateKey()
{
...
else if (GameCanvas.keyAsciiPress == 114)
{
ChatTextField.gI().startChat(this, string.Empty);
}
else if (GameCanvas.keyAsciiPress == 'c')
{
/*typeCapsual là loại capsual sử dụng do mình tự qui ước mang giá trị 0 là không sử dụng capsual, 1 là sử dụng capsual thường, 2 là sử dụng capsual vip.*/
int typeCapsual = 0;
// Kiểm tra capsual bay trong hành trang
Item[] items = Char.myCharz().arrItemBag;
for (int i = 0; i < items.Length; i++)
{
if (items[i] != null)
{
if (items[i].template.id == 194)
{
typeCapsual = 2;
break;
}
if (items[i].template.id == 193)
{
typeCapsual = 1;
}
}
}
if (typeCapsual != 0)
{
if (typeCapsual == 1)
{
Service.gI().useItem(0, 1, -1, 193);
}
else
{
Service.gI().useItem(0, 1, -1, 194);
}
/* Nếu chỉ muốn bật capsual không chọn map đầu thì bỏ dòng dưới*/
Service.gI().requestMapSelect(0);
}
}
...
}
...
Gọi đệ tử khi vào game
Nghe bảo vào game muốn gọi đệ tử ra up cần nhảy nhẹ một cái 🤣🤣. Chán cho mấy ông làm mod cũng nghe theo rồi cho acc vào game nhảy một phát nhìn hài lắm, up đệ các thứ nhảy lên nó cũng rời vị trí đi mất hay.
Muốn gọi đệ tử ra khi vào game chỉ cần di chuyển nhẹ một phát thôi và với code thì ta có thể làm mà mắt thường không nhận ra sự di chuyển.
Khi mod game hãy cố gắng tìm gốc rễ vấn đề rồi làm sao cho tối ưu nhất!
Ngoài ra mình thấy lúc mới chuyển map mà chưa di chuyển nhân vật đệ tử cũng chưa xuất hiện nên làm luôn một thể.
Mình tìm ra được hàm void loadInfoMap(Message msg)
ở class Controller
sẽ được gọi khi vào game và mỗi lần chuyển map. Vì vậy mình sẽ cho nhân vật di chuyển mỗi khi gọi hàm đó là được.
Lưu ý khi chỉnh trong class Controller
dnSpy không hỗ trợ chỉnh sửa hàm void onMessage(Message msg)
nên nếu sử dụng dnSpy ta phải sử dụng tính năng Edit Method (C#) để chỉnh sửa hàm void loadInfoMap(Message msg)
. Ta cũng có thể sử dụng Visual Studio 2019 để mod game sẽ không gặp phải tình trạng này.
// Controller
...
public void loadInfoMap(Message msg)
{
...
Cout.LogError("----------DA CHAY XONG LOAD INFO MAP");
// Di chuyển rồi về lại vị trí cũ ngay lập tức
Char.myCharz().cy--;
Service.gI().charMove();
Char.myCharz().cy++;
...
}
...
Mình phải bỏ phần auto skill và phần khoá vị trí đệ tử ra vì nó vượt quá mức cơ bản, nó sẽ sớm xuất hiện ở bài viết khác các bạn thông cảm
Nhận xét
Đăng nhận xét