From c80e4d95db756c941ccc8d0ea3813971cc0672ca Mon Sep 17 00:00:00 2001
From: Jesusaves <cpntb1@ymail.com>
Date: Sat, 16 Mar 2019 00:43:52 -0300
Subject: Add several stuff and several bugfixes (specially hidden errors).
 Most importantly, apartment system, a branch from Real Estate System.

---
 npc/009-6/doorbell.txt  |  10 +-
 npc/009-7/doorbell.txt  |  10 +-
 npc/012-8/doorbell.txt  |  10 +-
 npc/017-1/guild.txt     |   8 +-
 npc/017-7/doorbell.txt  |  10 +-
 npc/017-8/doorbell.txt  |  10 +-
 npc/024-13/_import.txt  |   1 +
 npc/024-13/manager.txt  | 123 ++++++++++++++++++
 npc/024-14/_import.txt  |   2 +
 npc/024-14/doorbell.txt | 323 ++++++++++++++++++++++++++++++++++++++++++++++++
 npc/024-14/utils.txt    | 147 ++++++++++++++++++++++
 npc/functions/timer.txt |   7 ++
 12 files changed, 632 insertions(+), 29 deletions(-)
 create mode 100644 npc/024-13/manager.txt
 create mode 100644 npc/024-14/doorbell.txt
 create mode 100644 npc/024-14/utils.txt

(limited to 'npc')

diff --git a/npc/009-6/doorbell.txt b/npc/009-6/doorbell.txt
index 2b461e8de..ddcf1021a 100644
--- a/npc/009-6/doorbell.txt
+++ b/npc/009-6/doorbell.txt
@@ -27,7 +27,7 @@
 009-6,32,34,0	script	Doorbell#RES_0096	NPC_NO_SPRITE,{
     // Name, Layer, Price, ID, x1, y1, x2, y2,
     function create_object {
-        array_push(.name$, getarg(0));
+        array_push(.nams$, getarg(0));
         array_push(.layer, getarg(1));
         array_push(.price, getarg(2));
         array_push(.objid, getarg(3));
@@ -165,7 +165,7 @@ L_ContinuousLoop:
 
     // Create a second array (@valid_ids) with the ID of objects within @re_col group
     for (.@i=0; .@i < getarraysize(.layer); .@i++) {
-        //debugmes "Found object ID %d named %s on layer %s coords (%d,%d) - Looking for layer %d", .@i, .name$[.@i], .layer[.@i], .x1[.@i], .y1[.@i], @re_col;
+        //debugmes "Found object ID %d named %s on layer %s coords (%d,%d) - Looking for layer %d", .@i, .nams$[.@i], .layer[.@i], .x1[.@i], .y1[.@i], @re_col;
         if (.layer[.@i] == @re_col)
             array_push(@valid_ids, .@i);
     }
@@ -176,9 +176,9 @@ L_ContinuousLoop:
     for (.@j=0; .@j < getarraysize(@valid_ids); .@j++) {
         .@i=@valid_ids[.@j];
         if (realestate_hasmobilia(.id, .layer[.@i], .objid[.@i]))
-            @menuentries$+=l("Sell ")+.name$[.@i]+l(" for ") + format_number( realestate_sellprice(.id,.price[.@i]) ) +":";
+            @menuentries$+=l("Sell ")+.nams$[.@i]+l(" for ") + format_number( realestate_sellprice(.id,.price[.@i]) ) +":";
         else
-            @menuentries$+=l("Purchase ")+.name$[.@i]+(" for ") + format_number( .price[.@i] )+":";
+            @menuentries$+=l("Purchase ")+.nams$[.@i]+(" for ") + format_number( .price[.@i] )+":";
     }
     select (@menuentries$);
     mes "";
@@ -232,7 +232,7 @@ OnInit:
 
     // Arrays
     // We go element by element on the array building the menu
-    .name$="";
+    .nams$="";
     .layer=0;
     .price=0;
     .objid=0;
diff --git a/npc/009-7/doorbell.txt b/npc/009-7/doorbell.txt
index 6060d49bd..330375bf7 100644
--- a/npc/009-7/doorbell.txt
+++ b/npc/009-7/doorbell.txt
@@ -27,7 +27,7 @@
 009-7,32,34,0	script	Doorbell#RES_0097	NPC_NO_SPRITE,{
     // Name, Layer, Price, ID, x1, y1, x2, y2,
     function create_object {
-        array_push(.name$, getarg(0));
+        array_push(.nams$, getarg(0));
         array_push(.layer, getarg(1));
         array_push(.price, getarg(2));
         array_push(.objid, getarg(3));
@@ -165,7 +165,7 @@ L_ContinuousLoop:
 
     // Create a second array (@valid_ids) with the ID of objects within @re_col group
     for (.@i=0; .@i < getarraysize(.layer); .@i++) {
-        //debugmes "Found object ID %d named %s on layer %s coords (%d,%d) - Looking for layer %d", .@i, .name$[.@i], .layer[.@i], .x1[.@i], .y1[.@i], @re_col;
+        //debugmes "Found object ID %d named %s on layer %s coords (%d,%d) - Looking for layer %d", .@i, .nams$[.@i], .layer[.@i], .x1[.@i], .y1[.@i], @re_col;
         if (.layer[.@i] == @re_col)
             array_push(@valid_ids, .@i);
     }
@@ -176,9 +176,9 @@ L_ContinuousLoop:
     for (.@j=0; .@j < getarraysize(@valid_ids); .@j++) {
         .@i=@valid_ids[.@j];
         if (realestate_hasmobilia(.id, .layer[.@i], .objid[.@i]))
-            @menuentries$+=l("Sell ")+.name$[.@i]+l(" for ") + format_number( realestate_sellprice(.id,.price[.@i]) ) +":";
+            @menuentries$+=l("Sell ")+.nams$[.@i]+l(" for ") + format_number( realestate_sellprice(.id,.price[.@i]) ) +":";
         else
-            @menuentries$+=l("Purchase ")+.name$[.@i]+(" for ") + format_number( .price[.@i] )+":";
+            @menuentries$+=l("Purchase ")+.nams$[.@i]+(" for ") + format_number( .price[.@i] )+":";
     }
     select (@menuentries$);
     mes "";
@@ -232,7 +232,7 @@ OnInit:
 
     // Arrays
     // We go element by element on the array building the menu
-    .name$="";
+    .nams$="";
     .layer=0;
     .price=0;
     .objid=0;
diff --git a/npc/012-8/doorbell.txt b/npc/012-8/doorbell.txt
index f910556e7..9c0ac250e 100644
--- a/npc/012-8/doorbell.txt
+++ b/npc/012-8/doorbell.txt
@@ -27,7 +27,7 @@
 012-8,32,34,0	script	Doorbell#RES_0128	NPC_NO_SPRITE,{
     // Name, Layer, Price, ID, x1, y1, x2, y2,
     function create_object {
-        array_push(.name$, getarg(0));
+        array_push(.nams$, getarg(0));
         array_push(.layer, getarg(1));
         array_push(.price, getarg(2));
         array_push(.objid, getarg(3));
@@ -165,7 +165,7 @@ L_ContinuousLoop:
 
     // Create a second array (@valid_ids) with the ID of objects within @re_col group
     for (.@i=0; .@i < getarraysize(.layer); .@i++) {
-        //debugmes "Found object ID %d named %s on layer %s coords (%d,%d) - Looking for layer %d", .@i, .name$[.@i], .layer[.@i], .x1[.@i], .y1[.@i], @re_col;
+        //debugmes "Found object ID %d named %s on layer %s coords (%d,%d) - Looking for layer %d", .@i, .nams$[.@i], .layer[.@i], .x1[.@i], .y1[.@i], @re_col;
         if (.layer[.@i] == @re_col)
             array_push(@valid_ids, .@i);
     }
@@ -176,9 +176,9 @@ L_ContinuousLoop:
     for (.@j=0; .@j < getarraysize(@valid_ids); .@j++) {
         .@i=@valid_ids[.@j];
         if (realestate_hasmobilia(.id, .layer[.@i], .objid[.@i]))
-            @menuentries$+=l("Sell ")+.name$[.@i]+l(" for ") + format_number( realestate_sellprice(.id,.price[.@i]) ) +":";
+            @menuentries$+=l("Sell ")+.nams$[.@i]+l(" for ") + format_number( realestate_sellprice(.id,.price[.@i]) ) +":";
         else
-            @menuentries$+=l("Purchase ")+.name$[.@i]+(" for ") + format_number( .price[.@i] )+":";
+            @menuentries$+=l("Purchase ")+.nams$[.@i]+(" for ") + format_number( .price[.@i] )+":";
     }
     select (@menuentries$);
     mes "";
@@ -232,7 +232,7 @@ OnInit:
 
     // Arrays
     // We go element by element on the array building the menu
-    .name$="";
+    .nams$="";
     .layer=0;
     .price=0;
     .objid=0;
diff --git a/npc/017-1/guild.txt b/npc/017-1/guild.txt
index f674f3037..f187adaac 100644
--- a/npc/017-1/guild.txt
+++ b/npc/017-1/guild.txt
@@ -20,15 +20,15 @@ OnTouch:
     @MAP_NAME$="guild@"+str(.@ID); // Max 4 chars for map name
 
     .@INSTID = instance_create("guilds@a"+(.@ID), getcharid(2), IOT_GUILD);
-    .@instanceMapName$ = instance_attachmap("guilds", .@INSTID, 0, @MAP_NAME$);
 
-    // Instance already exists, or something went wrong
-    // Let's assume it exists
-    if (.@instanceMapName$ == "") {
+    // Instance already exists - .@INSTID returns "-4"
+    if (.@INSTID == -4) {
         warp @MAP_NAME$, any(34,35), 48;
         end;
     }
 
+    .@instanceMapName$ = instance_attachmap("guilds", .@INSTID, 0, @MAP_NAME$);
+
     // It'll be self-destroyed eventually...
     instance_set_timeout(1000000, 1000000, .@INSTID);
     instance_init(.@INSTID);
diff --git a/npc/017-7/doorbell.txt b/npc/017-7/doorbell.txt
index c343ef1dc..f04a8cd2e 100644
--- a/npc/017-7/doorbell.txt
+++ b/npc/017-7/doorbell.txt
@@ -27,7 +27,7 @@
 017-7,32,34,0	script	Doorbell#RES_0177	NPC_NO_SPRITE,{
     // Name, Layer, Price, ID, x1, y1, x2, y2,
     function create_object {
-        array_push(.name$, getarg(0));
+        array_push(.nams$, getarg(0));
         array_push(.layer, getarg(1));
         array_push(.price, getarg(2));
         array_push(.objid, getarg(3));
@@ -165,7 +165,7 @@ L_ContinuousLoop:
 
     // Create a second array (@valid_ids) with the ID of objects within @re_col group
     for (.@i=0; .@i < getarraysize(.layer); .@i++) {
-        //debugmes "Found object ID %d named %s on layer %s coords (%d,%d) - Looking for layer %d", .@i, .name$[.@i], .layer[.@i], .x1[.@i], .y1[.@i], @re_col;
+        //debugmes "Found object ID %d named %s on layer %s coords (%d,%d) - Looking for layer %d", .@i, .nams$[.@i], .layer[.@i], .x1[.@i], .y1[.@i], @re_col;
         if (.layer[.@i] == @re_col)
             array_push(@valid_ids, .@i);
     }
@@ -176,9 +176,9 @@ L_ContinuousLoop:
     for (.@j=0; .@j < getarraysize(@valid_ids); .@j++) {
         .@i=@valid_ids[.@j];
         if (realestate_hasmobilia(.id, .layer[.@i], .objid[.@i]))
-            @menuentries$+=l("Sell ")+.name$[.@i]+l(" for ") + format_number( realestate_sellprice(.id,.price[.@i]) ) +":";
+            @menuentries$+=l("Sell ")+.nams$[.@i]+l(" for ") + format_number( realestate_sellprice(.id,.price[.@i]) ) +":";
         else
-            @menuentries$+=l("Purchase ")+.name$[.@i]+(" for ") + format_number( .price[.@i] )+":";
+            @menuentries$+=l("Purchase ")+.nams$[.@i]+(" for ") + format_number( .price[.@i] )+":";
     }
     select (@menuentries$);
     mes "";
@@ -232,7 +232,7 @@ OnInit:
 
     // Arrays
     // We go element by element on the array building the menu
-    .name$="";
+    .nams$="";
     .layer=0;
     .price=0;
     .objid=0;
diff --git a/npc/017-8/doorbell.txt b/npc/017-8/doorbell.txt
index 9b1095b38..b7b8f7dd4 100644
--- a/npc/017-8/doorbell.txt
+++ b/npc/017-8/doorbell.txt
@@ -27,7 +27,7 @@
 017-8,32,34,0	script	Doorbell#RES_0178	NPC_NO_SPRITE,{
     // Name, Layer, Price, ID, x1, y1, x2, y2,
     function create_object {
-        array_push(.name$, getarg(0));
+        array_push(.nams$, getarg(0));
         array_push(.layer, getarg(1));
         array_push(.price, getarg(2));
         array_push(.objid, getarg(3));
@@ -165,7 +165,7 @@ L_ContinuousLoop:
 
     // Create a second array (@valid_ids) with the ID of objects within @re_col group
     for (.@i=0; .@i < getarraysize(.layer); .@i++) {
-        //debugmes "Found object ID %d named %s on layer %s coords (%d,%d) - Looking for layer %d", .@i, .name$[.@i], .layer[.@i], .x1[.@i], .y1[.@i], @re_col;
+        //debugmes "Found object ID %d named %s on layer %s coords (%d,%d) - Looking for layer %d", .@i, .nams$[.@i], .layer[.@i], .x1[.@i], .y1[.@i], @re_col;
         if (.layer[.@i] == @re_col)
             array_push(@valid_ids, .@i);
     }
@@ -176,9 +176,9 @@ L_ContinuousLoop:
     for (.@j=0; .@j < getarraysize(@valid_ids); .@j++) {
         .@i=@valid_ids[.@j];
         if (realestate_hasmobilia(.id, .layer[.@i], .objid[.@i]))
-            @menuentries$+=l("Sell ")+.name$[.@i]+l(" for ") + format_number( realestate_sellprice(.id,.price[.@i]) ) +":";
+            @menuentries$+=l("Sell ")+.nams$[.@i]+l(" for ") + format_number( realestate_sellprice(.id,.price[.@i]) ) +":";
         else
-            @menuentries$+=l("Purchase ")+.name$[.@i]+(" for ") + format_number( .price[.@i] )+":";
+            @menuentries$+=l("Purchase ")+.nams$[.@i]+(" for ") + format_number( .price[.@i] )+":";
     }
     select (@menuentries$);
     mes "";
@@ -232,7 +232,7 @@ OnInit:
 
     // Arrays
     // We go element by element on the array building the menu
-    .name$="";
+    .nams$="";
     .layer=0;
     .price=0;
     .objid=0;
diff --git a/npc/024-13/_import.txt b/npc/024-13/_import.txt
index f2e92fbdc..b2645428e 100644
--- a/npc/024-13/_import.txt
+++ b/npc/024-13/_import.txt
@@ -1,3 +1,4 @@
 // Map 024-13: Frostia Indoors
 // This file is generated automatically. All manually added changes will be removed when running the Converter.
 "npc/024-13/_warps.txt",
+"npc/024-13/manager.txt",
diff --git a/npc/024-13/manager.txt b/npc/024-13/manager.txt
new file mode 100644
index 000000000..7c9df55e3
--- /dev/null
+++ b/npc/024-13/manager.txt
@@ -0,0 +1,123 @@
+// TMW2: Moubootaur Legends scripts.
+// Author:
+//    Jesusalva
+// Description:
+//    Real Estate System
+
+// ESTATE_ID → Instance ID of the Estate (required for NPCs, expire)
+// ESTATE_RENTTIME → When the rent will expire
+// ESTATE_MOBILIA_2 → Bitmask of mobilia currently purchased on Monster Collision (6) (Use on walls only)
+// ESTATE_MOBILIA_4 → Bitmask of mobilia currently purchased on Air Collision (2)
+// ESTATE_MOBILIA_8 → Bitmask of mobilia currently purchased on Water Collision (3)
+// ESTATE_MOBILIA_32 → Bitmask of mobilia currently purchased on Yellow Collision (4)
+// ESTATE_MOBILIA_64 → Bitmask of mobilia currently purchased on Normal Collision (1)
+// ESTATE_MOBILIA_128 → Bitmask of mobilia currently purchased on Player Collision (5)
+
+// REAL_ESTATE_CREDITS → Credits equivalent to GP the player have. Will be used first.
+
+// The sign is the main controller
+024-13,31,32,0	script	Apartment Manager	NPC_ELF,{
+    if (ESTATE_RENTTIME < gettimetick(2))
+        goto L_RentAvailable;
+
+    mesn;
+    mesq l("Your rent is valid for @@.", FuzzyTime(ESTATE_RENTTIME));
+    mesc l("Apartment rents cannot be renewed until they expire. Furniture won't be lost.");
+    close;
+
+L_RentAvailable:
+    do
+    {
+        mesc l("This Real Estate is available for rent for only @@ GP!", format_number(.price));
+        .@gp=REAL_ESTATE_CREDITS+Zeny;
+        mesc l("You currently have: @@ GP and mobiliary credits", format_number(.@gp));
+        next;
+        select
+            rif(.@gp > .price, l("Rent it! Make it mine!")),
+            l("Information"),
+            l("Don't rent it");
+
+        // You want to rent
+        if (@menu == 1) {
+            realestate_payment(.price);
+
+            // Payment done, you can now acquire the house for a month
+            ESTATE_RENTTIME=gettimetick(2)+.time;
+
+            mesc l("Rent successful for 30 days!");
+        } else if (@menu == 2) {
+            mesc l("You can rent this house to make it yours.") + " " + l("The rent lasts 30 days.");
+            mesc l("Then you'll be able to buy furniture and utility.");
+            mesc l("This is an apartment. You cannot renew until it expire, and cannot invite guests.");
+            next;
+            mesc l("Both rent and furniture are bought using money, however, there are mobiliary credits.");
+            mesc l("Mobiliary Credits is a special currency which can only be used on real estate.");
+            mesc l("It's obtained with ADMINS or by selling furniture. It is sumed to money and used first.");
+            next;
+        }
+    } while (@menu == 2);
+    close;
+
+OnInit:
+    .sex = G_OTHER;
+    .distance = 3;
+
+    .@npcId = getnpcid(.name$);
+    setunitdata(.@npcId, UDT_HEADTOP, BowlerHat);
+    setunitdata(.@npcId, UDT_HEADMIDDLE, CreasedShirt);
+    setunitdata(.@npcId, UDT_HEADBOTTOM, NPCEyes);
+    setunitdata(.@npcId, UDT_WEAPON, LeatherTrousers);
+
+    .price=10000; // Monthly rent price.
+    .time=2592000; // Defaults to 30 days
+    end;
+
+}
+
+// Door entrance
+024-13,29,28,0	script	#RES_PPL	NPC_HIDDEN,0,0,{
+    end;
+OnTouch:
+    if (ESTATE_RENTTIME < gettimetick(2))
+        goto L_RentAvailable;
+
+    // Warp you to your apartment if exists in memory already.
+    // Build the instance otherwise.
+
+    // Well, "checking if instance exist by mapname" is an illusion.
+    // So we try to build and if we fail, we warp the player to the instance.
+    .@ID=getcharid(0);
+    @MAP_NAME$="ples@"+str(.@ID); // Max 4 chars for map name
+
+    .@INSTID = instance_create("ples@a"+(.@ID), getcharid(3), IOT_CHAR);
+
+    // Instance already exists - .@INSTID returns "-4"
+    if (.@INSTID == -4) {
+        warp @MAP_NAME$, 33, 33;
+        end;
+    }
+
+    // Attach the map
+    .@instanceMapName$ = instance_attachmap("024-14", .@INSTID, 0, @MAP_NAME$);
+
+    // Record important stuff & load furniture
+    ESTATE_ID=.@INSTID;
+    addtimer(20, "Doorbell#RES_PPL::OnReload");
+    addtimer(70, "NPCs#RES_PPL::OnReload");
+
+    // It'll be self-destroyed eventually...
+    instance_set_timeout(1000000, 1000000, .@INSTID);
+    instance_init(.@INSTID);
+    warp @MAP_NAME$, 33, 33;
+    end;
+
+L_RentAvailable:
+    dispbottom l("You do not have booked an apartment here.");
+    close;
+
+OnInit:
+    .distance=1;
+    end;
+
+}
+
diff --git a/npc/024-14/_import.txt b/npc/024-14/_import.txt
index 82d3bfc16..2c45a9c1f 100644
--- a/npc/024-14/_import.txt
+++ b/npc/024-14/_import.txt
@@ -1,3 +1,5 @@
 // Map 024-14: Real Estate
 // This file is generated automatically. All manually added changes will be removed when running the Converter.
 "npc/024-14/_warps.txt",
+"npc/024-14/doorbell.txt",
+"npc/024-14/utils.txt",
diff --git a/npc/024-14/doorbell.txt b/npc/024-14/doorbell.txt
new file mode 100644
index 000000000..f60d9cea7
--- /dev/null
+++ b/npc/024-14/doorbell.txt
@@ -0,0 +1,323 @@
+// TMW2: Moubootaur Legends scripts.
+// Author:
+//    Jesusalva
+// Description:
+//    Real Estate System
+//    Doorbell allows you to purchase mobilia, besides loading it when server starts
+//    Each layer can have 32 different furniture pieces because bitmask limit.
+//    This file is custom to every room
+
+// ESTATE_ID → Instance ID of the Estate (required for NPCs, expire)
+// ESTATE_RENTTIME → When the rent will expire
+// ESTATE_MOBILIA_2 → Bitmask of mobilia currently purchased on Monster Collision (6) (Use on walls only)
+// ESTATE_MOBILIA_4 → Bitmask of mobilia currently purchased on Air Collision (2)
+// ESTATE_MOBILIA_8 → Bitmask of mobilia currently purchased on Water Collision (3)
+// ESTATE_MOBILIA_32 → Bitmask of mobilia currently purchased on Yellow Collision (4)
+// ESTATE_MOBILIA_64 → Bitmask of mobilia currently purchased on Normal Collision (1)
+// ESTATE_MOBILIA_128 → Bitmask of mobilia currently purchased on Player Collision (5)
+
+// REAL_ESTATE_CREDITS → Credits equivalent to GP the player have. Will be used first.
+
+// The sign is the main controller
+024-14,32,34,0	script	Doorbell#RES_PPL	NPC_NO_SPRITE,{
+    // Name, Layer, Price, ID, x1, y1, x2, y2,
+    function create_object {
+        array_push(.nams$, getarg(0));
+        array_push(.layer, getarg(1));
+        array_push(.price, getarg(2));
+        array_push(.objid, getarg(3));
+        array_push(.x1, getarg(4));
+        array_push(.y1, getarg(5));
+        array_push(.x2, getarg(6));
+        array_push(.y2, getarg(7));
+        return;
+    }
+    function re2_sellprice;
+    function re2_togglemobilia;
+    function re2_hasmobilia;
+
+    .id=getcharid(0);
+    goto L_Manage;
+// Managment Menu
+L_Manage:
+    mesc l("@@'s Apartment", strcharinfo(0));
+    mesc ".:: "+ l("Managment Menu") + " ::.";
+
+    .@gp=REAL_ESTATE_CREDITS+Zeny;
+    mesc l("Rent time available: @@", FuzzyTime(ESTATE_RENTTIME));
+    mesc l("Total Credits and GP: @@", format_number(.@gp));
+    mes "";
+
+    next;
+    select
+        l("Leave"),
+        l("Manage Furniture");
+
+    switch (@menu) {
+        case 1:
+            close;
+            break;
+        case 2:
+            goto L_Furniture;
+            break;
+    }
+    goto L_Manage;
+
+L_Furniture:
+    mesc l("@@'s Estate", strcharinfo(0));
+    mesc ".:: "+ l("Furniture Menu") + " ::.";
+
+    .@gp=REAL_ESTATE_CREDITS+Zeny;
+    mesc l("Total Credits and GP: @@", format_number(.@gp));
+
+    next;
+    select
+        l("Finish"),
+        l("Manage Beds"),
+        l("Manage Utilities"),
+        l("Manage Luxury furniture"),
+        l("Manage Decoration"),
+        l("Manage Chairs"),
+        l("Manage Paintings");
+    mes "";
+
+    switch (@menu) {
+        case 1:
+            goto L_Manage;
+            break;
+        case 2:
+            mesc ".:: "+ l("Beds") + " ::.", 3;
+            @re_col=RES_OBJECTS;
+            break;
+        case 3:
+            mesc ".:: "+ l("Utilities") + " ::.", 3;
+            @re_col=RES_UTILITIES;
+            break;
+        case 4:
+            mesc ".:: "+ l("Luxury furniture") + " ::.", 3;
+            @re_col=RES_LUXURY;
+            break;
+        case 5:
+            mesc ".:: "+ l("Decoration") + " ::.", 3;
+            @re_col=RES_DECORATION;
+            break;
+        case 6:
+            mesc ".:: "+ l("Chairs") + " ::.", 3;
+            @re_col=RES_SITTABLE;
+            break;
+        case 7:
+            mesc ".:: "+ l("Paintings") + " ::.", 3;
+            @re_col=RES_WALLDECORATION;
+            break;
+    }
+
+// L_ContinuousLoop
+// Requires the following variables:
+//  @re_col
+//      Target Collision ID
+L_ContinuousLoop:
+    deletearray @valid_ids;
+
+    // Create a second array (@valid_ids) with the ID of objects within @re_col group
+    for (.@i=0; .@i < getarraysize(.layer); .@i++) {
+        //debugmes "Found object ID %d named %s on layer %s coords (%d,%d) - Looking for layer %d", .@i, .nams$[.@i], .layer[.@i], .x1[.@i], .y1[.@i], @re_col;
+        if (.layer[.@i] == @re_col)
+            array_push(@valid_ids, .@i);
+    }
+    //debugmes "Found %d valid objects", getarraysize(@valid_ids);
+
+    // Create the menu with @valid_ids - Check if you already have the item to decide if you're buying or selling
+    @menuentries$="Finish:";
+    for (.@j=0; .@j < getarraysize(@valid_ids); .@j++) {
+        .@i=@valid_ids[.@j];
+        if (re2_hasmobilia(.id, .layer[.@i], .objid[.@i]))
+            @menuentries$+=l("Sell ")+.nams$[.@i]+l(" for ") + format_number( re2_sellprice(.id,.price[.@i]) ) +":";
+        else
+            @menuentries$+=l("Purchase ")+.nams$[.@i]+(" for ") + format_number( .price[.@i] )+":";
+    }
+    select (@menuentries$);
+    mes "";
+
+    // First option to return to previous menu
+    if (@menu == 1)
+        goto L_Furniture;
+
+    // Otherwise, we know then that (@menu-2) is the ID in @valid_ids
+    // So we save .@id with the correct ID in object arrays.
+    // We also calculate how much aggregated money you have.
+    .@id=@valid_ids[@menu-2];
+    .@gp=REAL_ESTATE_CREDITS+Zeny;
+
+    if (re2_hasmobilia(.id, .layer[.@id], .objid[.@id])) {
+        // If you have the mobilia, you're selling it for Mobiliary Credits
+        delcells realestate_cellname(.id, .@id);
+        re2_togglemobilia(.id, .layer[.@id], .objid[.@id]);
+        addtimer2(150, "NPCs#RES_PPL::OnReload");
+        REAL_ESTATE_CREDITS+=re2_sellprice(.id,.price[.@i]);
+        mesc l("Sale successful!");
+        next;
+    } else {
+        // Else, you're buying it, so we must check if you have the moolah first
+        .@price=.price[.@id];
+        if (.@gp > .@price) {
+            realestate_payment(.@price);
+            setcells "ples@"+getcharid(0), .x1[.@id], .y1[.@id], .x2[.@id], .y2[.@id], .layer[.@id], realestate_cellname(.id, .@id);
+            areatimer("ples@"+getcharid(0), .x1[.@id], .y1[.@id], .x2[.@id], .y2[.@id], 10, "::OnSlide");
+            re2_togglemobilia(.id, .layer[.@id], .objid[.@id]);
+            addtimer2(150, "NPCs#RES_PPL::OnReload");
+            mesc l("Purchase successful!");
+            next;
+        } else {
+            mesc l("Not enough funds!");
+            next;
+        }
+    }
+
+    // This loops forever
+    goto L_ContinuousLoop;
+
+
+// When using setcells() a player could get trapped!
+// This label will slide the player back to entrance, which should be a safe spot
+OnSlide:
+    slide 33, 33;
+    end;
+
+OnInit:
+    .sex = G_OTHER;
+    .distance = 3;
+
+    // Arrays
+    // We go element by element on the array building the menu
+    .nams$="";
+    .layer=0;
+    .price=0;
+    .objid=0;
+    .x1=0;
+    .y1=0;
+    .x2=0;
+    .y2=0;
+
+    // Furniture Settings
+    // Name, Collision Layer, Price, ID, x1, y1, x2, y2
+    // For Collision Layer, see constants.conf ("Real Estate Collisions")
+    create_object("Placeholder" ,99,999999,99999, 99, 99, 99, 99);
+
+    create_object("Red Bed"     , 5,  5000,    1, 25, 29, 26, 32);
+    create_object("Blue Bed"    , 5,  5000,    2, 27, 29, 28, 32);
+
+    create_object("Wardrobe"    , 1,  7000,    1, 25, 26, 26, 26);
+    create_object("Cauldron"    , 1,  5000,    2, 28, 27, 29, 27);
+    create_object("Empty Shelf" , 1,  2000,    4, 34, 26, 34, 26);
+    create_object("Bookshelf"   , 1,  2000,    8, 35, 26, 35, 26);
+    create_object("Bottle Shelf", 1,  2000,   16, 36, 26, 36, 26);
+    create_object("Beer Shelf"  , 1,  2000,   32, 37, 26, 37, 26);
+
+    create_object("Piano"       , 3, 10000,    1, 31, 26, 33, 26);
+
+    create_object("Right Desk"  , 2,  5000,    2, 36, 30, 38, 32);
+
+    create_object("Right Chair" , 4,  2000,    2, 37, 29, 37, 29);
+
+    create_object("Painting 01" , 6,  3000,    1, 27, 23, 27, 23);
+    create_object("Painting 02" , 6,  3000,    2, 29, 24, 29, 24);
+    create_object("Painting 03" , 6,  3000,    4, 32, 23, 32, 23);
+    create_object("Painting 04" , 6,  3000,    8, 35, 23, 35, 23);
+    end;
+
+OnReload:
+    // Load Mobilia already existing
+    debugmes "[REAL ESTATE] Now loading mobilia";
+    for (.@i=0; .@i < getarraysize(.layer); .@i++) {
+        switch (.layer[.@i]) {
+            case 1:
+                if (ESTATE_MOBILIA_128 & .objid[.@i])
+                    array_push(.valid_ids, .@i);
+                break;
+            case 2:
+                if (ESTATE_MOBILIA_4 & .objid[.@i])
+                    array_push(.valid_ids, .@i);
+                break;
+            case 3:
+                if (ESTATE_MOBILIA_8 & .objid[.@i])
+                    array_push(.valid_ids, .@i);
+                break;
+            case 4:
+                if (ESTATE_MOBILIA_32 & .objid[.@i])
+                    array_push(.valid_ids, .@i);
+                break;
+            case 5:
+                if (ESTATE_MOBILIA_64 & .objid[.@i])
+                    array_push(.valid_ids, .@i);
+                break;
+            case 6:
+                if (ESTATE_MOBILIA_2 & .objid[.@i])
+                    array_push(.valid_ids, .@i);
+                break;
+            default:
+                break;
+        }
+    }
+    debugmes "Found %d valid objects", getarraysize(.valid_ids);
+    for (.@j=0; .@j < getarraysize(.valid_ids); .@j++) {
+        .@id=.valid_ids[.@j];
+        setcells "ples@"+getcharid(0), .x1[.@id], .y1[.@id], .x2[.@id], .y2[.@id], .layer[.@id], realestate_cellname(.id, .@id);
+    }
+    deletearray .valid_ids;
+    end;
+
+
+    // Additional crap needed because instance system
+    // Previously declared functions here. Copy paste from functions/, but without $.
+    function re2_sellprice
+    {
+        .@timeleft=ESTATE_RENTTIME-gettimetick(2); // Number of seconds
+        .@daysleft=.@timeleft/86400; // Number of days left of rent
+        .@weeksleft=.@timeleft/604800; // Number of weeks left of rent
+        return (getarg(1)/max(1, 6-.@weeksleft)) - max(0, 45-.@daysleft);
+    }
+    function re2_togglemobilia
+    {
+        switch (getarg(1)) {
+            case 1:
+                ESTATE_MOBILIA_64 = ESTATE_MOBILIA_64 ^ getarg(2); break;
+            case 2:
+                ESTATE_MOBILIA_4 = ESTATE_MOBILIA_4 ^ getarg(2); break;
+            case 3:
+                ESTATE_MOBILIA_8 = ESTATE_MOBILIA_8 ^ getarg(2); break;
+            case 4:
+                ESTATE_MOBILIA_32 = ESTATE_MOBILIA_32 ^ getarg(2); break;
+            case 5:
+                ESTATE_MOBILIA_128 = ESTATE_MOBILIA_128 ^ getarg(2); break;
+            case 6:
+                ESTATE_MOBILIA_2 = ESTATE_MOBILIA_2 ^ getarg(2); break;
+            default:
+                debugmes("[ERROR] [CRITICAL] [REAL ESTATE]: Object %d have Invalid Collision Type: %d (must range 1~6)", getarg(2), getarg(1)); break;
+        }
+        if (getarg(3, "error") != "error") { addtimer2(150, "NPCs#RES_PPL::OnReload"); }
+        return;
+    }
+    function re2_hasmobilia
+    {
+        switch (getarg(1)) {
+            case 1:
+                return ESTATE_MOBILIA_64 & getarg(2);
+            case 2:
+                return ESTATE_MOBILIA_4 & getarg(2);
+            case 3:
+                return ESTATE_MOBILIA_8 & getarg(2);
+            case 4:
+                return ESTATE_MOBILIA_32 & getarg(2);
+            case 5:
+                return ESTATE_MOBILIA_128 & getarg(2);
+            case 6:
+                return ESTATE_MOBILIA_2 & getarg(2);
+            default:
+                debugmes("[ERROR] [CRITICAL] [REAL ESTATE]: Object %d have Invalid Collision Type: %d (must range 1~6)", getarg(2), getarg(1)); return false;
+        }
+        return false;
+    }
+
+}
+
+
diff --git a/npc/024-14/utils.txt b/npc/024-14/utils.txt
new file mode 100644
index 000000000..e9c8853bb
--- /dev/null
+++ b/npc/024-14/utils.txt
@@ -0,0 +1,147 @@
+// TMW2: Moubootaur Legends scripts.
+// Author:
+//    Jesusalva
+// Description:
+//    Real Estate System
+//    Utils take care of NPCs - Their code, and enable/disable using check_cell
+//    This file is custom to every room
+
+// ESTATE_ID → Instance ID of the Estate (required for NPCs, expire)
+// ESTATE_RENTTIME → When the rent will expire
+// ESTATE_MOBILIA_2 → Bitmask of mobilia currently purchased on Monster Collision (6) (Use on walls only)
+// ESTATE_MOBILIA_4 → Bitmask of mobilia currently purchased on Air Collision (2)
+// ESTATE_MOBILIA_8 → Bitmask of mobilia currently purchased on Water Collision (3)
+// ESTATE_MOBILIA_32 → Bitmask of mobilia currently purchased on Yellow Collision (4)
+// ESTATE_MOBILIA_64 → Bitmask of mobilia currently purchased on Player Collision (5)
+// ESTATE_MOBILIA_128 → Bitmask of mobilia currently purchased on Normal Collision (1)
+
+// REAL_ESTATE_CREDITS → Credits equivalent to GP the player have. Will be used first.
+
+// The sign is the main controller for rent system
+// Doorbell is the main controller for indoor
+// This is the NPC script controller
+024-14,0,0,0	script	NPCs#RES_PPL	NPC_HIDDEN,{
+    // load_npc ( name , map, x , y{, cell} )
+    function load_npc {
+        if (checknpccell(getarg(1), getarg(2), getarg(3), getarg(4, cell_chknopass))) {
+            enablenpc instance_npcname(getarg(0), ESTATE_ID);
+        } else {
+            disablenpc instance_npcname(getarg(0), ESTATE_ID);
+        }
+        return;
+    }
+    end;
+
+OnInit:
+    // Estate Settings
+    .mapa$="024-14"; // Map name
+
+    // NPC Settings
+    .sex = G_OTHER;
+    .distance = 3;
+    end;
+
+// Load or unload accordingly
+OnReload:
+    //debugmes "[REAL ESTATE] NPC ONRELOAD";
+    // load_npc ( name , map, x , y{, cell} )
+    load_npc("Wardrobe#RES_PPL", .mapa$, 25, 26);
+    load_npc("Cauldron#RES_PPL", .mapa$, 28, 27);
+    load_npc("Piano#RES_PPL"   , .mapa$, 32, 26);
+    end;
+
+}
+
+024-14,25,26,0	script	Wardrobe#RES_PPL	NPC_NO_SPRITE,{
+    openstorage;
+    end;
+
+OnInit:
+    .distance=3;
+    end;
+}
+
+
+024-14,28,27,0	script	Cauldron#RES_PPL	NPC_NO_SPRITE,{
+    mesc l("What will you brew today?");
+    if (AlchemySystem(CRAFT_PLAYER))
+        mesc l("Success!"), 3;
+    else
+        mesc l("That didn't work!"), 1;
+    close;
+
+OnInit:
+    .distance=3;
+    end;
+}
+
+
+024-14,32,26,0	script	Piano#RES_PPL	NPC_NO_SPRITE,{
+    mesc l("Do you want to play a song?");
+    mesc l("This is not saved.");
+    select
+        l("Nothing"),
+        l("Default"),
+        l("Indoors 1 (Peace)"),
+        l("Indoors 2 (Dimonds)"),
+        l("TMW Adventure"),
+        l("Sailing Away!"),
+        l("Magick Real"),
+        l("The Forest"),
+        l("Dragons and Toast"),
+        l("Unforgiving Lands"),
+        l("Arabesque (Action)"),
+        l("No Chains (Tulimshar)"),
+        l("School of Quirks (Candor)"),
+        l("Cake Town (Hurnscald)"),
+        l("Steam (LoF Village)"),
+        l("Woodland Fantasy"),
+        l("Birds in the Sunrise");
+
+    mes "";
+    .@m$="";
+    switch (@menu) {
+        case 1:
+            close;
+        case 2:
+            .@m$="8bit_the_hero.ogg"; break;
+        case 3:
+            .@m$="peace.ogg"; break;
+        case 4:
+            .@m$="peace2.ogg"; break;
+        case 5:
+            .@m$="tmw_adventure.ogg"; break;
+        case 6:
+            .@m$="sail_away.ogg"; break;
+        case 7:
+            .@m$="magick_real.ogg"; break;
+        case 8:
+            .@m$="dariunas_forest.ogg"; break;
+        case 9:
+            .@m$="dragon_and_toast.ogg"; break;
+        case 10:
+            .@m$="Unforgiving_Lands.ogg"; break;
+        case 11:
+            .@m$="Arabesque.ogg"; break;
+        case 12:
+            .@m$="mvrasseli_nochains.ogg"; break;
+        case 13:
+            .@m$="school_of_quirks.ogg"; break;
+        case 14:
+            .@m$="caketown.ogg"; break;
+        case 15:
+            .@m$="steam.ogg"; break;
+        case 16:
+            .@m$="woodland_fantasy.ogg"; break;
+        case 17:
+            .@m$="tws_birds_in_the_sunrise.ogg"; break;
+
+    }
+    changemusic "ples@"+getcharid(0), .@m$;
+    close;
+
+OnInit:
+    .distance=3;
+    end;
+}
+
diff --git a/npc/functions/timer.txt b/npc/functions/timer.txt
index 17a7d4b68..5d7466d0f 100644
--- a/npc/functions/timer.txt
+++ b/npc/functions/timer.txt
@@ -29,6 +29,13 @@ function	script	areatimer2	{
     return .@i;
 }
 
+// addtimer2(<tick>, "<npc>::<event>")
+function	script	addtimer2	{
+    deltimer(getarg(1));
+    addtimer(getarg(0), getarg(1));
+    return .@i;
+}
+
 
 // maptimer("<map>", <tick>, "<npc>::<event>")
 function	script	maptimer	{
-- 
cgit v1.2.3-70-g09d2