summaryrefslogtreecommitdiff
path: root/npc/items
diff options
context:
space:
mode:
authorJesusaves <cpntb1@ymail.com>2022-10-23 21:44:22 -0300
committerJesusaves <cpntb1@ymail.com>2022-10-23 21:44:22 -0300
commita7c45a192268da2601cef47a4cdba987ae2327ca (patch)
treec5fb5b97db109fe7106496dd96498c475881046b /npc/items
downloadserverdata-a7c45a192268da2601cef47a4cdba987ae2327ca.tar.gz
serverdata-a7c45a192268da2601cef47a4cdba987ae2327ca.tar.bz2
serverdata-a7c45a192268da2601cef47a4cdba987ae2327ca.tar.xz
serverdata-a7c45a192268da2601cef47a4cdba987ae2327ca.zip
Initial commit (Moubootaur Legends fork)
Diffstat (limited to 'npc/items')
-rw-r--r--npc/items/alcohol.txt124
-rw-r--r--npc/items/arcmage.txt51
-rw-r--r--npc/items/books.txt737
-rw-r--r--npc/items/croconut.txt78
-rw-r--r--npc/items/emptybox.txt54
-rw-r--r--npc/items/grenade.txt122
-rw-r--r--npc/items/inc_sc_bonus.txt72
-rw-r--r--npc/items/legacy_heal.txt79
-rw-r--r--npc/items/lofteleporter.txt123
-rw-r--r--npc/items/maps.txt17
-rw-r--r--npc/items/mercenary.txt150
-rw-r--r--npc/items/miscrecipes.txt93
-rw-r--r--npc/items/rand_mp_heal.txt63
-rw-r--r--npc/items/rand_sc_heal.txt112
-rw-r--r--npc/items/shovel.txt544
-rw-r--r--npc/items/teleporter.txt54
-rw-r--r--npc/items/valentine.txt31
17 files changed, 2504 insertions, 0 deletions
diff --git a/npc/items/alcohol.txt b/npc/items/alcohol.txt
new file mode 100644
index 0000000..374a4be
--- /dev/null
+++ b/npc/items/alcohol.txt
@@ -0,0 +1,124 @@
+// TMW-2 Script.
+// Author:
+// Crush
+// Jesusalva
+// Description:
+// Alcohol effects
+// TODO: Retroactive, weakens every hour...
+//
+// Variables:
+// @taste Alcohol taste (0~100) - influences exp up
+// @Alcohol Alcoholic rating (0~100) - influences Attack Speed Malus, Min. Vit and duration
+// ALC_DELAYTIME For how long you are drunk (the delay) - gettimetick(2)
+// ALC_THRESHOLD How drunk you are (the bonus)
+//
+// When drunk, attack speed is lowered but exp gain is increased.
+// Attack Speed Reductor: SC_ATTHASTE_INFINITY (reset upon death), SC_ATTHASTE_POTION2 (not reset upon death)
+// Max HP Reductor: SC_INCMHPRATE
+// EXP Increaser: SC_CASH_PLUSEXP (not reset upon death), SC_OVERLAPEXPUP (reset upon death)
+
+function script ALCReset {
+ if (ALC_DELAYTIME < gettimetick(2))
+ ALC_THRESHOLD=0;
+ return;
+}
+
+- script alcohol_sc -1,{
+
+ // Stack remaning bonuses if the last one hasn't finished
+ // remaining_bonuses(sc, type)
+ // type 0: delay
+ // type 1: value
+ function remaining_bonus
+ {
+ if (getstatus(getarg(0)))
+ {
+ if (getarg(1))
+ return getstatus(getarg(0), 1);
+ else
+ return getstatus(getarg(0), 5); // Shouldn't it be 5?
+ }
+ return 0;
+ }
+
+OnUse:
+ if (@Alcohol <= 0 || @taste <= 0) {
+ Exception("Invalid alcoholic item, deleting without any effect.");
+ end;
+ }
+ // Just to be sure
+ ALCReset();
+
+ // Do you have enough vitality to hold your beer?
+ // Skip this check on the first drink
+ if (ALC_THRESHOLD) {
+ .@vit=readparam2(bVit);
+ if (@Alcohol+ALC_THRESHOLD > .@vit) {
+ if (GSET_ALCOHOL_NOOVERDRINK) dispbottom l("You vomit, you are too drunk for this to have effect anymore.");
+ else dispbottom l("You vomit, you are too drunk and drinking is harmful.");
+ dispbottom l("Raise vitality to be able to drink even more.");
+ sc_start SC_CONFUSION, 5000, 0, 10000, SCFLAG_NOAVOID; // Warning, forces user to use @resync!
+ if (GSET_ALCOHOL_NOOVERDRINK) end;
+ percentheal -@Alcohol, -@Alcohol;
+ }
+ }
+ .@deltatime=2*60*1000; // How long (in ms) each Alcohol point works? (max. 100 points)
+ // Default value is 2 minutes per alcohol point - you'll be somber after at most four hours.
+ // Beer (7): 14 minutes
+ // Wine (16): 32 minutes
+ // Sake (25): 50 minutes
+ // Rum (40) : 80 minutes (1h20)
+ // Ale (70) : 140 minutes (2h20)
+
+ // Taste is affected by users near you.
+ // Each user raises exp bonus in 1.5%, capped to the beverage taste
+ // If you are with many people, drink a better beverage! ;-)
+ getmapxy(.@m$, .@x, .@y, 0);
+ .@bonus=getareausers(.@m$, .@x-12, .@y-12, .@x+12, .@y+12)-1;
+ .@bonus=.@bonus*15/10;
+ @taste+=min(@taste, .@bonus);
+
+ // Alcohol EXP Bonus - ponderate average, so having more VIT doesn't means
+ // more experience - only more time (be careful when mixing alcohol!)
+ .@v=remaining_bonus(SC_OVERLAPEXPUP, true);
+ .@t=remaining_bonus(SC_OVERLAPEXPUP, false)/1000;
+
+ if (.@t) .@val1 = ponderate_avg(@taste, @Alcohol, .@v, .@t);
+ else .@val1 = @taste;
+
+ // Put the delay in ms.
+ .@delay = remaining_bonus(SC_OVERLAPEXPUP, false);
+ .@delay = .@t + @Alcohol*.@deltatime;
+
+ // Reset EXP Bonus based on the new cumulative delay and average exp bonus
+ sc_end SC_OVERLAPEXPUP;
+ sc_start SC_OVERLAPEXPUP, .@delay, .@val1;
+
+ // Recalculate Alcohol Threshold and time
+ ALC_THRESHOLD+=@Alcohol;
+ if (ALC_DELAYTIME < gettimetick(2)) {
+ ALC_DELAYTIME=gettimetick(2);
+ ALC_THRESHOLD=@Alcohol;
+ }
+ ALC_DELAYTIME+=@Alcohol*.@deltatime;
+
+ // Debug comment if you need to check stuff
+ //debugmes "%d %d | %d %d | f t ", remaining_bonus(SC_OVERLAPEXPUP, false), remaining_bonus(SC_OVERLAPEXPUP, true), remaining_bonus(SC_ATTHASTE_INFINITY, false), remaining_bonus(SC_ATTHASTE_INFINITY, true);
+
+ // For debuff I'll use inc_sc_bonus utilities (exp gain = atk speed loss)
+ .@delay=@Alcohol*(.@deltatime/1000);
+ .@min=-(remaining_bonus(SC_OVERLAPEXPUP, true)*2);
+ .@max=-(remaining_bonus(SC_OVERLAPEXPUP, true)*2);
+ // Sanitization, and nerf the debuff
+ .@min=(.@min/2)+1;
+ .@max=(.@max/2)+2;
+ // DEX and VIT may affect a tiny bit (there's caps)
+ .@pam=readparam2(bVit)+readparam2(bDex);
+ .@min=min(0, .@min*limit(50, 500-.@pam, 500)/500);
+ .@max=min(0, .@min*limit(100, 500-.@pam, 500)/500);
+ SC_Bonus(.@delay, SC_ATTHASTE_INFINITY, .@min, .@max);
+ if (debug || $@GM_OVERRIDE)
+ debugmes "Alcohol penalty: %d ~ %d for %d s", .@min, .@max, .@delay;
+ close;
+}
+
diff --git a/npc/items/arcmage.txt b/npc/items/arcmage.txt
new file mode 100644
index 0000000..038be82
--- /dev/null
+++ b/npc/items/arcmage.txt
@@ -0,0 +1,51 @@
+// TMW-2 script.
+// Author:
+// Jesusalva
+// Description:
+// Card boxsets, shout out for arcmage.org
+
+// Create a random card, with rares
+function script MakeRandomArcmageCard {
+ array_push(.@arcmagecards, NatureCard);
+ array_push(.@arcmagecards, NinjaCard);
+ array_push(.@arcmagecards, MageCard);
+ array_push(.@arcmagecards, DruidCard);
+ array_push(.@arcmagecards, ClericCard);
+ array_push(.@arcmagecards, KnightCard);
+ array_push(.@arcmagecards, HeroCard);
+ array_push(.@arcmagecards, NecromancerCard);
+
+ .@r=rand(0,10000);
+ // 5% chances of a rare card
+ // Actually only 1/3 of this rate is effective
+ // Therefore real chances are of about 1.67%
+ if (.@r < 500+(JobLevel*2)) {
+ array_push(.@arcmagecards, SpeedCard);
+ array_push(.@arcmagecards, ReflectCard);
+ array_push(.@arcmagecards, PowerCard);
+ array_push(.@arcmagecards, WallCard);
+ }
+ // You may get the S Card if you are lucky.
+ // However, less than 1/2 of this rate is
+ // effective, and it is not so flexible.
+ // 0.05% at start and +0.1% per rebirth
+ if (.@r < 5+(REBIRTH*10)) {
+ array_push(.@arcmagecards, NatureCardS);
+ array_push(.@arcmagecards, NinjaCardS);
+ array_push(.@arcmagecards, MageCardS);
+ array_push(.@arcmagecards, DruidCardS);
+ array_push(.@arcmagecards, ClericCardS);
+ array_push(.@arcmagecards, KnightCardS);
+ array_push(.@arcmagecards, HeroCardS);
+ array_push(.@arcmagecards, NecromancerCardS);
+ // These cards does not have S version for drop
+ array_push(.@arcmagecards, SpeedCard);
+ array_push(.@arcmagecards, ReflectCard);
+ array_push(.@arcmagecards, PowerCard);
+ array_push(.@arcmagecards, WallCard);
+ }
+
+ getitem any_of(.@arcmagecards), 1;
+ return;
+}
+
diff --git a/npc/items/books.txt b/npc/items/books.txt
new file mode 100644
index 0000000..1c50486
--- /dev/null
+++ b/npc/items/books.txt
@@ -0,0 +1,737 @@
+// TMW-2 script.
+// Author:
+// Jesusalva
+// gumi
+// Tirifto
+// Description:
+// Books used by TMW-2. Some are from evol.
+
+function script FishingBook {
+ narrator S_FIRST_BLANK_LINE | S_LAST_NEXT,
+ l("To get started with fishing, you'll need two things: a fishing rod and a bait."),
+ l("You just need one fishing rod, although you should take more than one single bait.");
+
+ @menu = 0; // reset for the rif
+
+ do
+ {
+ narrator S_NO_NPC_NAME,
+ l("Please select a chapter:");
+
+ mes "";
+
+ select
+ rif2(1, true, l("Ch 1 — Fishing apparatus")),
+ rif2(2, true, l("Ch 2 — Baits")),
+ rif2(3, true, l("Ch 3 — Location")),
+ rif2(4, true, l("Ch 4 — Casting")),
+ rif2(5, true, l("Ch 5 — Reeling")),
+ l("Close");
+ mes "";
+
+ switch(@menu)
+ {
+ case 1:
+ narrator S_LAST_NEXT,
+ l("You'll want your fishing rod to be flexible but solid."),
+ l("Comfortable grip is important especially for newcomers, since they'll be holding it for quite a while.");
+ break;
+ case 2:
+ narrator S_LAST_NEXT,
+ l("You can use many diverse items to lure fishes."),
+ l("Most common and widely popular in the fish realm are @@ and pieces of @@.",
+ getitemlink(SmallTentacles), getitemlink(Bread)),
+ l("Some types of fish also enjoy @@ quite a bit.",
+ getitemlink(Aquada)),
+ l("Some people, however, prefer to fish with more unorthodox baits, such as @@ or @@.",
+ getitemlink(RoastedMaggot), getitemlink(CaveSnakeTongue)),
+ l("Other food can be used as a bait, too.");
+ break;
+ case 3:
+ narrator S_LAST_NEXT,
+ l("Find yourself a nice dry spot on a coast where you can easily reach into deep water."),
+ l("Fishing next to shallow water is not going to work well, because fishes seldom go there."),
+ l("You can easily identify fishing spots, small bubbles and fishes are visible from the surface."),
+ l("Don't forget to come as close as possible to these spots!");
+ break;
+ case 4:
+ narrator S_LAST_NEXT,
+ l("Toss the hook into deep water by clicking on where you want to cast it."),
+ l("Make sure to put on a bait after you click, though!"),
+ l("After that, stay still and be patient, but also alert!");
+ break;
+ case 5:
+ narrator S_LAST_NEXT,
+ l("To successfully catch a fish, you need to pull up your hook by clicking it, right after it submerges."),
+ l("Should you be too quick or wait too long, you will most likely fail.");
+ break;
+ }
+ } while (@menu != 6);
+ return;
+}
+
+- script #Book-Fishing1 NPC_HIDDEN,{
+
+ function read_book {
+ setnpcdialogtitle l(.book_name$);
+ FishingBook();
+ closeclientdialog;
+ end;
+ }
+
+OnShelfUse:
+ if (openbookshelf())
+ read_book;
+ bye;
+
+OnUse:
+ if (openbook())
+ read_book;
+ bye;
+
+OnInit:
+ .book_name$ = getitemname(FishingGuideVolI);
+ .sex = G_OTHER;
+ .distance = 1;
+ end;
+}
+
+
+
+function script PetcaringBook {
+ select
+ l("General Information"),
+ l("Pet Summary");
+ mes "";
+ if (@menu == 1) {
+ narrator 1,
+ l("So you have now a pet, who is loyal to you. It'll follow you everywhere, but there are two things you must know."),
+ l("Do not let intimacy and hunger get to zero. If any of those get to zero, it'll leave you forever."),
+ l("Pets must keep a strict diet. Pious eats Piberries, Bhoppers eat Aquadas, and Maggots eats Bug Legs."),
+ l("White Cats drink Milk, Forest Mushroom eats Moss, Black Cats eats marshmallow. Keep in mind whatever they eat."),
+ l("However, you should only give food when it's hungry, otherwise it'll believe you're a bad owner and intimacy will decrease."),
+ l("Dying will also decrease the pet intimacy, and there are bonuses when your intimacy is high!"),
+ l("To perform most actions, like feeding and renaming, just right-click it. You can even put it back on the egg if its following gets too annoying. When in the egg, they will not feel hunger."),
+ l("Give your pet a nice name, and keep it healthy, and you'll be a successful pet owner!"),
+ l("Some pets will also collect loot for you, right click on it so it drop whatever it is holding for you."),
+ l("...And if you're still trying to check your pet stats, just hover it with your mouse. Thanks."),
+ l("-- Animals Protection Agency of Hurnscald");
+ } else {
+ mes l("%s", getitemlink(PiouEgg));
+ mesc b(l("Acquisition: ")) + l("Login Bonus");
+ mesc b(l("Food: ")) + getitemlink(Piberries);
+ mesc b(l("Bonus: ")) + l("Loot 10, Luck +2, Luck Dance");
+
+ dnext;
+ mes "";
+ mes l("%s", getitemlink(Ratte));
+ mesc b(l("Acquisition: ")) + l("Unobtanium");
+ mesc b(l("Food: ")) + getitemlink(Cheese);
+ mesc b(l("Bonus: ")) + l("Loot 3, Dex +5");
+
+ dnext;
+ mes "";
+ mes l("%s", getitemlink(DuckEgg));
+ mesc b(l("Acquisition: ")) + l("Grand Hunter Quest");
+ mesc b(l("Food: ")) + getitemlink(CherryCake);
+ mesc b(l("Bonus: ")) + l("Loot 3, Steal +15%, Passive HP Regen (1)");
+
+ dnext;
+ mes "";
+ mes l("%s", getitemlink(FluffyEgg));
+ mesc b(l("Acquisition: ")) + l("Grand Hunter Quest");
+ mesc b(l("Food: ")) + getitemlink(LettuceLeaf);
+ mesc b(l("Bonus: ")) + l("Loot 3, Max MP +250");
+
+ dnext;
+ mes "";
+ mes l("%s", getitemlink(MaggotCocoon));
+ mesc b(l("Acquisition: ")) + l("Grand Hunter Quest");
+ mesc b(l("Food: ")) + getitemlink(BugLeg);
+ mesc b(l("Bonus: ")) + l("Loot 3, Max HP +250");
+
+ dnext;
+ mes "";
+ mes l("%s", getitemlink(BatEgg));
+ mesc b(l("Acquisition: ")) + l("Grand Hunter Quest");
+ mesc b(l("Food: ")) + getitemlink(RoastedMaggot);
+ mesc b(l("Bonus: ")) + l("Loot 3, ASPD +5%");
+
+ dnext;
+ mes "";
+ mes l("%s", getitemlink(ForestShroomEgg));
+ mesc b(l("Acquisition: ")) + l("Grand Hunter Quest");
+ mesc b(l("Food: ")) + getitemlink(Moss);
+ mesc b(l("Bonus: ")) + l("Loot 3, STR +4, AGI +1");
+
+ dnext;
+ mes "";
+ mes l("%s", getitemlink(MoggunEgg));
+ mesc b(l("Acquisition: ")) + l("Grand Hunter Quest");
+ mesc b(l("Food: ")) + getitemlink(MoubooSteak);
+ mesc b(l("Bonus: ")) + l("Loot 3, DEF +5");
+
+ dnext;
+ mes "";
+ mes l("%s", getitemlink(TamedSnakeEgg));
+ mesc b(l("Acquisition: ")) + l("Grand Hunter Quest");
+ mesc b(l("Food: ")) + getitemlink(MoubooSteak);
+ mesc b(l("Bonus: ")) + l("Loot 3, Evasion +7");
+
+ dnext;
+ mes "";
+ mes l("%s", getitemlink(DragonHorn));
+ mesc b(l("Acquisition: ")) + l("Grand Hunter Quest");
+ mesc b(l("Food: ")) + getitemlink(Dragonfruit);
+ mesc b(l("Bonus: ")) + l("Loot 4, Str +1, Gold Drop (4%)");
+
+ dnext;
+ mes "";
+ mes l("%s", getitemlink(BhopEgg));
+ mesc b(l("Acquisition: ")) + l("Easter Top 1 Prize");
+ mesc b(l("Food: ")) + getitemlink(Aquada);
+ mesc b(l("Bonus: ")) + l("Loot 3, Luck +5, Luck Dance, Passive MP Regen (1)");
+
+ dnext;
+ mes "";
+ mes l("%s", getitemlink(DoggyDog));
+ mesc b(l("Acquisition: ")) + l("Valentine Top 1 Prize");
+ mesc b(l("Food: ")) + getitemlink(AnimalBones);
+ mesc b(l("Bonus: ")) + l("Loot 3, STR +5, Vitality Dance, Passive HP Regen (1)");
+
+ dnext;
+ mes "";
+ mes l("%s", getitemlink(CattyCat));
+ mesc b(l("Acquisition: ")) + l("Christmas Top 1 Prize");
+ mesc b(l("Food: ")) + getitemlink(Milk);
+ mesc b(l("Bonus: ")) + l("Loot 3, Agi +5, Agility Dance, Passive MP Regen (1)");
+
+ dnext;
+ mes "";
+ mes l("%s", getitemlink(BlackyCat));
+ mesc b(l("Acquisition: ")) + l("Magic Olympics Top 1 Prize");
+ mesc b(l("Food: ")) + getitemlink(Mashmallow);
+ mesc b(l("Bonus: ")) + l("Loot 3, Int +5, Agi +1");
+
+ dnext;
+ mes "";
+ mes l("%s", getitemlink(PinkieCrystal));
+ mesc b(l("Acquisition: ")) + l("Ultra Rare Drop");
+ mesc b(l("Food: ")) + getitemlink(CherryCake);
+ mesc b(l("Bonus: ")) + l("Loot 3, Agi +5");
+
+ next;
+ mes l("-- Animals Protection Agency of Hurnscald");
+ }
+ return;
+}
+
+- script #Book-Petcaring NPC_HIDDEN,{
+ function read_book {
+ PetcaringBook();
+ close;
+ }
+
+OnShelfUse:
+ @book_name$ = .bookname$;
+ if (openbookshelf ())
+ read_book;
+ close;
+OnUse:
+ @book_name$ = .bookname$;
+ if (openbook ())
+ read_book;
+ close;
+OnInit:
+ .bookname$ = "Fluffy Animals who Love Their Owners";
+ .sex = G_OTHER;
+ .distance = 1;
+ end;
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+- script #Book-JGrimorium NPC_HIDDEN,{
+ end;
+
+function myself {
+ // TODO: Save the variables in temp vars
+ // If getarg(1) is not your charid, detach
+ // Then attach the getarg(1) instead
+ // Display then using the temp vars
+ // For scrying, myself(false, .@me)
+ // While still using attachrid
+ .@all = getarg(0, true);
+ .@who = getarg(1, getcharid(3));
+ .@why = getcharid(3);
+ .@adm = is_admin();
+
+ /* Basic Data */
+ detachrid();
+ attachrid(.@who);
+ // No scrying
+ if (GSET_NOSCRY && .@who != .@why && !.@adm) {
+ detachrid();
+ attachrid(.@why);
+ return 1;
+ }
+ // Basic data
+ .@name$ = strcharinfo(0, "error", .@who);
+ .@staff = is_staff();
+ .@sponsor = is_sponsor();
+ .@party$ = (getcharid(1) ? strcharinfo(1) : "");
+ .@guild$ = (getcharid(2) ? strcharinfo(2) : "");
+ .@clan$ = (getcharid(5) ? strcharinfo(4) : "");
+ .@married = getpartnerid();
+ .@legend = islegendary();
+ if (getpetinfo(0)) {
+ .@pet = true;
+ .@pet_name$ = getpetinfo(2);
+ .@pet_type$ = getpetinfo(1);
+ } else {
+ .@pet = false;
+ }
+ if (gethominfo(0)) {
+ .@homun = true;
+ .@hc_name$ = gethominfo(2);
+ } else {
+ .@homun = false;
+ }
+ .@born = #REG_DATE;
+ detachrid();
+ attachrid(.@why);
+
+ mes ".:: " + .@name$ + " ::.";
+ if (.@staff)
+ mesc l("%s is currently a staff member.", .@name$), 3;
+ else if (.@sponsor)
+ mesc l("%s is currently sponsoring the High Alliance.", .@name$), 3;
+ mes "";
+ if (.@party$ != "")
+ mesc l("Party Name: @@", .@party$);
+ if (.@guild$ != "")
+ mesc l("Guild Name: @@", .@guild$);
+ if (.@clan$ != "")
+ mesc l("Clan Name: @@", .@clan$);
+ if (.@married)
+ mesc l("Civil status: Married");
+ else
+ mesc l("Civil status: Single");
+ if (.@legend)
+ mesc l("%s is a legendary hero.", .@name$), 2;
+ if (.@pet)
+ mesc l("Proud owner of %s the %s.", .@pet_name$, .@pet_type$);
+ if (.@homun)
+ mesc l("Proud owner of %s the Homunculus.", .@hc_name$);
+ mesc l("Born %s ago", FuzzyTime(.@born));
+ dnext;
+
+ /* Magic Data */
+ detachrid();
+ attachrid(.@who);
+ .@lvl = MAGIC_LVL;
+ .@rank$ = academicrank();
+ if (.@all) {
+ .@rp$ = fnum(MAGIC_RP);
+ .@sp = sk_points();
+ .@msp = sk_maxpoints();
+ } else {
+ .@rp$ = "?";
+ }
+ detachrid();
+ attachrid(.@why);
+
+ mes ".:: " + l("Magic Status") + " ::.";
+ mesc l("Current magic rank: %d", .@lvl);
+ if (.@all)
+ mesc l("You have @@/@@ magic skill points available.",
+ b(.@sp), .@msp);
+ mesc l("Your current scholar rank: %s (%s Research Points)",
+ .@rank$, .@rp$);
+ if (.@who == .@why)
+ ShowAbizit(true);
+ dnext;
+
+
+ /* Rogue Data */
+ detachrid();
+ attachrid(.@who);
+ .@rank$ = thiefrank();
+ if (.@all) {
+ .@exp = THIEF_EXP;
+ .@rank= THIEF_RANK;
+ }
+ detachrid();
+ attachrid(.@why);
+
+ mes ".:: " + l("Rogue Status") + " ::.";
+ mesc l("Your current rank: %s", .@rank$);
+ if (.@all && .@exp > (.@rank*2)**5)
+ mesc l("An upgrade is available."), 2;
+ dnext;
+
+
+ /* Craft Data */
+ detachrid();
+ attachrid(.@who);
+ .@skill = getskilllv(TMW2_CRAFT);
+ .@score = CRAFTING_SCORE_COMPLETE/40;
+ .@milis = CRAFTING_SCORE_COMPLETE%40*100; // Broken?
+ detachrid();
+ attachrid(.@why);
+
+ mes ".:: " + l("Crafting Status") + " ::.";
+ mesc l("Skill Level: %d", .@skill);
+ mesc l("Crafting Score: %d.%02d", .@score, .@milis);
+ dnext;
+
+
+
+
+ /* Misc Data */
+ detachrid();
+ attachrid(.@who);
+ if (.@all) {
+ .@mpt = Mobpt;
+ .@gp = (Zeny+BankVault);
+ }
+ .@die = PC_DIE_COUNTER;
+ .@reborn = REBIRTH;
+ .@honor = HONOR;
+ .@kills = MONSTERS_KILLED;
+ .@gid = getcharid(2);
+ .@afk_h = AFKING/1200;
+ .@afk_m = AFKING%1200/60*3;
+ detachrid();
+ attachrid(.@why);
+
+ mes ".:: " + l("Miscellaneous Status") + " ::.";
+ if (.@all)
+ mesc l("Monster Points: %s", fnum(.@mpt));
+ mesc l("Times died: %s", fnum(.@die));
+ mesc l("Times reborn: %d", .@reborn);
+ if (.@all)
+ mesc l("Total Gold: %s", fnum(.@gp));
+ mesc l("Honor Points: %s", fnum(.@honor));
+ mesc l("Monsters killed: %s", fnum(.@kills));
+ if (.@gid > 0) {
+ .@pos=getguildrole(.@gid, .@who);
+ mesc l("Current Guild: %s", getguildname(.@gid));
+ mesc l("Guild Master: @@", getguildmaster(.@gid));
+ if (.@all)
+ mesc l("You are the guild's \"%s\", and you contribute with %02d%% EXP.",
+ getguildpostitle(.@gid, .@pos),
+ getguildpostax(.@gid, .@pos));
+ }
+ mesc l("Total time AFK'ed in Tulimshar: %d hours and %d minutes",
+ .@afk_h, .@afk_m);
+ dnext;
+
+
+ /* Records Data */
+ detachrid();
+ attachrid(.@who);
+ .@candor = CRAZYPOINTS;
+ .@bloodbath = gettimetick(2)+SCANDORPTS;
+ .@ctf = CAPTURE_FLAG;
+ .@cod = getq2(LoFQuest_COD);
+ .@merc = MERCENARY_DAILYQUEST;
+ .@udt = UDTRANK;
+ .@egg = getq3(General_EasterEggs);
+ detachrid();
+ attachrid(.@why);
+ mes ".:: " + l("Personal Records") + " ::.";
+ mesc l("Candor Battle Score: %s", fnum(.@candor));
+ mesc l("Candor Bloodbath Score: %s", FuzzyTime(.@bloodbath));
+ mesc l("Times won Capture the Flag: %s", fnum(.@ctf));
+ mesc l("Times won Call of Dusty: %s", fnum(.@cod));
+ mesc l("Mercenary Quests completed: %s", fnum(.@merc));
+ mesc l("Doppelganger Waves Won: %s", fnum(.@udt));
+ mesc l("Easter Eggs found: %d", .@egg);
+ dnext;
+
+
+ /* Feat Data */
+ detachrid();
+ attachrid(.@who);
+ .@yeti = YETIKING_WINNER;
+ .@hh = HEROESHOLD_WINNER;
+ .@reborn = REBIRTH_WINNER;
+ .@quirin = QUIRINO_WINNER;
+ .@gemini = GEMINI_WINNER;
+ .@ghq = GHQ_WINNER;
+ .@doct = EPISODE_WINNER;
+ .@fort = FORT_1ST_VISIT;
+ .@seal = MOUBOOTAUR_WINNER;
+ detachrid();
+ attachrid(.@why);
+ mes ".:: " + l("Personal Feats") + " ::.";
+ if (.@yeti)
+ mesc l("Cleared the Yeti King Challenge %s ago", FuzzyTime(.@yeti));
+ if (.@hh)
+ mesc l("Cleared Heroes Hold %s ago", FuzzyTime(.@hh));
+ if (.@gemini)
+ mesc l("Cleared Gemini Sisters Quest %s ago", FuzzyTime(.@gemini));
+ if (.@reborn)
+ mesc l("First reborn %s ago", FuzzyTime(.@reborn));
+ if (.@quirin)
+ mesc l("Won Quirino Voraz Arena %s ago", FuzzyTime(.@quirin));
+ if (.@ghq)
+ mesc l("First Grand Hunter challenge cleared %s ago", FuzzyTime(.@ghq));
+ if (.@doct)
+ mesc l("Completed The Episode of Ozthokk %s ago", FuzzyTime(.@doct));
+ if (.@fort)
+ mesc l("First visit to Fortress Is. %s ago", FuzzyTime(.@fort));
+ if (.@seal)
+ mesc l("Defeated the Moubootaur (Sealed) %s ago", FuzzyTime(.@seal));
+
+ /* Heroic Data */
+ detachrid();
+ attachrid(.@who);
+ .@candor = (reputation("Candor") >= 100);
+ .@tulim = (reputation("Tulim") >= 100);
+ .@halin = (reputation("Halin") >= 100);
+ .@hurns = (reputation("Hurns") >= 100);
+ .@lof = (reputation("LoF") >= 100);
+ .@nival = (reputation("Nival") >= 100);
+ .@frost = (reputation("Frostia") >= 100);
+ .@forte = (reputation("Fortress") >= 100);
+ detachrid();
+ attachrid(.@why);
+ if (.@candor)
+ mesc l("%s Hero", l("Candor"));
+ if (.@tulim)
+ mesc l("%s Hero", l("Tulimshar"));
+ if (.@halin)
+ mesc l("%s Hero", l("Halinarzo"));
+ if (.@hurns)
+ mesc l("%s Hero", l("Hurnscald"));
+ if (.@lof)
+ mesc l("%s Hero", l("Land Of Fire"));
+ if (.@nival)
+ mesc l("%s Hero", l("Nivalis"));
+ if (.@frost)
+ mesc l("%s Hero", l("Frostia"));
+ if (.@forte)
+ mesc l("%s Hero", l("Fortress Town"));
+ // TODO: Total players invited to ML
+ // TODO: Houses owned
+ // TODO: Times elected
+ // TODO: Admin of how many towns?
+ // TODO: First election won date
+ // TODO: Most used skill
+ // TODO: Remember the position attained on previous events
+ //mes ".:: " + l("Achievements") + " ::.";
+ return 0;
+}
+
+OnScry:
+ if (!countitem(JesusalvaGrimorium) && !is_staff())
+ end;
+ .@w$ = implode(.@atcmd_parameters$, " ");
+ if (.@w$ == "" || .@w$ == "NULL")
+ .@w$ = strcharinfo(0);
+ .@me = getcharid(3);
+ .@id = getcharid(3, .@w$);
+ if (!.@id) {
+ mesc l("The requested char \"%s\" is not online or does not exist.", .@w$), 1;
+ close;
+ }
+ /* attachrid() + mes() is a no-go for this */
+ setnpcdialogtitle sprintf("@scry %s", .@w$);
+ if (myself(is_admin(), .@id)) {
+ mesc l("%s has protected themselves from prying eyes. Your scry attempt failed.", .@w$), 1;
+ }
+ close;
+
+function read_book {
+
+ setnpcdialogtitle l(.book_name$);
+
+ narrator S_FIRST_BLANK_LINE | S_LAST_NEXT,
+ l("I, second sage of Fate, write this book. The knowledge on it shall guide you to the Secret Of Mana.");
+
+ @menu = 0; // reset for the rif
+
+ do
+ {
+ narrator S_NO_NPC_NAME,
+ l("Please select a chapter:");
+
+ mes "";
+
+ select
+ rif2(1, MAGIC_LVL, l("Ch 1 — Prologue")),
+ rif2(2, MAGIC_EXP, l("Ch 2 — About Magic Skills")),
+ rif2(3, MAGIC_LVL, l("Ch 3 — Status Ailments")),
+ rif2(4, true, l("Ch 4 — Information About You")),
+ rif2(5, true, l("Ch 5 — Information About Others")),
+ rif2(6, true, l("Open Fishing Book")),
+ rif2(7, true, l("Open Petcaring Book")),
+ rif2(8, getq(LoFQuest_Pets), l("List of Unlocked Pets and Food")),
+ rif2(9, CRAFTQUEST, l("Open Recipe Book")),
+ rif2(10, true, l("Read Rules")),
+ l("Close");
+ mes "";
+
+ switch(@menu)
+ {
+ case 1:
+ mesc l("You have @@/@@ magic skill points available.", b(sk_points()), sk_maxpoints());
+ mesc l("Your current scholar rank: %s (%d Research Points)",
+ academicrank(), fnum(MAGIC_RP));
+ next;
+ narrator S_LAST_NEXT,
+ l("Mana is something which existed since the being, but nobody knows much about."),
+ l("This book will write itself, and reveal you the Secret Of Mana."),
+ l("Give it time, increase your magic power, and you'll find out the truth."),
+ l("You are a @@º degree mage. This book allows you many new possibilities.", MAGIC_LVL);
+ break;
+ case 2:
+ narrator S_LAST_NEXT,
+ l("Re-casting the same magic spell or skill won't give you magic experience."),
+ l("Summoning and Homunculus (H) skills can be raised from skill window directly.");
+ // TODO: We could show you all the skills via loop? Meh
+
+ ShowAbizit(true);
+ next;
+ break;
+ case 3:
+ mes l("There are several minor status conditions, which may buff or debuff you.");
+ mes l("An example is dec agi, which lowers your agility.");
+ mes l("The most aggressive and main ones are:");
+ next;
+ mesf("##B%s##b - %s", l("Blind"), l("Acc. and Evade -25%%"));
+ mesf("##B%s##b - %s", l("Burning"), l("Damage over time, MDF -25%%"));
+ mesf("##B%s##b - %s", l("Curse"), l("ATK = 25%%, LUK = 0, Slow down"));
+ mesf("##B%s##b - %s", l("Freeze"), l("Can't move, DEF-, no evade, Water element"));
+ mesf("##B%s##b - %s", l("Poison"), l("DEF-, Damage over time, no MP regen"));
+ mesf("##B%s##b - %s", l("Silence"), l("Can't use skills"));
+ mesf("##B%s##b - %s", l("Sleep"), l("Can't move nor attack, crit def -100%"));
+ mesf("##B%s##b - %s", l("Stone"), l("Can't move, DEF-, Damage over time, Earth element"));
+ mesf("##B%s##b - %s", l("Stun"), l("Can't move nor evade."));
+ next;
+ mes l("There are also less common ailments:");
+ next;
+ mesf("##B%s##b - %s", l("Bleed"), l("Lethal damage over time, no regen."));
+ mesf("##B%s##b - %s", l("Confuse"), l("Random movement and extra desync"));
+ mesf("##B%s##b - %s", l("Cold"), l("Total slow down, DEF-, may freeze"));
+ mesf("##B%s##b - %s", l("Deadly Poison"), l("Lower MAXHP, Damage over time"));
+ mesf("##B%s##b - %s", l("Deep Sleep"), l("Can't chat, recover HP over time"));
+ mesf("##B%s##b - %s", l("Fear"), l("Can't move, Acc. and Evade -20%%"));
+ next;
+ break;
+ case 4:
+ myself();
+ break;
+ case 5:
+ mesc l("You can scry other players with: %s", b("@scry"));
+ mesc l("To scry Jesusaves, for example, you would do:");
+ mes b("@scry Jesusaves");
+ mes "";
+ mesc l("- Players can prevent being scry'ed with %s", b("@ucp"));
+ mesc l("- Some information like money won't be available.");
+ mesc l("- Target player must be online.");
+ next;
+ break;
+ case 6:
+ FishingBook();
+ break;
+ case 7:
+ PetcaringBook();
+ break;
+ case 8:
+ mesf(".:: %s ::.", l("Grand Hunter Quest"));
+ if (PDQ_CheckGHQ(Maggot) >= 10000)
+ mesf("%s - %s",
+ getmonsterlink(Maggot), getitemlink(BugLeg));
+ if (PDQ_CheckGHQ(ForestMushroom) >= 10000)
+ mesf("%s - %s",
+ getmonsterlink(ForestMushroom), getitemlink(Moss));
+ if (PDQ_CheckGHQ(Fluffy) >= 10000)
+ mesf("%s - %s",
+ getmonsterlink(Fluffy), getitemlink(LettuceLeaf));
+ if (PDQ_CheckGHQ(Duck) >= 10000)
+ mesf("%s - %s",
+ getmonsterlink(Duck), getitemlink(CherryCake));
+ if (PDQ_CheckGHQ(Bat) >= 10000)
+ mesf("%s - %s",
+ getmonsterlink(Bat), getitemlink(RoastedMaggot));
+ if (PDQ_CheckGHQ(Moggun) >= 10000)
+ mesf("%s - %s",
+ getmonsterlink(Moggun), getitemlink(MoubooSteak));
+ if (#LOGIN_ALLTIME >= 6)
+ mesf("%s - %s",
+ getmonsterlink(Piou), getitemlink(Piberries));
+ next;
+ mesf(".:: %s ::.", l("Special Event Pets"));
+ if (countitem(Ratte))
+ mesf("%s - %s",
+ getmonsterlink(Ratto), getitemlink(Cheese));
+ if (countitem(BhopEgg))
+ mesf("%s - %s",
+ getmonsterlink(BhopFluffy), getitemlink(Aquada));
+ if (countitem(DoggyDog))
+ mesf("%s - %s",
+ getmonsterlink(Toto), getitemlink(AnimalBones));
+ if (countitem(CattyCat))
+ mesf("%s - %s",
+ getmonsterlink(WhiteCat), getitemlink(Milk));
+ if (countitem(BlackyCat))
+ mesf("%s - %s",
+ getmonsterlink(BlackCat), getitemlink(Mashmallow));
+ next;
+ break;
+ case 9:
+ closeclientdialog;
+ doevent("#RecipeBook::OnUse");
+ end;
+ break;
+ case 10:
+ GameRules();
+ break;
+ default:
+ close;
+ }
+ } while (true);
+
+ end;
+}
+
+OnShelfUse:
+ if (openbookshelf())
+ read_book;
+ bye;
+
+OnUse:
+ read_book;
+ bye;
+
+OnInit:
+ .book_name$ = getitemname(JesusalvaGrimorium);
+ .sex = G_OTHER;
+ .distance = 1;
+ bindatcmd "scry", "#Book-JGrimorium::OnScry", 0, 0, 0;
+ end;
+}
+
diff --git a/npc/items/croconut.txt b/npc/items/croconut.txt
new file mode 100644
index 0000000..eececb0
--- /dev/null
+++ b/npc/items/croconut.txt
@@ -0,0 +1,78 @@
+// Evol scripts.
+// Authors:
+// 4144
+// Reid
+// Jesusalva
+// Description:
+// Allows to break a Croconut into multiple parts.
+
+- script Croconut NPC_HIDDEN,{
+ close;
+
+OnUse:
+ mesc l("Do you want to break open this %s?", getitemlink(Croconut));
+
+ select
+ l("Yes."),
+ l("No.");
+ mes "";
+ closeclientdialog;
+ switch (@menu) {
+ case 1:
+ goto L_Weapon;
+ case 2:
+ getitem Croconut, 1;
+ close;
+ }
+ close;
+
+L_Weapon:
+ .@r=rand2(1,5);
+ switch (.@r) {
+ case 1:
+ case 2:
+ case 3:
+ goto L_TooWeak; break;
+ case 4:
+ goto L_Weak; break;
+ case 5:
+ goto L_Good; break;
+ }
+
+L_TooWeak:
+ // Croconuts do not heal much. So opening them without fail should be possible at relatively low strength levels.
+ .@q = rand2(5);
+ if (readparam2(bStr) > 10)
+ .@q = .@q + 1;
+ if (readparam2(bStr) > 25)
+ .@q = .@q + 1;
+ if (readparam2(bStr) > 35)
+ .@q = .@q + 1;
+
+ if (.@q == 0) goto L_TooWeakLost;
+ if ( (.@q == 1) || (.@q == 2) ) goto L_TooWeakFail;
+ if ( (.@q >= 3) && (.@q <= 6) ) goto L_Weak;
+ if ( (.@q > 6) ) goto L_Good;
+
+L_TooWeakLost:
+ dispbottom l("Oops! You destroyed your %s.", getitemlink(Croconut));
+ close;
+
+L_TooWeakFail:
+ dispbottom l("Well... you did not succeed in opening this %s.", getitemlink(Croconut));
+
+ getitem Croconut, 1;
+ close;
+
+L_Weak:
+ dispbottom l("You broke the %s into two parts, but you crushed one of them.", getitemlink(Croconut));
+
+ getitem HalfCroconut, 1;
+ close;
+
+L_Good:
+ dispbottom l("You perfectly cut your %s into two edible parts.", getitemlink(Croconut));
+
+ getitem HalfCroconut, 2;
+ close;
+}
diff --git a/npc/items/emptybox.txt b/npc/items/emptybox.txt
new file mode 100644
index 0000000..2f676b1
--- /dev/null
+++ b/npc/items/emptybox.txt
@@ -0,0 +1,54 @@
+// TMW2 scripts.
+// Authors:
+// Jesusalva
+// Description:
+// Allows to create your own fish/plushroom/croconut box
+
+- script Empty Box#it NPC_HIDDEN,{
+ close;
+
+OnUse:
+ mesn;
+ mesc l("You can fill this box with the following items:");
+ mesc l("- @@ @@", 7, getitemlink(GrassCarp));
+ mesc l("- @@ @@", 8, getitemlink(Croconut));
+ mesc l("- @@ @@", 20, getitemlink(CommonCarp));
+ mesc l("- @@ @@", 87, getitemlink(Plushroom));
+ mes "";
+ mesc l("Fill with what?");
+ select
+ l("Don't fill"),
+ rif(countitem(GrassCarp) >= 7, l("7 Grass Carps")),
+ rif(countitem(Croconut) >= 8, l("8 Croconuts")),
+ rif(countitem(CommonCarp) >= 20, l("20 Common Carps")),
+ rif(countitem(Aquada) >= 50, l("50 Aquadas")),
+ rif(countitem(Plushroom) >= 87, l("87 Plushrooms"));
+ mes "";
+ switch (@menu) {
+ case 2:
+ delitem GrassCarp, 7;
+ getitem FishBox, 1;
+ break;
+ case 3:
+ delitem Croconut, 8;
+ getitem CroconutBox, 1;
+ break;
+ case 4:
+ delitem CommonCarp, 20;
+ getitem FishBox, 1;
+ break;
+ case 5:
+ delitem Aquada, 50;
+ getitem AquadaBox, 1;
+ break;
+ case 6:
+ delitem Plushroom, 87;
+ getitem PlushroomBox, 1;
+ break;
+ default:
+ getitem EmptyBox, 1;
+ }
+ delitem EmptyBox, 1;
+ closedialog;
+ close;
+}
diff --git a/npc/items/grenade.txt b/npc/items/grenade.txt
new file mode 100644
index 0000000..96d952c
--- /dev/null
+++ b/npc/items/grenade.txt
@@ -0,0 +1,122 @@
+// TMW2 scripts.
+// Authors:
+// Jesusalva
+// Description:
+// Grenades workaround
+
+// grenade(range, damage - in 0.01%, flag) - defaults to 3x3 square, with 5% damage.
+// If flag is set, damage will be deemed to be absolute values.
+function script grenade {
+ .@r=getarg(0, 3);
+ .@d=getarg(1, 500);
+ .@f$=getarg(2, "filter_notboss");
+
+ getmapxy(.@m$, .@x, .@y, 0);
+ .@c=getunits(BL_MOB, .@mbs, false, .@m$, .@x-.@r, .@y-.@r, .@x+.@r, .@y+.@r);
+ for (.@i = 0; .@i < .@c; .@i++) {
+ .@hp=getunitdata(.@mbs[.@i], UDT_HP);
+ .@dm=max(1, .@hp*(10000-.@d)/10000);
+ if (getarg(2, false))
+ .@dm=max(1, .@hp-.@d);
+ if (callfunc(.@f$)) {
+ //debugmes "Hitting monster (%d hp) for %d damage", .@hp, .@dm;
+ setunitdata(.@mbs[.@i], UDT_HP, .@dm);
+ specialeffect(FX_ATTACK, AREA, .@mbs[.@i]);
+ }
+ }
+ return;
+}
+
+// areasc(range, time, sc, bl, value, filter, target, chances)
+// Defaults to 3x3 square, sleep mob for 500ms. Ignores you.
+// Centered on player attached, 100% success chance
+// Need a player caster. Valid BL: BL_MOB | BL_PC | BL_HOM | BL_MER
+function script areasc {
+ .@r=getarg(0, 3);
+ .@d=getarg(1, 500);
+ .@s=getarg(2, SC_SLEEP);
+ .@b=getarg(3, BL_MOB);
+ .@val=getarg(4, 1);
+ .@f$=getarg(5, "filter_notme");
+ .@t=getarg(6, playerattached());
+ .@sr=getarg(7, 10000);
+
+ getmapxy(.@m$, .@x, .@y, getunittype(.@t), .@t);
+ .@c=getunits(.@b, .@mbs, false, .@m$, .@x-.@r, .@y-.@r, .@x+.@r, .@y+.@r);
+ for (.@i = 0; .@i < .@c; .@i++) {
+ // Filtering
+ if (!callfunc(.@f$, .@mbs[.@i]))
+ continue;
+ sc_start .@s, .@d, .@val, .@sr, SCFLAG_NONE, .@mbs[.@i];
+ specialeffect(FX_BUFF, AREA, .@mbs[.@i]);
+ }
+ return;
+}
+
+// areasc2(map, x, y, {range, time, sc, bl, value, filter}) - can be used by NPC
+// Valid BL: BL_MOB | BL_PC | BL_HOM | BL_MER
+function script areasc2 {
+ .@m$=getarg(0);
+ .@x=getarg(1);
+ .@y=getarg(2);
+ .@r=getarg(3, 3);
+ .@d=getarg(4, 500);
+ .@s=getarg(5, SC_SLEEP);
+ .@b=getarg(6, BL_MOB);
+ .@val=getarg(7, 1);
+ .@f$=getarg(8, "filter_always");
+
+ .@c=getunits(.@b, .@mbs, false, .@m$, .@x-.@r, .@y-.@r, .@x+.@r, .@y+.@r);
+ for (.@i = 0; .@i < .@c; .@i++) {
+ // Filtering
+ if (!callfunc(.@f$, .@mbs[.@i]))
+ continue;
+ sc_start .@s, .@d, .@val, 10000, SCFLAG_NONE, .@mbs[.@i];
+ specialeffect(FX_BUFF, AREA, .@mbs[.@i]);
+ }
+ return;
+}
+
+// areasc3(range, time, sc, bl, val1, val2, filter)
+// Defaults to 3x3 square, sleep mob for 500ms. Ignores you.
+// Need a player caster. Valid BL: BL_MOB | BL_PC | BL_HOM | BL_MER
+function script areasc3 {
+ .@r=getarg(0, 3);
+ .@d=getarg(1, 500);
+ .@s=getarg(2, SC_SLEEP);
+ .@b=getarg(3, BL_MOB);
+ .@v1=getarg(4, 1);
+ .@v2=getarg(5, 1);
+ .@f$=getarg(6, "filter_notme");
+
+ getmapxy(.@m$, .@x, .@y, 0);
+ .@c=getunits(.@b, .@mbs, false, .@m$, .@x-.@r, .@y-.@r, .@x+.@r, .@y+.@r);
+ for (.@i = 0; .@i < .@c; .@i++) {
+ // Filtering
+ if (!callfunc(.@f$, .@mbs[.@i]))
+ continue;
+ sc_start2 .@s, .@d, .@v1, .@v2, 10000, SCFLAG_NONE, .@mbs[.@i];
+ specialeffect(FX_BUFF, AREA, .@mbs[.@i]);
+ }
+ return;
+}
+
+// massprovoke(range, {map, x, y}) - player only
+function script massprovoke {
+ getmapxy(.@m$, .@x, .@y, 0);
+ .@r=getarg(0, 3);
+ .@m$=getarg(1, .@m$);
+ .@x=getarg(2, .@x);
+ .@y=getarg(3, .@y);
+
+ .@c=getunits(BL_MOB, .@mbs, false, .@m$, .@x-.@r, .@y-.@r, .@x+.@r, .@y+.@r);
+ for (.@i = 0; .@i < .@c; .@i++) {
+ //sc_start .@s, .@d, 1, 10000, SCFLAG_NONE, .@mbs[.@i];
+ aggravate .@mbs[.@i];
+ specialeffect(FX_MAGIC, AREA, .@mbs[.@i]);
+ }
+ return;
+}
+
+// TODO: Maybe we could use areasc() with a special check
+// To force the implementation of guild skills... (Yet another script based)
diff --git a/npc/items/inc_sc_bonus.txt b/npc/items/inc_sc_bonus.txt
new file mode 100644
index 0000000..8e6b6e5
--- /dev/null
+++ b/npc/items/inc_sc_bonus.txt
@@ -0,0 +1,72 @@
+// TMW-2 Script.
+// Author:
+// Jesusalva
+// Description:
+// Applies effects for INC_* (STR doesn't exist)
+// Valid values: INCAGI INCVIT INCINT INCDEX INCLUK INCHIT INCFLEE SC_FURY
+// Doesn't works: SC_STRUP
+// Works if .@min == .@max: INCMHP INCMHPRATE INCMSP INCMSPRATE
+/// Untested Values: WALKSPEED (reverse logic) INVINCIBLE (broken)
+// PS. SC_FURY causes crit rate to increase
+//
+// Variables:
+// .@delay Second of buffing
+// .@type SC_*
+// .@min Min amount of type
+// .@max Max amount of type (optional)
+
+// SC_Bonus(delay, SC, min{, max})
+function script SC_Bonus {
+ .@delay=getarg(0);
+ .@type=getarg(1);
+ .@min=getarg(2);
+ .@max=getarg(3, .@min);
+ if (.@delay <= 0)
+ return false;
+
+ // Get the bonus value
+ if (.@min != .@max)
+ .@bonus=rand2(.@min, .@max);
+ else
+ .@bonus=.@min;
+
+ // Remaining time and effect conversion
+ .@v=getstatus(.@type, 1);
+ .@t=getstatus(.@type, 5);
+
+ // Convert remaining time to seconds, rounded down
+ if (.@t > 1000)
+ .@t=.@t/1000;
+ else
+ .@t=0;
+
+ // If there was effect previously, get ponderate average
+ if (.@v > 0)
+ .@v=ponderate_avg(.@bonus, .@delay, .@v, .@t);
+ else
+ .@v=.@bonus;
+
+ // Update time value to ms and to stack
+ .@t+=.@delay;
+ .@t*=1000;
+
+ // Debug print if needed
+ if (debug || $@GM_OVERRIDE)
+ debugmes "Effect %d (+%d percent) for %d ms", .@type, .@bonus, .@t;
+
+ // Restart the bonus
+ sc_end .@type;
+ sc_start .@type,.@t,.@v;
+ return true;
+}
+
+- script inc_sc_bonus -1,{
+OnUse:
+ SC_Bonus(@delay, @type, @min, @max);
+ @delay=0;
+ @type=0;
+ @min=0;
+ @max=0;
+ end;
+}
+
diff --git a/npc/items/legacy_heal.txt b/npc/items/legacy_heal.txt
new file mode 100644
index 0000000..2beb820
--- /dev/null
+++ b/npc/items/legacy_heal.txt
@@ -0,0 +1,79 @@
+// TMW-2 Script.
+// Evol scripts.
+// Author:
+// Reid
+// Jesusalva
+// Description:
+// Legacy Healing System
+//
+// Variables:
+// @delay Second of healing
+// @min Min amount of healing
+// @max Max amount of healing
+//
+// *getequipoption(EQI_HEAD_TOP,1,168); → Heal Bonus (should be first bonus on Chef Hat)
+
+
+// ItHeal(delay, min, {max=min})
+function script ItHeal {
+ .@delay=getarg(0, @delay);
+ .@min=getarg(1, @min);
+ .@max=getarg(2, (@max ? @max : .@min));
+
+ if (.@delay <= 0) {
+ Exception("Invalid legacy healing item, deleting without healing effect.");
+ end;
+ }
+
+ // Decide the healing bonus type. We have four types: S, L, G and M
+ // By default, we use 'S'
+ .@skill = SC_S_LIFEPOTION;
+
+ // Vitality can improve the healing amount
+ .@min = min(.@max, .@min + (.@min * readparam2(bVit) / 100));
+ .@max = .@max + (.@max * readparam2(bVit) / 240);
+
+ // Obtain the real healing
+ @val1 = rand2(.@min, .@max) / .@delay;
+ //debugmes "Heal %d-%d/%d = %d", .@min, .@max, .@delay, @val1;
+
+ .@delay *= 1000; // Put the delay in ms
+
+ // We now have @val1 (new effect), @delay (new delay)
+ // But do we have .@v and .@d (old effect and delay)?
+ .@v=getstatus(.@skill, 1);
+ .@d=getstatus(.@skill, 4) * 1000;
+
+ // If there WAS an effect previously, get ponderate average
+ // Note: never use double-precision ponderate averages
+ if (.@v > 0) {
+ @val1=ponderate_avg(@val1, .@delay, .@v, .@d);
+ // Overflow and Underflow protection
+ if (.@delay+.@d < .@delay*5 && .@d > 0)
+ .@delay=.@delay+.@d;
+ //.@delay=ponderate_avg(.@delay, @val1, .@d, .@v);
+ }
+
+ // Apply the effect and finish
+ sc_end .@skill;
+ sc_start2 .@skill, .@delay, @val1, 1;
+
+ // @val1 must be preserved for cross-reading
+ @delay=0;
+ @min=0;
+ @max=0;
+ // @val1=0;
+ return;
+}
+
+- script legacy_heal -1,{
+
+OnUse:
+ if (@delay <= 0) {
+ Exception("Invalid legacy healing item, deleting without healing effect.");
+ end;
+ }
+
+ ItHeal(@delay, @min, @max);
+ end;
+}
diff --git a/npc/items/lofteleporter.txt b/npc/items/lofteleporter.txt
new file mode 100644
index 0000000..9952d72
--- /dev/null
+++ b/npc/items/lofteleporter.txt
@@ -0,0 +1,123 @@
+// TMW2 scripts.
+// Authors:
+// Pyndragon
+// Jesusalva
+// Description:
+// Hand Teleporter (also saves coordinates - @memo)
+
+- script LoF Teleporter NPC_HIDDEN,{
+ close;
+
+ // Checks if you can warp
+ function loftel_check {
+ getmapxy(.@m$, .@x, .@y, 0);
+ .@is_hurt=(readparam(Hp) < readparam(MaxHp)*9/10); // <90% hp
+ .@is_town=(getmapflag(.@m$, mf_town));
+ return (.@is_hurt && !.@is_town);
+ }
+
+ // Calculate time remaining
+ // (time, .@x)
+ function loftel_time {
+ return gettimetick(2)+max((60*getarg(0))-(getarg(1)*60), 30);
+ }
+
+L_Cooldown:
+ mesn;
+ mesc l("This teleporter is currently recharging.");
+ mesc l("You can use it again in @@.", FuzzyTime(TELEPORTER_TIME));
+ close;
+
+OnUse:
+ if (TELEPORTER_TIME > gettimetick(2))
+ goto L_Cooldown;
+ if (readparam(Hp) < readparam(MaxHp)) {
+ dispbottom l("You are hurt, and cannot use this.");
+ end;
+ }
+ if (BaseLevel < 20) {
+ dispbottom l("This is too powerful to you. Get level 20 before attempting to use.");
+ end;
+ }
+
+ mesn;
+ mesc l("Ozthokk, a great sage from the Land Of Fire, holds secrets of time and space travel.");
+ mesc l("This is not magic, it is science!");
+ mes "";
+ mesc l("PS. Additional reagents may be required for warps.");
+ next;
+
+ .@x=(reputation("LoF")/10)+min(15, countitem(TimeFlask)-1); // up to 10 minutes reduction from quests, and 15 from time flasks
+
+ select
+ l("Don't warp"),
+ l("Land Of Fire Village (@@m)", 35-.@x),
+ l("Tulimshar (@@m)", 35-.@x),
+ rif(TELEPORTERS & TP_FROST, l("Frostia (@@m)", 120-.@x)),
+ rif(TELEPORTERS & TP_HALIN, l("Halinarzo (@@m)", 120-.@x)),
+ rif(TELEPORTERS & TP_LILIT, l("Lilit (@@m)", 150-.@x)),
+ rif(GSET_SOULMENHIR_MANUAL, l("Save Point (@@m)", 30-.@x)),
+ rif(getq(LoFQuest_EPISODE) >= 15, l("200 years ago, The Great Fire (%dm)", 360-(.@x*2)));
+
+ if (@menu == 1)
+ close;
+
+ switch (@menu) {
+ case 1:
+ }
+ if (loftel_check()) {
+ dispbottom l("You are hurt, and cannot use this.");
+ }
+ sshake();
+ switch (@menu) {
+ case 2:
+ warp "017-1", 120, 89;
+ TELEPORTER_TIME=loftel_time(35, .@x);
+ EnterTown("LoF");
+ @timer_navio_running=0;
+ break;
+ case 3:
+ warp "003-1", 41, 49;
+ TELEPORTER_TIME=loftel_time(35, .@x);
+ EnterTown("Tulim");
+ @timer_navio_running=0;
+ break;
+ case 4:
+ warp "024-1", 155, 82;
+ TELEPORTER_TIME=loftel_time(120, .@x);
+ EnterTown("Frostia");
+ @timer_navio_running=0;
+ break;
+ case 5:
+ warp "009-1", 113, 91;
+ TELEPORTER_TIME=loftel_time(120, .@x);
+ EnterTown("Halin");
+ @timer_navio_running=0;
+ break;
+ case 6:
+ warp "018-5", 111, 53;
+ TELEPORTER_TIME=loftel_time(150, .@x);
+ EnterTown("Lilit");
+ @timer_navio_running=0;
+ break;
+ case 7:
+ TELEPORTER_TIME=loftel_time(30, .@x);
+ //EnterTown("Save");
+ teleporthome();
+ @timer_navio_running=0;
+ break;
+ case 8:
+ //atcommand("@block "+strcharinfo(0));
+ // Reset your bank savings (temporary)
+ #MerchantBank+=BankVault;
+ BankVault=0;
+ setq2 LoFQuest_EPISODE, 0;
+ setq3 LoFQuest_EPISODE, 0;
+ warp "032-1", 23, 25;
+ TELEPORTER_TIME=loftel_time(360, .@x*2);
+ @timer_navio_running=0;
+ break;
+ }
+ closedialog;
+ end;
+}
diff --git a/npc/items/maps.txt b/npc/items/maps.txt
new file mode 100644
index 0000000..cb9b418
--- /dev/null
+++ b/npc/items/maps.txt
@@ -0,0 +1,17 @@
+// TMW2 Script.
+// Authors:
+// Jesusalva
+// Description:
+// World Map Items
+
+function script wmap {
+ .@loc$=getarg(0, LOCATION$);
+ setnpcdialogtitle l("World Map - @@", l(.@loc$));
+ setskin "map_"+.@loc$;
+ mes "Please keep your ManaPlus updated.";
+ select("Ok");
+ setskin "";
+ closeclientdialog;
+ return;
+}
+
diff --git a/npc/items/mercenary.txt b/npc/items/mercenary.txt
new file mode 100644
index 0000000..140d388
--- /dev/null
+++ b/npc/items/mercenary.txt
@@ -0,0 +1,150 @@
+// TMW2 scripts.
+// Authors:
+// Jesusalva
+// Description:
+// Core functions for Mercenary boxset
+
+// Main loop
+// merc_boxset(5★, 4★, 3★, 2★, 1★)
+function script merc_boxset {
+ .@s5=getarg(0,0);
+ .@s4=getarg(1,0);
+ .@s3=getarg(2,0);
+ .@s2=getarg(3,0);
+ .@s1=getarg(4,0);
+
+ .@sumup=.@s5+.@s4+.@s3+.@s2+.@s1;
+ /* I wonder if this is needed...?
+ .@s4+=.@s5;
+ .@s3+=.@s4;
+ .@s2+=.@s3;
+ .@s1+=.@s2;
+ */
+
+ // Make the seed. More level and luck increases odds of higher rarity
+ .@seed=max(0, rand(0, .@sumup)-BaseLevel-readparam(bLuk));
+
+ // 5 ★
+ if (.@seed < .@s5) {
+ setarray .@r, MercCard_AndreiSakar, MercCard_Woody, MercCard_Lilanna, MercCard_Xanthem;
+ .@n=any_of(.@r);
+ // 4 ★
+ } else if (.@seed < .@s4) {
+ setarray .@r, MercCard_Aisen, MercCard_Msawis, MercCard_Swezanne, MercCard_DragonStar;
+ .@n=any_of(.@r);
+ // 3 ★
+ } else if (.@seed < .@s3) {
+ setarray .@r, MercCard_Saulc, MercCard_Crazyfefe, MercCard_LawnCable, MercCard_Arthur;
+ .@n=any_of(.@r);
+ // 2 ★
+ } else if (.@seed < .@s2) {
+ setarray .@r, MercCard_Pookie, MercCard_Jesusalva, MercCard_Demure, MercCard_EarthWitch;
+ .@n=any_of(.@r);
+ // 1 ★
+ } else {
+ setarray .@r, MercCard_Apane, MercCard_Soren, MercCard_GonzoDark, MercCard_Rosa;
+ .@n=any_of(.@r);
+ }
+
+ getitem .@n, 1;
+ return;
+}
+
+/*
+// Setup a merc_boxset based on level (TODO)
+function script cond_mercboxset {
+ return;
+}
+*/
+
+// Get mercenary ID
+// merc_randid(5★, 4★, 3★, 2★, 1★)
+function script merc_randid {
+ .@s5=getarg(0,0);
+ .@s4=getarg(1,0);
+ .@s3=getarg(2,0);
+ .@s2=getarg(3,0);
+ .@s1=getarg(4,0);
+
+ .@sumup=.@s5+.@s4+.@s3+.@s2+.@s1;
+ /* I wonder if this is needed...?
+ .@s4+=.@s5;
+ .@s3+=.@s4;
+ .@s2+=.@s3;
+ .@s1+=.@s2;
+ */
+
+ // Make the seed. More level and luck increases odds of higher rarity
+ .@seed=max(0, rand(0, .@sumup)-BaseLevel-readparam(bLuk));
+
+ // 5 ★
+ if (.@seed < .@s5) {
+ setarray .@r, 1192, 1191, 1193, 1210;
+ .@n=any_of(.@r);
+ // 4 ★
+ } else if (.@seed < .@s4) {
+ setarray .@r, 1194, 1195, 1209, 1205;
+ .@n=any_of(.@r);
+ // 3 ★
+ } else if (.@seed < .@s3) {
+ setarray .@r, 1196, 1197, 1198, 1208;
+ .@n=any_of(.@r);
+ // 2 ★
+ } else if (.@seed < .@s2) {
+ setarray .@r, 1200, 1201, 1199, 1207;
+ .@n=any_of(.@r);
+ // 1 ★
+ } else {
+ setarray .@r, 1203, 1204, 1202, 1206;
+ .@n=any_of(.@r);
+ }
+
+ //debugmes "Return: %d ([%d, %d, %d])", .@n, .@r[0], .@r[1], .@r[2];
+ return .@n;
+}
+
+// Read the card and return its rarity
+// merc_getstar(card)
+function script merc_getstar {
+ switch (getarg(0)) {
+ case MercBoxE:
+ case MercBoxEE:
+ case MercCard_AndreiSakar:
+ case MercCard_Lilanna:
+ case MercCard_Woody:
+ case MercCard_Xanthem:
+ return 5;
+ case MercBoxD:
+ case MercBoxDD:
+ case MercCard_Aisen:
+ case MercCard_Msawis:
+ case MercCard_DragonStar:
+ case MercCard_Swezanne:
+ return 4;
+ case MercBoxC:
+ case MercBoxCC:
+ case MercCard_Saulc:
+ case MercCard_Crazyfefe:
+ case MercCard_LawnCable:
+ case MercCard_Arthur:
+ return 3;
+ case MercBoxB:
+ case MercBoxBB:
+ case MercCard_Pookie:
+ case MercCard_Jesusalva:
+ case MercCard_Demure:
+ case MercCard_EarthWitch:
+ return 2;
+ case MercBoxA:
+ case MercBoxAA:
+ case MercCard_Apane:
+ case MercCard_Soren:
+ case MercCard_GonzoDark:
+ case MercCard_Rosa:
+ return 1;
+ default:
+ return 0;
+ }
+ return -1;
+}
+
diff --git a/npc/items/miscrecipes.txt b/npc/items/miscrecipes.txt
new file mode 100644
index 0000000..275d5b0
--- /dev/null
+++ b/npc/items/miscrecipes.txt
@@ -0,0 +1,93 @@
+// TMW-2 script.
+// Author:
+// Jesusalva
+// Description:
+// More specific Recipe Books in TMW2
+
+// callfunc("TerraniteBlueprint")
+function script TerraniteBlueprint {
+ // Lucky roll (0.25%)
+ if (rand2(2500) < 1) {
+ getitem AncientBlueprint, 1;
+ dispbottom l("Wait a minute... This is written in Mananese! I can't read it!");
+ return;
+ }
+
+ // Setup
+ array_push(.@recipes, CraftRockKnife); // Weapon: Rock Knife
+ array_push(.@recipes, CraftTerraniteArmor); // TerraniteArmor
+ array_push(.@recipes, CraftTerranitePants); // TerranitePants
+ array_push(.@recipes, CraftTerraniteHelmet); // TerraniteHelmet
+ //array_push(.@recipes, CraftTerranite); // TerraniteGloves (?)(X)(TODO)
+ //array_push(.@recipes, CraftTerranite); // TerraniteBoots (?)(X)(TODO)
+
+ // Now you'll learn some recipe!
+ .@rcp=any_of(.@recipes);
+
+ // Double precision failsafe
+ if (RECIPES_EQUIPMENT[.@rcp])
+ .@rcp=any_of(.@recipes);
+
+ // Maybe you already knew it?
+ if (RECIPES_EQUIPMENT[.@rcp]) {
+ .@mpot=rand2(2000, 5000);
+ dispbottom l("It was a recipe you already knew... (+ @@ Mobpt)", .@mpot);
+ getexp (BaseLevel+JobLevel)*rand2(1,.@rarity), JobLevel+rand2(1,.@rarity);
+ // Give you some Monster Points to use with Intense Beard
+ // You do NOT need to be registered with Aidan for this.
+ Mobpt+=.@mpot;
+ } else {
+ dispbottom l("Learned a new recipe!");
+ RECIPES_EQUIPMENT[.@rcp]=true;
+ }
+ return;
+}
+/////////////////////////////////////////////////////////////////////////////////
+
+// callfunc("LegendaryBlueprint")
+function script LegendaryBlueprint {
+ // Unlucky roll (0.25%)
+ if (rand2(2500) < 1) {
+ getitem AncientBlueprint, 1;
+ dispbottom l("Wait a minute... This is written in Mananese! I can't read it!");
+ return;
+ }
+ // Current: 3x Mylarin & 2x Sunny Crystal (can be cheated for 2)
+ // Needed: 3x Mylarin & 2x Sunny Crystal
+
+ // Setup
+ array_push(.@recipes, CraftSkypiercer); // Weapon: Sky Piercer
+ array_push(.@recipes, CraftSaviorShield); // Shield: Savior Shield
+ array_push(.@recipes, CraftSaviorArmor); // Savior Armor
+ //array_push(.@recipes, CraftSaviorPants); // Savior Pants
+ array_push(.@recipes, CraftSaviorBoots); // Savior Boots
+ array_push(.@recipes, CraftSaviorHelmet); // Savior Helmet
+ //array_push(.@recipes, CraftSavior); // Savior Gloves (?)(X)
+
+ // Now you'll learn some recipe!
+ .@rcp=any_of(.@recipes);
+
+ // Double precision failsafe
+ if (RECIPES_EQUIPMENT[.@rcp])
+ .@rcp=any_of(.@recipes);
+
+ // Triple precision failsafe
+ if (RECIPES_EQUIPMENT[.@rcp])
+ .@rcp=any_of(.@recipes);
+
+ // Maybe you already knew it?
+ if (RECIPES_EQUIPMENT[.@rcp]) {
+ .@mpot=rand2(40000, 80000);
+ dispbottom l("It was a recipe you already knew... (+ @@ Mobpt)", .@mpot);
+ getexp rand(100000, 200000), rand(10000, 25000);
+ // Give you some Monster Points to use with Intense Beard
+ // You do NOT need to be registered with Aidan for this.
+ Mobpt+=.@mpot;
+ } else {
+ dispbottom l("Learned a new recipe!");
+ RECIPES_EQUIPMENT[.@rcp]=true;
+ }
+ return;
+}
+
+
diff --git a/npc/items/rand_mp_heal.txt b/npc/items/rand_mp_heal.txt
new file mode 100644
index 0000000..4b6a5ca
--- /dev/null
+++ b/npc/items/rand_mp_heal.txt
@@ -0,0 +1,63 @@
+// TMW-2 Script.
+// Author:
+// Jesusalva
+// Description:
+// UGLY WORKAROUND. Legacy Method Only.
+// Variables:
+// @min
+// @max
+// @delay
+
+function script MPHeal {
+ @delay = getarg(0);
+ @min = getarg(1);
+ @max = getarg(2);
+
+ if (@delay <= 0) {
+ Exception("Invalid healing item, deleting without healing effect.");
+ end;
+ }
+
+ // +1 max MP per 3 Int, +1 min MP per 5 int.
+ // Original max MP will be respected
+ @max = min(@max*2, @min+(readparam2(bInt)/5));
+ @min = min(@max, @min+(readparam2(bInt)/3));
+
+ // Make these abstract % in absolute values
+ @min=max(1, MaxHp*@min/100);
+ @max=max(3, MaxHp*@max/100);
+
+ // Save the effect
+ @mp_healeffect = rand2(@min, @max);
+ @mp_healeffect = @mp_healeffect / @delay + 1;
+ @mp_healdelay = @delay;
+
+ // Apply the effect and finish
+ deltimer .name$+"::OnUpdate";
+ addtimer 1000, .name$+"::OnUpdate";
+
+ // Clear stuff
+ // @mp_healeffect and @mp_healdelay must be preserved for cross-reading
+ @delay=0;
+ @min=0;
+ @max=0;
+}
+
+- script rand_mp_heal -1,{
+
+OnUse:
+ MPHeal(@delay, @min, @max);
+ end;
+
+// Script Heart
+OnUpdate:
+ deltimer .name$+"::OnUpdate";
+ heal 0, @mp_healeffect;
+ @mp_healdelay-=1;
+ if (@mp_healdelay >= 1)
+ addtimer 1000, .name$+"::OnUpdate";
+ else
+ @mp_healeffect=0;
+ end;
+}
+
diff --git a/npc/items/rand_sc_heal.txt b/npc/items/rand_sc_heal.txt
new file mode 100644
index 0000000..41ea3b3
--- /dev/null
+++ b/npc/items/rand_sc_heal.txt
@@ -0,0 +1,112 @@
+// TMW-2 Script.
+// Evol scripts.
+// Author:
+// Reid
+// Jesusalva
+// Description:
+// Random heal every x seconds.
+//
+// Variables:
+// @type
+// 0 - Sweeties (lowest)
+// 1 - Vegetables
+// 2 - Proteins
+// 3 - Proccessed
+// 4 - Magical (highest)
+// @delay
+// Overrides the lasting time
+// @rarity
+// How rare the item is (how much should it effect)
+// Ranges from 1 to 10.
+//
+// Formula:
+// MinHeal %: @rarity * ((@type*1) + 1)
+// MaxHeal %: @rarity * ((@type*2) + 1)
+// Delay: 1 + (@type*2)
+// Sweeties: 1s
+// Vegetables: 3s
+// Proteins: 5s
+// Proccessed: 7s
+// Magical: 9s
+//
+// *getequipoption(EQI_HEAD_TOP,1,168); → Heal Bonus (should be first bonus on Chef Hat)
+
+// ItHeal(type, rarity{, delay=auto})
+function script ItHeal2 {
+ .@type=getarg(0);
+ .@rarity=getarg(1);
+ .@delay=getarg(2, 0);
+
+ // Calculate healing value in %
+ .@min=.@rarity * ((.@type*1) + 1);
+ .@max=.@rarity * ((.@type*2) + 1);
+
+ // Healing items always have a bonus positive variation as you are reborn
+ .@max+=REBIRTH;
+
+ // Vitality raises the minimum healing value in 1%, capped at maximum vlaue
+ // It also raises @max up to double
+ .@max = min(.@max*2, .@max+(readparam2(bVit)/50));
+ .@min = min(.@max, .@min+(readparam2(bVit)/30));
+
+ // Make these abstract % in absolute values
+ .@min=max(1, MaxHp*.@min/100);
+ .@max=max(3, MaxHp*.@max/100);
+
+ // Calculate how much you'll heal
+ @val1 = rand2(.@min, .@max);
+
+ // Calculate delay if it was not given
+ if (.@delay <= 0) {
+ .@delay=1 + ((.@type*3)/2);
+ }
+
+ // Update val1
+ @val1 = (@val1 / .@delay) + 1;
+
+ // Decide the healing bonus type. We have four types: S, L, G and M
+ // By default, we use 'S'
+ .@skill = SC_S_LIFEPOTION;
+
+ // We now have @val1 (new effect), @delay (new delay)
+ // But do we have .@v and .@d (old effect and delay)?
+ //Meh, just override it
+ /*
+ if (getstatus(.@skill)) {
+ .@v=getstatus(.@skill, 1);
+ .@d=getstatus(.@skill, 5);
+ }
+
+ // If there WAS an effect previously, get ponderate average
+ if (.@v > 0) {
+ @val1=ponderate_avg(@val1, @delay, .@v, .@d);
+ @delay=ponderate_avg(@delay, @val1, .@d, .@v);
+ }
+ */
+
+ // Put the delay in ms
+ .@delay *= 1000;
+
+ // Apply the effect and finish
+ sc_end .@skill;
+ sc_start2 .@skill, .@delay, @val1, 1;
+ return @val1;
+}
+
+- script rand_sc_heal -1,{
+ end;
+OnUse:
+ if (@rarity <= 0) {
+ Exception("Invalid healing item, deleting without healing effect.");
+ end;
+ }
+ ItHeal2(@type, @rarity, @delay);
+
+ // Clear stuff
+ // @val1 must be preserved for cross-reading
+ @delay=0;
+ @type=0;
+ @rarity=0;
+ // @val1=0;
+ end;
+}
diff --git a/npc/items/shovel.txt b/npc/items/shovel.txt
new file mode 100644
index 0000000..ed1f464
--- /dev/null
+++ b/npc/items/shovel.txt
@@ -0,0 +1,544 @@
+// TMW2 Script
+// Evol scripts.
+// Author:
+// Travolta
+// Jesusalva
+// Description:
+// NPC to use shovel (dig, bury etc)
+// TODO: A function to retrieve a list of all walkable cells on a map
+// would be a great improvement to shovel_* functions. As long that it doesn't
+// a huge loop, of course...
+
+// shovel_scriptItem( map, x, y, item, {amount} )
+function script shovel_scriptItem {
+ .@map$=getarg(0);
+ .@x=getarg(1);
+ .@y=getarg(2);
+ .@id=getarg(3);
+ .@amount=getarg(4,1);
+
+ // Players can't walk on this cell, why bother?
+ if (!checkcell(.@map$, .@x, .@y, cell_chkpass))
+ return;
+
+ // Bury the item, they're renewed daily at 00:00
+ .@wtc = getarraysize($@WBT_Random_id);
+ $@WBT_Random_id[.@wtc] = .@id;
+ $@WBT_Random_amount[.@wtc] = .@amount;
+ $@WBT_Random_map$[.@wtc] = .@map$;
+ $@WBT_Random_x[.@wtc] = .@x;
+ $@WBT_Random_y[.@wtc] = .@y;
+ //debugmes "Buried"+.@amount+" "+getitemname(.@id);
+ return;
+}
+
+// shovel_scatter( map, x1, y1, x2, y2, amount, item1, {item2, item3...} )
+function script shovel_scatter {
+ .@map$=getarg(0);
+ .@x1=getarg(1);
+ .@y1=getarg(2);
+ .@x2=getarg(3);
+ .@y2=getarg(4);
+ .@amount=getarg(5,1);
+ .@dbgamm=.@amount; // debug message
+
+ .@wta = getarraysize($@WBT_Random_id); // wta - original
+ .@wtb = .@wta+.@amount; // wtb - future
+ .@tries=3;
+ do {
+ freeloop(true); // DANGEROUS
+ for (.@i = 0; .@i < .@amount; .@i++)
+ shovel_scriptItem(.@map$, rand(.@x1,.@x2), rand(.@y1,.@y2), getarg(rand2(getargcount()-6)+6));
+ freeloop(false);
+
+ .@wtc = getarraysize($@WBT_Random_id); // wtc - current
+ .@amount=.@wtb-.@wtc;
+
+ //debugmes "WTA %d WTB %d. WTC %d, tries %d, amount %d and dbg %d", .@wta, .@wtb, .@wtc, .@tries, .@amount, .@dbgamm;
+ if (.@wtc >= .@wtb)
+ .@tries=0;
+ else
+ .@tries-=1;
+ } while (.@tries > 0);
+ debugmes "Scattered "+.@dbgamm+" items on "+.@map$+". Currently scattered: "+.@wtc;
+ return;
+
+}
+
+// Here we begin
+- script Shovel -1,{
+ function CheckDigLocation;
+ function AddDigRect;
+ function PlayerIsTired;
+
+
+ function Dig {
+ // First check: Did some player bury some TREASURE? O.o
+ getmapxy(.@map$, .@x, .@y, 0);
+ for (.@i = 0; .@i < getarraysize($WorldBuriedTreasures_id); .@i++)
+ {
+ if (!strcmp($WorldBuriedTreasures_map$[.@i], .@map$) &&
+ $WorldBuriedTreasures_x[.@i] == .@x &&
+ $WorldBuriedTreasures_y[.@i] == .@y)
+ {
+ .@id = $WorldBuriedTreasures_id[.@i];
+ .@amount = $WorldBuriedTreasures_amount[.@i];
+
+ inventoryplace .@id, .@amount;
+
+ deletearray $WorldBuriedTreasures_id[.@i], 1;
+ deletearray $WorldBuriedTreasures_amount[.@i], 1;
+ deletearray $WorldBuriedTreasures_map$[.@i], 1;
+ deletearray $WorldBuriedTreasures_x[.@i], 1;
+ deletearray $WorldBuriedTreasures_y[.@i], 1;
+ getitem .@id, .@amount;
+ mesn strcharinfo(0);
+ mesc l("You found something!");
+ mesc l("It's @@ @@.", .@amount, getitemname(.@id));
+ next;
+ closeclientdialog;
+ return 1;
+ }
+ }
+ // Second check: Perhaps here is a rare, random ore?
+ freeloop(true);
+ for (.@i = 0; .@i < getarraysize($@WBT_Random_id); .@i++)
+ {
+ if (!strcmp($@WBT_Random_map$[.@i], .@map$) &&
+ $@WBT_Random_x[.@i] == .@x &&
+ $@WBT_Random_y[.@i] == .@y)
+ {
+ .@id = $@WBT_Random_id[.@i];
+ .@amount = $@WBT_Random_amount[.@i];
+
+ inventoryplace .@id, .@amount;
+
+ deletearray $@WBT_Random_id[.@i], 1;
+ deletearray $@WBT_Random_amount[.@i], 1;
+ deletearray $@WBT_Random_map$[.@i], 1;
+ deletearray $@WBT_Random_x[.@i], 1;
+ deletearray $@WBT_Random_y[.@i], 1;
+ getitem .@id, .@amount;
+ mesn strcharinfo(0);
+ mesc l("You found something!");
+ mesc l("It's @@ @@.", .@amount, getitemname(.@id));
+ next;
+ closeclientdialog;
+ return 1;
+ }
+ }
+ freeloop(false);
+ dispbottom l("Sadly, you found nothing but dirt.");
+ return 0;
+ }
+
+ function Bury {
+ narrator S_FIRST_BLANK_LINE | S_LAST_BLANK_LINE, l("What would you like to bury?");
+ .@items$ = "";
+
+ mes "##B" + l("Drag and drop an item from your inventory.") + "##b";
+
+ .@id = requestitem();
+
+ // If ID is invalid, there's not enough items, it is an Iron Shovel, it is bound = Cannot bury
+ // NOBODY bypass notrade check. (ITR_NONE is 0)
+ if (.@id < 1) close;
+ if (.@id < 1 || countitem(.@id) < 1 || .@id == IronShovel || .@id == SteelShovel || checkbound(.@id) ||
+ (!getiteminfo(.@id, ITEMINFO_TRADE))
+ ) {
+ @ShovelLastUsed = 0;
+ if (.@id == IronShovel || .@id == SteelShovel || checkbound(.@id))
+ mesc l("You cannot bury this item!");
+ else if (!getiteminfo(.@id, ITEMINFO_TRADE))
+ mesc l("This item is too precious, you cannot part with it!");
+ else
+ mesc l("You give up.");
+ close;
+ return;
+ }
+
+ .@amount = 1;
+ if (countitem(.@id) > 1) {
+ narrator S_FIRST_BLANK_LINE | S_LAST_BLANK_LINE, l("Amount?");
+ input .@amount, 0, countitem(.@id);
+ }
+ if (.@amount == 0 || .@amount > countitem(.@id)) {
+ close;
+ return;
+ }
+
+ getmapxy(.@map$, .@x, .@y, 0);
+ delitem .@id, .@amount;
+ .@wtc = getarraysize($WorldBuriedTreasures_id);
+ $WorldBuriedTreasures_id[.@wtc] = .@id;
+ $WorldBuriedTreasures_amount[.@wtc] = .@amount;
+ $WorldBuriedTreasures_map$[.@wtc] = .@map$;
+ $WorldBuriedTreasures_x[.@wtc] = .@x;
+ $WorldBuriedTreasures_y[.@wtc] = .@y;
+ narrator S_FIRST_BLANK_LINE, l("You buried @@ @@.", .@amount, getitemname(.@id));
+ close;
+ return 0;
+ }
+
+ // Scope: ShovelQuests_ → x, y, map, func{tion}
+ function ShovelQuests {
+ getmapxy(.@map$, .@x, .@y, 0);
+ for (.@i = 0; .@i < getarraysize(ShovelQuests_func$); .@i++)
+ {
+ if (!strcmp(ShovelQuests_map$[.@i], .@map$) &&
+ ShovelQuests_x[.@i] == .@x &&
+ ShovelQuests_y[.@i] == .@y)
+ {
+ .@func$ = ShovelQuests_func$[.@i];
+ deletearray ShovelQuests_func$[.@i], 1;
+ deletearray ShovelQuests_map$[.@i], 1;
+ deletearray ShovelQuests_x[.@i], 1;
+ deletearray ShovelQuests_y[.@i], 1;
+ callfunc(.@func$);
+ return 1;
+ }
+ }
+ return 0;
+ }
+
+OnDig:
+ if (!CheckDigLocation()) {
+ dispbottom l("I can't use the shovel here.");
+ end;
+ }
+ if (PlayerIsTired())
+ end;
+ if (!ShovelQuests())
+ Dig();
+ end;
+
+OnBury:
+ if (!CheckDigLocation()) {
+ dispbottom l("I can't use the shovel here.");
+ end;
+ }
+ if (PlayerIsTired())
+ end;
+ Bury();
+ end;
+
+function CheckDigLocation {
+ getmapxy(.@map$, .@x, .@y, 0);
+ for (.@i = 0; .@i < getarraysize(.WorldDigRect_Map$); .@i++)
+ {
+ if (!strcmp(.WorldDigRect_Map$[.@i], .@map$) &&
+ .WorldDigRect_x1[.@i] <= .@x &&
+ .WorldDigRect_x2[.@i] >= .@x &&
+ .WorldDigRect_y1[.@i] <= .@y &&
+ .WorldDigRect_y2[.@i] >= .@y)
+ {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+function AddDigRect {
+ if (getargcount() < 5) {
+ debugmes "usage: AddDigRect(map$,x1,y1,x2,y2)";
+ return 0;
+ }
+ .@map$ = str(getarg(0));
+ .@x1 = getarg(1);
+ .@y1 = getarg(2);
+ .@x2 = getarg(3);
+ .@y2 = getarg(4);
+ .@size = getarraysize(.WorldDigRect_Map$);
+ .WorldDigRect_Map$[.@size] = .@map$;
+ .WorldDigRect_x1[.@size] = .@x1;
+ .WorldDigRect_y1[.@size] = .@y1;
+ .WorldDigRect_x2[.@size] = .@x2;
+ .WorldDigRect_y2[.@size] = .@y2;
+ return 1;
+}
+
+function PlayerIsTired {
+ /*
+ // GMs can do this in an unrestricted way
+ if (is_gm())
+ return 0;
+ */
+ // I changed the rule to: No restriction on Aeros! (GM or not) (May be laggy)
+ if (getmap() == "001-1")
+ return 0;
+
+ .@tick = gettimetick(2);
+ .@playertick = .PlayerTiredTime - (readparam(bStr)/5) - (readparam(bVit)/10);
+
+ // Dig and Bury more with Steel Shovel
+ if (countitem(SteelShovel)) {
+ .@playertick-=(.PlayerTiredTime*2)/3;
+ }
+
+ if (@ShovelLastUsed + max(5, .@playertick) > .@tick) {
+ .@time$=FuzzyTime(@ShovelLastUsed + max(5, .@playertick));
+ dispbottom lg("You are exhausted, you should rest @@.", "You are exhausted, you should rest @@.", .@time$);
+ return 1;
+ }
+ @ShovelLastUsed = .@tick;
+ return 0;
+}
+
+OnInit:
+ // Define constants
+ .PlayerTiredTime = 25;
+
+ // You can bury & dig on all mines
+ AddDigRect("007-1", 20, 20, 180, 190);
+ AddDigRect("011-1", 20, 20, 180, 180);
+ AddDigRect("015-1", 20, 20, 180, 180);
+ AddDigRect("032-2", 20, 20, 180, 180);
+
+ // Aeros can be used too (for events)
+ AddDigRect("001-1", 20, 20, 342, 158);
+
+ // LoF Areas
+ AddDigRect("018-1", 20, 20, 80, 80);
+
+OnHour00:
+ // Clear random treasure
+ deletearray $@WBT_Random_id;
+ deletearray $@WBT_Random_amount;
+ deletearray $@WBT_Random_map$;
+ deletearray $@WBT_Random_x;
+ deletearray $@WBT_Random_y;
+
+
+ // Scatter Treasure.
+ // There are 25600 possible cells, and about 60% of them are collisions.
+ // As we don't prevent treasure from falling on collision, it is pretty high.
+ // If two treasures fall on same place, the previous treasure will be ignored.
+ // Theoretical chance of uncovering a treasure on an attempt is 0.12% to 0.70%
+ // 2019-05-27: Doubled ammount of treasures. New rates should be 0.24% ~ 1.40%
+ shovel_scatter("007-1", 20, 20, 180, 180, rand(60,360),
+ TreasureKey,CoinBag,TreasureKey,SulfurPowder,Coal,EarthPowder,
+ IronOre,CopperOre,LeadOre,TinOre,SilverOre,GoldOre,PlatinumOre,IridiumOre,TitaniumOre,
+ Diamond,Ruby,Emerald,Sapphire,Topaz,Amethyst,
+ CursedAmmoBox,ThornAmmoBox,SacredBullet);
+ shovel_scatter("011-1", 20, 20, 180, 180, rand(60,360),
+ TreasureKey,CoinBag,TreasureKey,SulfurPowder,Coal,EarthPowder,
+ IronOre,CopperOre,LeadOre,TinOre,SilverOre,GoldOre,PlatinumOre,IridiumOre,TitaniumOre,
+ Diamond,Ruby,Emerald,Sapphire,Topaz,Amethyst,
+ CursedAmmoBox,ThornAmmoBox,SacredBullet);
+ shovel_scatter("015-1", 20, 20, 180, 180, rand(60,360),
+ TreasureKey,CoinBag,TreasureKey,SulfurPowder,Coal,EarthPowder,
+ IronOre,CopperOre,LeadOre,TinOre,SilverOre,GoldOre,PlatinumOre,IridiumOre,TitaniumOre,
+ Diamond,Ruby,Emerald,Sapphire,Topaz,Amethyst,
+ CursedAmmoBox,ThornAmmoBox,SacredBullet);
+
+ // Extra burried treasure (25~65 over 3600 tiles: aprox. 0.70% to 1.80%)
+ // New Rate: 1.40% ~ 3.60% since 2019-05-27
+ shovel_scatter("018-1", 20, 20, 80, 80, rand(50,130),
+ TreasureKey,CoinBag,TreasureKey,SulfurPowder,Coal,EarthPowder,
+ IronOre,CopperOre,LeadOre,TinOre,SilverOre,GoldOre,PlatinumOre,IridiumOre,TitaniumOre,
+ Diamond,Ruby,Emerald,Sapphire,Topaz,Amethyst,
+ CursedAmmoBox,ThornAmmoBox,SacredBullet);
+
+ // Aeros can't be forgotten, but only during Thanksgiving
+ if ($EVENT$ == "Thanksgiving") {
+ shovel_scatter("001-1", 171, 20, 340, 160, rand(160,360),
+ TreasureKey,CoinBag,TreasureKey,SulfurPowder,Coal,EarthPowder,
+ IronOre,CopperOre,LeadOre,TinOre,SilverOre,GoldOre,PlatinumOre,IridiumOre,TitaniumOre,
+ Diamond,Ruby,Emerald,Sapphire,Topaz,Amethyst);
+ shovel_scatter("001-1", 20, 20, 140, 140, rand(160,360),
+ TreasureKey,CoinBag,TreasureKey,SulfurPowder,Coal,EarthPowder,
+ IronOre,CopperOre,LeadOre,TinOre,SilverOre,GoldOre,PlatinumOre,IridiumOre,TitaniumOre,
+ Diamond,Ruby,Emerald,Sapphire,Topaz,Amethyst);
+ }
+ end;
+
+}
+
+function script shovel_addquest {
+ if (getargcount() < 4)
+ {
+ debugmes "usage: shovel_addquest(map$,x,y,func$)";
+ return 0;
+ }
+ .@map$ = str(getarg(0));
+ .@x = getarg(1);
+ .@y = getarg(2);
+ .@func$ = str(getarg(3));
+ .@size = getarraysize(ShovelQuests_func$);
+ ShovelQuests_func$[.@size] = .@func$;
+ ShovelQuests_map$[.@size] = .@map$;
+ ShovelQuests_x[.@size] = .@x;
+ ShovelQuests_y[.@size] = .@y;
+ return 1;
+}
+
+function script shovel_adddigrect {
+ if (getargcount() < 5)
+ {
+ debugmes "usage: shovel_adddigrect(map$,x1,y1,x2,y2)";
+ return 0;
+ }
+ .@map$ = str(getarg(0));
+ .@x1 = getarg(1);
+ .@y1 = getarg(2);
+ .@x2 = getarg(3);
+ .@y2 = getarg(4);
+ .@size = getarraysize(getvariableofnpc(.WorldDigRect_Map$, strnpcinfo(3)));
+ set getvariableofnpc(.WorldDigRect_Map$[.@size], strnpcinfo(3)), .@map$;
+ set getvariableofnpc(.WorldDigRect_x1[.@size], strnpcinfo(3)), .@x1;
+ set getvariableofnpc(.WorldDigRect_y1[.@size], strnpcinfo(3)), .@y1;
+ set getvariableofnpc(.WorldDigRect_x2[.@size], strnpcinfo(3)), .@x2;
+ set getvariableofnpc(.WorldDigRect_y2[.@size], strnpcinfo(3)), .@y2;
+ return 1;
+}
+
+function script shovel_getcity {
+ .@a$=getarg(0);
+
+ // else is not required (return prevails)
+ if (.@a$ == "007-1")
+ return l("Tulimshar Mines");
+ if (.@a$ == "011-1")
+ return l("Halinarzo Mines");
+ if (.@a$ == "015-1")
+ return l("Hurnscald Mines");
+ if (.@a$ == "018-1")
+ return l("Sincerity Island");
+ if (.@a$ == "032-2")
+ return l("Tree Maze (Past Tulimshar)");
+
+ return .@a$;
+}
+
+// [Treasure Map] functions
+
+function script shovel_randomtreasure {
+ .@id=any(TreasureKey,CoinBag,TreasureKey,CoinBag,CoinBag,GoldPieces,
+ Diamond,Ruby,Emerald,Sapphire,Topaz,Amethyst,
+ StrangeCoin, CasinoCoins, MercBoxA, AncientBlueprint);
+ // Legacy Tulimshar has different items
+ if (getmap() == "032-2") .@id = any(CoinBag, TreasureKey, GoldPieces,
+ Diamond,Ruby,Emerald,
+ Sapphire,Topaz,Amethyst,
+ StrangeCoin, MercBoxB, AncientBlueprint,
+ TulimWarpCrystal, AlchemyBlueprintC,
+ EarthPowder, MercBoxC, StrangeCoin);
+ if (rand2(400) == 136) .@id = EarthPowder; // 0.25% chance
+ if (rand2(2000) == 337) .@id = PirateBandana; // 0.05% chance
+ delitem TreasureMap, 1;
+ .@amount=any(1,1,2)+(getmap() == "032-2" ? 1 : 0);
+ if (.@id == TreasureKey || .@id == CoinBag || .@id == StrangeCoin)
+ .@amount+=any(0,1,0,1,2);
+ if (.@id == CasinoCoins)
+ .@amount+=rand2(0,8);
+ if (.@id == MercBoxA || .@id == AncientBlueprint ||
+ .@id == GoldPieces || .@id == EarthPowder ||
+ .@id == PirateBandana || .@id == MercBoxB ||
+ .@id == MercBoxC || .@id == TulimWarpCrystal ||
+ .@id == AlchemyBlueprintC)
+ .@amount=1;
+ getitem .@id, .@amount;
+ ShovelQuests_AssignedMAP$="";
+ ShovelQuests_AssignedX=0;
+ ShovelQuests_AssignedY=0;
+
+ mesn strcharinfo(0);
+ mesc l("You found something!");
+ mesc l("It's @@ @@.", .@amount, getitemname(.@id));
+ next;
+ closeclientdialog;
+ return;
+}
+
+function script shovel_genrandtreasure {
+ if (getq(LoFQuest_EPISODE) >= 16)
+ .@m$=any("007-1", "011-1", "015-1", "018-1", "032-2",
+ "007-1", "011-1", "015-1", "018-1");
+ else
+ .@m$=any("007-1", "011-1", "015-1", "018-1");
+ // Dangerous
+ do {
+ .@x=rand2(20, getmapinfo(MAPINFO_SIZE_X, .@m$)-20);
+ .@y=rand2(20, getmapinfo(MAPINFO_SIZE_Y, .@m$)-20);
+ } while (!checkcell(.@m$, .@x, .@y, cell_chkpass));
+
+ // Success
+ if (checkcell(.@m$, .@x, .@y, cell_chkpass)) {
+ shovel_addquest(.@m$, .@x, .@y, "shovel_randomtreasure");
+ ShovelQuests_AssignedMAP$=shovel_getcity(.@m$);
+ ShovelQuests_AssignedX=.@x;
+ ShovelQuests_AssignedY=.@y;
+ }
+ return;
+}
+
+// [Dungeon Map] functions
+
+function script shovel_randomdungeon {
+ // If you don't have the map, stop
+ delitem DungeonMap, 1;
+
+ // Determine the dungeon size
+ if (ShovelQuests_DungeonMAP$ == "032-2" && REBIRTH)
+ .@mz = (MAZE_SIZE_X | MAZE_SIZE_A);
+ else if (ShovelQuests_DungeonMAP$ == "032-2")
+ .@mz = (MAZE_SIZE_G | MAZE_SIZE_X);
+ else if (REBIRTH)
+ .@mz = (MAZE_SIZE_M | MAZE_SIZE_G);
+ else
+ .@mz = (MAZE_SIZE_S | MAZE_SIZE_M);
+
+ // Create the Maze
+ CreateMaze(IOT_CHAR, .@mz);
+ MazeMobs((BaseLevel*8/10), false, rand2(20, 24));
+
+ // Prepare the chest array
+ if (.@mz & MAZE_SIZE_A)
+ array_push(.@mob, SupremeChest);
+ if (.@mz & MAZE_SIZE_X)
+ array_push(.@mob, PrismChest);
+ if (.@mz & MAZE_SIZE_G)
+ array_push(.@mob, GoldenChest);
+ if (.@mz & MAZE_SIZE_M)
+ array_push(.@mob, SilverChest);
+ if (.@mz & MAZE_SIZE_S)
+ array_push(.@mob, BronzeChest);
+
+ // Spawn & Configure the boss monster
+ .@mx=getmapinfo(MAPINFO_SIZE_X, MAZE_MAP$)-20;
+ .@my=getmapinfo(MAPINFO_SIZE_Y, MAZE_MAP$)-20;
+ .@mob=areamonster(MAZE_MAP$, .@mx/2, .@my/2, .@mx, .@my, "Treasure Chest", any_of(.@mob), 1, "Emergency Exit::OnTalk");
+
+ InitMaze(4800, true, true); // 1h30, random start, with treasure chest
+
+ // Cleanup old variables and exit
+ ShovelQuests_DungeonMAP$="";
+ ShovelQuests_DungeonX=0;
+ ShovelQuests_DungeonY=0;
+ dispbottom l("Now, to find the treasure chest!");
+ closeclientdialog;
+ return;
+}
+
+function script shovel_genranddungeon {
+ if (getq(LoFQuest_EPISODE) >= 16)
+ .@m$=any("007-1", "011-1", "015-1", "018-1", "032-2",
+ "007-1", "011-1", "015-1", "018-1");
+ else
+ .@m$=any("007-1", "011-1", "015-1", "018-1");
+ // Dangerous
+ do {
+ .@x=rand2(20, getmapinfo(MAPINFO_SIZE_X, .@m$)-20);
+ .@y=rand2(20, getmapinfo(MAPINFO_SIZE_Y, .@m$)-20);
+ } while (!checkcell(.@m$, .@x, .@y, cell_chkpass));
+
+ // Success
+ if (checkcell(.@m$, .@x, .@y, cell_chkpass)) {
+ shovel_addquest(.@m$, .@x, .@y, "shovel_randomdungeon");
+ ShovelQuests_DungeonMAP$=shovel_getcity(.@m$);
+ ShovelQuests_DungeonX=.@x;
+ ShovelQuests_DungeonY=.@y;
+ }
+ return;
+}
+
diff --git a/npc/items/teleporter.txt b/npc/items/teleporter.txt
new file mode 100644
index 0000000..b7e311b
--- /dev/null
+++ b/npc/items/teleporter.txt
@@ -0,0 +1,54 @@
+// TMW2 scripts.
+// Authors:
+// Pyndragon
+// Jesusalva
+// Description:
+// Warp Crystal
+
+- script Warp Crystal NPC_HIDDEN,{
+ close;
+
+function Cooldown {
+ mesn;
+ mesc l("Successive warps cause time-space distortions and are thus not allowed.");
+ mesc l("You can use it again in %s.", FuzzyTime(TELEPORTER_TIME));
+ getitem @itemid, 1;
+ close;
+}
+
+function ReturnItem {
+ getitem @itemid, 1;
+ end;
+}
+
+OnUse:
+ // Receives @dest$
+ if (TELEPORTER_TIME > gettimetick(2))
+ Cooldown();
+ if (BaseLevel < 20) {
+ dispbottom l("The might contained in this curious object is too powerful. You have to be at least level 20 to harness it.");
+ ReturnItem();
+ }
+ // TODO: Are you already at target point?
+
+ // The chance to break is always at least 20%
+ // Begins at 100% and each second will subtract 0.01%
+ // It will never go below 15%, which happens after x seconds
+ .@timet=limit(0, gettimetick(2)-TELEPORTER_TIME, 3600);
+ .@prop=.@timet*2777/1000; // Make it range from 0~10000
+ .@adj_breakrate=limit(1500, .@prop, 9500 );
+ //debugmes "Adjusted break ratio: %d", .@adj_breakrate;
+ if (rand(0,10000) > .@adj_breakrate)
+ getitem @itemid, 1;
+ else
+ getitem BrokenWarpCrystal, 1;
+
+ // Apply Cooldown, same variable as LoF Teleporter, cancel ship travels
+ TELEPORTER_TIME=gettimetick(2)+300;
+ @timer_navio_running=0;
+
+ // Save new location and warp you there
+ EnterTown(@dest$);
+ ReturnTown();
+ end;
+}
diff --git a/npc/items/valentine.txt b/npc/items/valentine.txt
new file mode 100644
index 0000000..79a0a96
--- /dev/null
+++ b/npc/items/valentine.txt
@@ -0,0 +1,31 @@
+// TMW-2 script.
+// Author:
+// Jesusalva
+// Description:
+// Valentine eating
+
+// Eats chocolate, returns bad ones if requested
+// EatValentineChocolate()
+function script EatValentineChocolate {
+ if ($EVENT$ != "Valentine") {
+ dispbottom l("Past due date.");
+ } else if (#VALENTINE_OPENED >= #VALENTINE_RECEIVED) {
+ dispbottom l("This is not meant for me.");
+ getitem BoxOfChocolates, 1;
+ } else {
+ .@pts=1;
+ if (GSET_VALENTINE_EATALL) {
+ .@pts=(#VALENTINE_RECEIVED-#VALENTINE_OPENED);
+ delitem BoxOfChocolates, .@pts-1;
+ }
+ // I know technically this is wrong grammar, but I want fixed width
+ #VALENTINE_OPENED=#VALENTINE_OPENED+.@pts;
+ #VALENTINE_POINTS=#VALENTINE_POINTS+.@pts;
+ if (rand2(0,1))
+ dispbottom l("It's tasty ^.^");
+ else
+ dispbottom l("It's yummy ^.^");
+ }
+ return;
+}
+