summaryrefslogtreecommitdiff
path: root/npc/functions
diff options
context:
space:
mode:
Diffstat (limited to 'npc/functions')
-rw-r--r--npc/functions/RNGesus.txt87
-rw-r--r--npc/functions/afk.txt69
-rw-r--r--npc/functions/array.txt445
-rw-r--r--npc/functions/asklanguage.txt56
-rw-r--r--npc/functions/asleep.txt19
-rw-r--r--npc/functions/bank.txt297
-rw-r--r--npc/functions/barber.txt220
-rw-r--r--npc/functions/beds.txt22
-rw-r--r--npc/functions/bitwise.txt66
-rw-r--r--npc/functions/bodytype.txt19
-rw-r--r--npc/functions/casino.txt254
-rw-r--r--npc/functions/clientversion.txt20
-rw-r--r--npc/functions/confused-tree-dict.txt515
-rw-r--r--npc/functions/crafting.txt119
-rw-r--r--npc/functions/daily.txt154
-rw-r--r--npc/functions/doors.txt65
-rw-r--r--npc/functions/event-listeners.txt169
-rw-r--r--npc/functions/faction.txt104
-rw-r--r--npc/functions/fishing.txt401
-rw-r--r--npc/functions/game-rules.txt66
-rw-r--r--npc/functions/generic-text.txt120
-rw-r--r--npc/functions/global_event_handler.txt73
-rw-r--r--npc/functions/goodbye.txt28
-rw-r--r--npc/functions/hammocks.txt50
-rw-r--r--npc/functions/harbours.txt39
-rw-r--r--npc/functions/hello.txt23
-rw-r--r--npc/functions/input.txt66
-rw-r--r--npc/functions/inventoryplace.txt37
-rw-r--r--npc/functions/legacy.txt238
-rw-r--r--npc/functions/legiontalk.txt66
-rw-r--r--npc/functions/libquest.txt104
-rw-r--r--npc/functions/lockpicks.txt193
-rw-r--r--npc/functions/main.txt445
-rw-r--r--npc/functions/manhole.txt73
-rw-r--r--npc/functions/masks.txt43
-rw-r--r--npc/functions/math.txt50
-rw-r--r--npc/functions/mouboofunc.txt89
-rw-r--r--npc/functions/music.txt87
-rw-r--r--npc/functions/npcmove.txt142
-rw-r--r--npc/functions/npcmovegraph.txt485
-rw-r--r--npc/functions/openbook.txt34
-rw-r--r--npc/functions/permissions.txt35
-rw-r--r--npc/functions/player-cache.txt501
-rw-r--r--npc/functions/quest-debug/000-ShipQuests_Julia.txt37
-rw-r--r--npc/functions/quest-debug/001-ShipQuests_Arpan.txt27
-rw-r--r--npc/functions/quest-debug/002-ShipQuests_Alige.txt27
-rw-r--r--npc/functions/quest-debug/003-ShipQuests_Peter.txt36
-rw-r--r--npc/functions/quest-debug/004-ShipQuests_Nard.txt38
-rw-r--r--npc/functions/quest-debug/005-ShipQuests_Knife.txt25
-rw-r--r--npc/functions/quest-debug/006-ShipQuests_ArpanMoney.txt27
-rw-r--r--npc/functions/quest-debug/007-ShipQuests_Door.txt25
-rw-r--r--npc/functions/quest-debug/008-ShipQuests_Couwan.txt26
-rw-r--r--npc/functions/quest-debug/009-ShipQuests_TreasureChest.txt25
-rw-r--r--npc/functions/quest-debug/010-ShipQuests_Ale.txt25
-rw-r--r--npc/functions/quest-debug/011-ShipQuests_Astapolos.txt25
-rw-r--r--npc/functions/quest-debug/012-ShipQuests_Gulukan.txt25
-rw-r--r--npc/functions/quest-debug/013-ShipQuests_Jalad.txt25
-rw-r--r--npc/functions/quest-debug/014-ShipQuests_QMuller.txt25
-rw-r--r--npc/functions/quest-debug/015-ShipQuests_Tibbo.txt25
-rw-r--r--npc/functions/quest-debug/016-ShipQuests_Gugli.txt48
-rw-r--r--npc/functions/quest-debug/017-ShipQuests_ChefGado.txt30
-rw-r--r--npc/functions/quest-debug/018-General_Cookies.txt25
-rw-r--r--npc/functions/quest-debug/020-ArtisQuests_LazyBrother.txt28
-rw-r--r--npc/functions/quest-debug/021-ArtisQuests_Urchin.txt26
-rw-r--r--npc/functions/quest-debug/022-ArtisQuests_CatchPiou.txt26
-rw-r--r--npc/functions/quest-debug/023-ArtisQuests_Fishman.txt26
-rw-r--r--npc/functions/quest-debug/024-ArtisQuests_QOnan.txt27
-rw-r--r--npc/functions/quest-debug/026-General_Rumly.txt27
-rw-r--r--npc/functions/quest-debug/027-ArtisQuests_Enora.txt49
-rw-r--r--npc/functions/quest-debug/029-ArtisQuests_Fexil.txt27
-rw-r--r--npc/functions/quest-debug/030-ArtisQuests_Lloyd.txt25
-rw-r--r--npc/functions/quest-debug/031-General_Janus.txt31
-rw-r--r--npc/functions/quest-debug/032-ArtisQuests_MonaDad.txt26
-rw-r--r--npc/functions/quest-debug/033-Artis_Legion_Progress.txt31
-rw-r--r--npc/functions/quest-debug/034-ArtisQuests_TrainingLegion.txt28
-rw-r--r--npc/functions/quest-debug/035-ThiefQuests_Artis.txt38
-rw-r--r--npc/functions/quest-debug/050-HurnscaldQuests_Hinnak.txt32
-rw-r--r--npc/functions/quest-debug/051-HurnscaldQuests_Soup.txt32
-rw-r--r--npc/functions/quest-debug/052-HurnscaldQuests_Inspector.txt41
-rw-r--r--npc/functions/quest-debug/053-HurnscaldQuests_ForestBow.txt42
-rw-r--r--npc/functions/quest-debug/054-HurnscaldQuests_WoodenShield.txt37
-rw-r--r--npc/functions/quest-debug/055-General_Cooking.txt92
-rw-r--r--npc/functions/quest-debug/056-General_Brotherhood.txt27
-rw-r--r--npc/functions/quest-debug/057-HurnscaldQuests_Kfahr.txt31
-rw-r--r--npc/functions/quest-debug/058-ArgaesQuest_Galimatia.txt32
-rw-r--r--npc/functions/quest-debug/059-HurnscaldQuests_Rossy.txt43
-rw-r--r--npc/functions/quest-debug/061-General_Hal.txt30
-rw-r--r--npc/functions/quest-debug/100-General_Narrator.txt27
-rw-r--r--npc/functions/quest-debug/functions.txt109
-rw-r--r--npc/functions/questgen.txt38
-rw-r--r--npc/functions/referral.txt43
-rw-r--r--npc/functions/refine.txt66
-rw-r--r--npc/functions/resetstatus.txt117
-rw-r--r--npc/functions/riddle.txt74
-rw-r--r--npc/functions/sailordialogue.txt53
-rw-r--r--npc/functions/sailortalk.txt37
-rw-r--r--npc/functions/savepoint.txt57
-rw-r--r--npc/functions/scoreboards.txt291
-rw-r--r--npc/functions/shops.txt18
-rw-r--r--npc/functions/skills.txt20
-rw-r--r--npc/functions/soul-menhir.txt75
-rw-r--r--npc/functions/spotlight.txt92
-rw-r--r--npc/functions/string.txt196
-rw-r--r--npc/functions/time.txt108
-rw-r--r--npc/functions/timer.txt63
-rw-r--r--npc/functions/treasure.txt134
-rw-r--r--npc/functions/util.txt102
-rw-r--r--npc/functions/vault.txt98
-rw-r--r--npc/functions/villagertalk.txt53
-rw-r--r--npc/functions/warp.txt58
110 files changed, 9517 insertions, 0 deletions
diff --git a/npc/functions/RNGesus.txt b/npc/functions/RNGesus.txt
new file mode 100644
index 00000000..7224977c
--- /dev/null
+++ b/npc/functions/RNGesus.txt
@@ -0,0 +1,87 @@
+// Evol functions.
+// Authors:
+// gumi
+// jesusalva
+// Description:
+// Randomization helper functions.
+
+// pseudo-fix randomness
+// rand2( min, max )
+function script rand2 {
+ if (getargcount() == 2) {
+ .@min=getarg(0)*100;
+ .@max=getarg(1)*100+99;
+ } else {
+ .@min=0;
+ .@max=getarg(0)*100-1;
+ }
+ return rand(.@min, .@max)/100;
+}
+
+
+
+// any(<arg>{, ...<arg>})
+// returns one argument randomly
+
+function script any {
+ return getarg(rand2(getargcount()));
+}
+
+
+
+// any_of(<array>)
+// returns any member of the array
+
+function script any_of {
+ return getelementofarray(getarg(0), getarrayindex(getarg(0)) + rand2(getarraysize(getarg(0)) - getarrayindex(getarg(0))));
+}
+
+
+
+// relative_array_random(<array: 0, {[value, probability]..}>)
+// returns a random entry from the array, by relative probability
+// the first key of the array should be 0 and every entries are a tuple
+// of [value, probability]
+
+function script relative_array_random {
+ .@is_str = getdatatype(getarg(0)) & DATATYPE_STR;
+ .@total_prob = getelementofarray(getarg(0), 0);
+ .@initial_index = getarrayindex(getarg(0));
+ .@initial_index = .@initial_index ? .@initial_index : 1;
+ freeloop(true);
+
+ if (.@total_prob < 1 || getarg(1, false))
+ {
+ // first calculation, or forced re-calculation
+ .@total_prob = 0;
+ .@size = getarraysize(getarg(0));
+
+ for (.@i = .@initial_index + 1; .@i < .@size; .@i += 2) {
+ if (.@is_str) {
+ .@total_prob += max(1, atoi(getelementofarray(getarg(0), .@i)));
+ } else {
+ .@total_prob += max(1, getelementofarray(getarg(0), .@i));
+ }
+ }
+
+ // we cache on the first key
+ set(getelementofarray(getarg(0), 0), .@total_prob);
+ }
+
+ .@target_sum = rand2(0, .@total_prob);
+
+ for (.@i = .@initial_index; .@sum < .@target_sum; .@i += 2) {
+ if (.@is_str) {
+ .@sum += atoi(getelementofarray(getarg(0), .@i + 1));
+ } else {
+ .@sum += getelementofarray(getarg(0), .@i + 1);
+ }
+
+ if (.@sum >= .@target_sum) {
+ break;
+ }
+ }
+
+ freeloop(false);
+ return getelementofarray(getarg(0), .@i);
+}
diff --git a/npc/functions/afk.txt b/npc/functions/afk.txt
new file mode 100644
index 00000000..9790ea1a
--- /dev/null
+++ b/npc/functions/afk.txt
@@ -0,0 +1,69 @@
+// Moubootaur Legends Script
+// Author:
+// Jesusalva
+// Hocus Pocus Fidibus
+// Micksha
+// Description:
+// Professor - allows you to gain EXP for idling (Speech skill)
+// Modified for The Mana World. Please update the bottom of this file always.
+
+// AFKLoop(label, map, x1, y1, x2, y2)
+function script AFKLoop {
+ deltimer(getarg(0)); // safeguard
+ .@m$=getarg(1);
+ .@x1=getarg(2);
+ .@y1=getarg(3);
+ .@x2=getarg(4);
+ .@y2=getarg(5);
+
+ // Limit maximum AFK TIme using the same rule as Moubootaur Legends
+ // Max AFK time is determined as 30 minutes + 1 second every 10 minutes AFKed
+ // Capped at 2 hours (you've AFK'ed 37 days and 12 hours - 900 hours)
+ .@maxafk=min(7200, 1800+(AFKING/600));
+
+ // Check if this timer is still relevant
+ if (getmap() != .@m$)
+ return false;
+
+ addtimer2(3000, getarg(0));
+
+ // To receive AFK experience, you must be sitting at the table...
+ if (!isin(.@m$, .@x1, .@y1, .@x2, .@y2))
+ return false;
+
+ // ...And be idle for less than .@maxafk
+ if (checkidle() > .@maxafk)
+ return false;
+
+ // Calculate amount of EXP based on players in the square
+ // Every 2 players grant you 1 XP, capped at 10 xp
+ // For job experience, it is twice as difficult.
+ .@ppl=getareausers(.@m$, .@x1, .@y1, .@x2, .@y2);
+ .@bxp=cap_value(1+.@ppl/2 , 1, 10);
+ .@jxp=cap_value(1+.@ppl/4 , 1, 5);
+
+ // Beer and Red Plush Wine modifiers
+ if (getstatus(SC_CONFUSION)) {
+ .@bxp+=1;
+ //.@jxp+=1;
+ } else {
+ .@bxp=1;
+ .@jxp=1;
+ }
+
+ // Increase time AFK'ed timers and grant experience
+ AFKING+=1;
+ getexp .@bxp, .@jxp;
+ return true;
+}
+
+// Record memory for player login, re-firing timers when player is in a bar.
+function script AFKLogin {
+ .@m$=getmap();
+ if (.@m$ == "008-2-2")
+ addtimer2(3000, "#AFKHurns::OnSpeeching");
+ else if (.@m$ == "001-2-28")
+ addtimer2(3000, "#AFKArtis::OnSpeeching");
+ return;
+}
+
diff --git a/npc/functions/array.txt b/npc/functions/array.txt
new file mode 100644
index 00000000..6093aa32
--- /dev/null
+++ b/npc/functions/array.txt
@@ -0,0 +1,445 @@
+// array_pad(<array>, <size>, <value>)
+// prepend or append <value> until the array is of <size> size
+// returns the amount added on success, or false (0) if nothing changed
+
+function script array_pad {
+ .@index = getarrayindex(getarg(0)); // passed index
+ .@count = getarraysize(getarg(0)) - .@index; // actual size
+ .@size = getarg(1); // desired size
+ .@absolute = (.@size >= 0 ? .@size : -(.@size)); // |size|
+ .@delta = .@absolute - .@count; // amount to fill
+
+ if (.@absolute <= .@count) {
+ return false; // nothing to do
+ }
+
+ if (.@size < 0) {
+ copyarray(getelementofarray(getarg(0), .@index + .@delta), getarg(0), .@count); // shift to the right
+ cleararray(getarg(0), getarg(2), .@delta); // prepend
+ } else {
+ cleararray(getelementofarray(getarg(0), .@index + .@count), getarg(2), .@delta); // append
+ }
+
+ return .@delta;
+}
+
+
+
+// array_replace(<array>, <needle>, <replace>{, <neq>})
+// replace every occurence of <needle> with <replace>
+// returns the number of replaced elements
+
+function script array_replace {
+ .@size = getarraysize(getarg(0));
+ .@neq = getarg(3, false);
+ freeloop(true);
+
+ for (.@i = getarrayindex(getarg(0)); .@i < .@size; ++.@i) {
+ if ((.@neq && (getelementofarray(getarg(0), .@i) != getarg(1))) ||
+ (!(.@neq) && (getelementofarray(getarg(0), .@i) == getarg(1)))) {
+ set(getelementofarray(getarg(0), .@i), getarg(2));
+ ++.@count;
+ }
+ }
+
+ freeloop(false);
+ return .@count;
+}
+
+
+
+// array_find(<array>, <needle>{, <neq>})
+// return the index of the first occurence of <needle> in <array>
+// if not found it returns -1
+
+function script array_find {
+ .@size = getarraysize(getarg(0));
+ .@neq = getarg(2, false);
+ freeloop(true);
+
+ for (.@i = getarrayindex(getarg(0)); .@i < .@size; ++.@i) {
+ if ((.@neq && (getelementofarray(getarg(0), .@i) != getarg(1))) ||
+ (!(.@neq) && (getelementofarray(getarg(0), .@i) == getarg(1)))) {
+ freeloop(false);
+ return .@i;
+ }
+ }
+
+ freeloop(false);
+ return -1;
+}
+
+
+
+// array_rfind(<array>, <needle>{, <neq>})
+// return the index of the last occurence of <needle> in <array>
+// if not found it returns -1
+
+function script array_rfind {
+ .@min = getarrayindex(getarg(0));
+ .@neq = getarg(2, false);
+ freeloop(true);
+
+ for (.@i = (getarraysize(getarg(0)) - 1); .@i >= .@min; --.@i) {
+ if ((.@neq && (getelementofarray(getarg(0), .@i) != getarg(1))) ||
+ (!(.@neq) && (getelementofarray(getarg(0), .@i) == getarg(1)))) {
+ freeloop(false);
+ return .@i;
+ }
+ }
+
+ freeloop(false);
+ return -1;
+}
+
+
+
+// array_exists(<array>, <needle>{, <neq>})
+// return true or false accordingly if <needle> is found in <array>
+
+function script array_exists {
+ return array_find(getarg(0), getarg(1), getarg(2, false)) > -1;
+}
+
+
+
+// array_count(<array>, <needle>{, <neq>})
+// counts the number of occurrence of <needle> in the <array>
+
+function script array_count {
+ .@size = getarraysize(getarg(0));
+ .@neq = getarg(2, false);
+ freeloop(true);
+
+ for (.@i = getarrayindex(getarg(0)); .@i < .@size; ++.@i) {
+ if ((.@neq && (getelementofarray(getarg(0), .@i) != getarg(1))) ||
+ (!(.@neq) && (getelementofarray(getarg(0), .@i) == getarg(1)))) {
+ ++.@count;
+ }
+ }
+
+ freeloop(false);
+ return .@count;
+}
+
+
+
+// array_entries(<array>)
+// returns the number of non-empty entries
+
+function script array_entries {
+ if (isstr(getarg(0)) == 1) {
+ return array_count(getarg(0), "", true);
+ }
+ return array_count(getarg(0), 0, true);
+}
+
+
+
+// array_remove(<array>, <needle>{, <neq>})
+// removes every occurrence of <needle> in the <array> while shifting left
+
+function script array_remove {
+ .@size = getarraysize(getarg(0));
+ .@neq = getarg(2, false);
+ freeloop(true);
+
+ for (.@i = getarrayindex(getarg(0)); .@i < .@size; ++.@i) {
+ if ((.@neq && (getelementofarray(getarg(0), .@i) != getarg(1))) ||
+ (!(.@neq) && (getelementofarray(getarg(0), .@i) == getarg(1)))) {
+ deletearray(getelementofarray(getarg(0), .@i), 1); // shift left
+ ++.@count; // increase the counter
+ --.@size; // reduce the size
+ --.@i; // step back
+ }
+ }
+
+ freeloop(false);
+ return .@count;
+}
+
+
+
+// array_reverse(<array>)
+// reverses the array
+
+function script array_reverse {
+ .@index = getarrayindex(getarg(0));
+ .@size = getarraysize(getarg(0));
+ freeloop(true);
+
+ for (.@i = .@index; .@i < ((.@size + .@index) / 2); ++.@i) {
+ swap(getelementofarray(getarg(0), .@i), getelementofarray(getarg(0), .@size + .@index - 1 - .@i)); // a <> b
+ }
+
+ freeloop(false);
+ return true;
+}
+
+
+
+// array_sum(<array>)
+// return the sum of every element of the array
+
+function script array_sum {
+ .@size = getarraysize(getarg(0));
+ freeloop(true);
+
+ for (.@i = getarrayindex(getarg(0)); .@i < .@size; ++.@i) {
+ .@sum += getelementofarray(getarg(0), .@i);
+ }
+
+ freeloop(false);
+ return .@sum;
+}
+
+
+
+// array_difference(<array>)
+// return the difference of every element of the array
+
+function script array_difference {
+ .@size = getarraysize(getarg(0));
+ freeloop(true);
+
+ for (.@i = getarrayindex(getarg(0)); .@i < .@size; ++.@i) {
+ .@diff -= getelementofarray(getarg(0), .@i);
+ }
+
+ freeloop(false);
+ return .@diff;
+}
+
+
+
+// array_product(<array>)
+// return the product of every element of the array
+
+function script array_product {
+ .@size = getarraysize(getarg(0));
+ freeloop(true);
+
+ for (.@i = getarrayindex(getarg(0)); .@i < .@size; ++.@i) {
+ .@prod *= getelementofarray(getarg(0), .@i);
+ }
+
+ freeloop(false);
+ return .@prod;
+}
+
+
+
+// array_quotient(<array>)
+// return the product of every element of the array
+
+function script array_quotient {
+ .@size = getarraysize(getarg(0));
+ freeloop(true);
+
+ for (.@i = getarrayindex(getarg(0)); .@i < .@size; ++.@i) {
+ .@quot /= getelementofarray(getarg(0), .@i);
+ }
+
+ freeloop(false);
+ return .@quot;
+}
+
+
+
+// array_shift(<array>)
+// returns the first element of the array and removes it, while shifting left
+
+function script array_shift {
+ if (isstr(getarg(0)) == 1) {
+ .@val$ = getarg(0);
+ } else {
+ .@int = true;
+ .@val = getarg(0);
+ }
+
+ deletearray(getarg(0), 1); // shift left
+
+ return .@int ? .@val : .@val$;
+}
+
+
+
+// array_unshift(<array>, <value>)
+// adds <value> to the start of the array, while shifting right
+// returns the new size
+
+function script array_unshift {
+ .@size = getarraysize(getarg(0)) + 1;
+ array_pad(getarg(0), -(.@size - getarrayindex(getarg(0))), getarg(1));
+ return .@size;
+}
+
+
+
+// array_pop(<array>)
+// returns the last element of the array and removes it
+
+function script array_pop {
+ .@last = getarraysize(getarg(0)) - 1;
+
+ if (isstr(getelementofarray(getarg(0), .@last)) == 1) {
+ .@val$ = getelementofarray(getarg(0), .@last);
+ } else {
+ .@int = true;
+ .@val = getelementofarray(getarg(0), .@last);
+ }
+
+ deletearray(getelementofarray(getarg(0), .@last), 1);
+
+ return .@int ? .@val : .@val$;
+}
+
+
+
+// array_push(<array>, <value>)
+// adds <value> to the end of the array
+// returns the new size
+
+function script array_push {
+ .@size = getarraysize(getarg(0));
+ set(getelementofarray(getarg(0), .@size), getarg(1));
+ return .@size + 1;
+}
+
+
+
+// array_shuffle(<array>)
+// shuffles the array
+// uses the Durstenfeld implementation of the Fisher-Yates algorithm
+
+function script array_shuffle {
+ .@index = getarrayindex(getarg(0));
+ .@size = getarraysize(getarg(0)) - .@index;
+ freeloop(true);
+
+ for (.@i = .@size - 1; .@i >= .@index + 1; --.@i) {
+ swap(getelementofarray(getarg(0), rand(.@index, .@i)), getelementofarray(getarg(0), .@i));
+ }
+
+ freeloop(false);
+ return true;
+}
+
+
+
+// array_unique(<array>{, <threshold>})
+// allows entries to appear up to <threshold> in the array
+
+function script array_unique {
+ .@size = getarraysize(getarg(0));
+ .@max = getarg(1, 1);
+ freeloop(true);
+
+ for (.@i = getarrayindex(getarg(0)); .@i < .@size; ++.@i) {
+ .@count = 1;
+ for (.@e = .@i + 1; .@e < .@size; ++.@e) {
+ if (getelementofarray(getarg(0), .@i) == getelementofarray(getarg(0), .@e)) {
+ if (++.@count >= .@max) {
+ deletearray(getelementofarray(getarg(0), .@e), 1);
+ ++.@removed; // increase counter
+ --.@size; // reduce size
+ --.@e; // step back
+ }
+ }
+ }
+ }
+
+ freeloop(false);
+ return .@removed;
+}
+
+
+
+// array_diff(<array1>, <array2>{, <array>...}, <array>)
+// compares array1 against one or more other arrays and fills the last array
+// with the values in array1 that are not present in any of the other arrays
+// returns the number of entries not matching
+
+function script array_diff {
+ .@size = getarraysize(getarg(0));
+ .@index = getarrayindex(getarg(0));
+ freeloop(true);
+
+ for (.@a = 1; .@a < (getargcount() - 1); ++.@a) {
+ for (.@i = .@index; .@i < .@size; ++.@i) {
+ if (!array_exists(getarg(.@a), getelementofarray(getarg(0), .@i))) {
+ array_push(getarg(getargcount() - 1), getelementofarray(getarg(0), .@i));
+ ++.@count;
+ }
+ }
+ }
+
+ freeloop(false);
+ return .@count;
+}
+
+
+
+// array_filter(<array>, "<function>"{, <neq>})
+// filters the array using a callback function
+
+function script array_filter {
+ .@size = getarraysize(getarg(0));
+ .@neq = getarg(2, false);
+ freeloop(true);
+
+ for (.@i = getarrayindex(getarg(0)); .@i < .@size; ++.@i) {
+ .@eq = callfunc(getarg(1), getelementofarray(getarg(0), .@i)) != false;
+ if ((.@neq && .@eq) || (!(.@neq) && !(.@eq))) {
+ deletearray(getelementofarray(getarg(0), .@i), 1); // shift left
+ ++.@count; // increase the counter
+ --.@size; // reduce the size
+ --.@i; // step back
+ }
+ }
+
+ freeloop(false);
+ return .@count;
+}
+
+
+
+// array_sort(<array>)
+// sorts the array in ascending order
+// uses the Lomuto implementation of the Quicksort algorithm
+
+function script array_sort {
+ callsub(S_Quicksort, getarg(0), getarrayindex(getarg(0)), getarraysize(getarg(0)) - 1);
+ return true;
+
+S_Quicksort:
+ if (getarg(1) < getarg(2)) {
+ .@p = callsub(S_Partition, getarg(0), getarg(1), getarg(2));
+ callsub(S_Quicksort, getarg(0), getarg(1), .@p - 1);
+ callsub(S_Quicksort, getarg(0), .@p + 1, getarg(2));
+ }
+ return true;
+
+S_Partition:
+ .@i = getarg(1) - 1;
+
+ freeloop(true);
+ for (.@j = getarg(1); .@j <= getarg(2) - 1; ++.@j) {
+ if (getelementofarray(getarg(0), .@j) < getelementofarray(getarg(0), getarg(2))) {
+ swap(getelementofarray(getarg(0), ++.@i), getelementofarray(getarg(0), .@j));
+ }
+ }
+ freeloop(false);
+
+ swap(getelementofarray(getarg(0), ++.@i), getelementofarray(getarg(0), getarg(2)));
+ return .@i;
+}
+
+
+
+// array_rsort(<array>)
+// sorts the array in descending order
+
+function script array_rsort {
+ return array_sort(getarg(0)) && array_reverse(getarg(0));
+}
diff --git a/npc/functions/asklanguage.txt b/npc/functions/asklanguage.txt
new file mode 100644
index 00000000..f0cbe0f8
--- /dev/null
+++ b/npc/functions/asklanguage.txt
@@ -0,0 +1,56 @@
+// Evol functions.
+// Author:
+// Reid
+// Description:
+// Function setting the player language
+
+function script asklanguage {
+
+ .@nb_language = 11;
+
+ switch (getarg(0, 0))
+ {
+ case LANG_ON_SEA:
+ setarray .@messages$[0], "I hear you... (English)", // English
+ "Je vous entends... (Français)", // French
+ "Te oigo... (Español)", // Spanish
+ "Ich höre euch... (Deutsch)"; // German
+ break;
+ case LANG_IN_SHIP:
+ setarray .@messages$[0], "I speak English.", // English
+ "Je parle français.", // French
+ "Hablo Español.", // Spanish
+ "Ich spreche Deutsch."; // German
+ break;
+ default:
+ return;
+ }
+
+ setarray .@flags$[0], "flags/en",
+ "flags/fr",
+ "flags/es",
+ "flags/de";
+
+ .@menustr$ = "";
+ .@separator$ = ":";
+
+ for (.@i = 0; .@i <= .@nb_language; .@i++)
+ {
+ if (.@i == .@nb_language)
+ {
+ .@separator$ = "";
+ }
+ .@menustr$ = .@menustr$ + .@flags$[.@i] + "|" + .@messages$[.@i] + .@separator$;
+ }
+
+ select(.@menustr$);
+
+ .@lang = @menu - 1;
+
+ if (.@lang >= 0 || .@lang <= .@nb_language)
+ {
+ Lang = .@lang;
+ }
+
+ return;
+}
diff --git a/npc/functions/asleep.txt b/npc/functions/asleep.txt
new file mode 100644
index 00000000..aa5abd63
--- /dev/null
+++ b/npc/functions/asleep.txt
@@ -0,0 +1,19 @@
+// Evol functions.
+// Authors:
+// Alige
+// Reid
+// Description:
+// Tell a random sleeping sound.
+// Variables:
+// .@rand = Random number between the number of "sleeping" choice.
+
+function script asleep {
+ switch(rand(5)) {
+ case 0: npctalkonce(l("Zzzzzzzzz...")); break;
+ case 1: npctalkonce(l("Rrrr... Pchhhh...")); break;
+ case 2: npctalkonce(l("Ggrmm... Grmmmm...")); break;
+ case 3: npctalkonce(l("Hm... Shhhh...")); break;
+ default: emotion(E_SLEEPY);
+ }
+ end;
+}
diff --git a/npc/functions/bank.txt b/npc/functions/bank.txt
new file mode 100644
index 00000000..2af9136e
--- /dev/null
+++ b/npc/functions/bank.txt
@@ -0,0 +1,297 @@
+// Evol scripts.
+// Authors:
+// gumi
+// Reid
+// Jesusalva
+
+function script MerchantGuild_Bank {
+ do
+ {
+ if (BankVault > 0)
+ {
+ speech S_FIRST_BLANK_LINE | S_LAST_NEXT,
+ l("You currently have @@ Esperin on your bank account.",
+ format_number(BankVault)),
+ l("What do you want to do with your money?");
+ }
+ else
+ {
+ speech S_FIRST_BLANK_LINE | S_LAST_NEXT,
+ l("What do you want to do with your money?");
+ }
+
+ select
+ rif(Zeny > 0, l("Deposit.")),
+ rif(BankVault > 0, l("Withdraw.")),
+ l("I'm done.");
+
+ switch (@menu)
+ {
+ case 1:
+ speech S_FIRST_BLANK_LINE | S_LAST_NEXT,
+ l("How much do you want to deposit?");
+
+ menuint
+ l("Other."), -1,
+ rif(Zeny >= 5000, format_number(5000) + " E."), 5000,
+ rif(Zeny >= 10000, format_number(10000) + " E."), 10000,
+ rif(Zeny >= 25000, format_number(25000) + " E."), 25000,
+ rif(Zeny >= 50000, format_number(50000) + " E."), 50000,
+ rif(Zeny >= 100000, format_number(100000) + " E."), 100000,
+ rif(Zeny >= 250000, format_number(250000) + " E."), 250000,
+ rif(Zeny >= 500000, format_number(500000) + " E."), 500000,
+ rif(Zeny >= 1000000, format_number(1000000) + " E."), 1000000,
+ l("All of my money."), -2,
+ l("I changed my mind."), -3;
+
+ switch (@menuret)
+ {
+ case -1:
+ input @menuret;
+ break;
+ case -2:
+ @menuret = Zeny;
+ }
+
+ if (@menuret > 0)
+ {
+ if (@menuret > Zeny)
+ {
+ speech S_FIRST_BLANK_LINE | S_LAST_NEXT,
+ l("You do not have enough Esperin on yourself.");
+ break;
+ }
+
+ @menuret = min(MAX_BANK_ZENY, @menuret); // make sure the variable can't overflow
+ .@before = BankVault; // amount before the deposit
+ .@max = MAX_BANK_ZENY - BankVault; // maximum possible deposit
+ .@deposit = min(.@max, @menuret); // actual deposit
+
+ if (.@deposit > 0)
+ {
+ BankVault += .@deposit; // add to bank
+ Zeny -= .@deposit; // remove from inventory
+
+ speech S_FIRST_BLANK_LINE | S_LAST_NEXT,
+ l("You made a cash deposit of @@ E.", format_number(.@deposit));
+ }
+ }
+ break;
+
+ case 2:
+ speech S_FIRST_BLANK_LINE | S_LAST_NEXT,
+ l("How much do you want to withdraw?");
+
+ menuint
+ l("Other."), -1,
+ rif(BankVault >= 5000, format_number(5000) + " E."), 5000,
+ rif(BankVault >= 10000, format_number(10000) + " E."), 10000,
+ rif(BankVault >= 25000, format_number(25000) + " E."), 25000,
+ rif(BankVault >= 50000, format_number(50000) + " E."), 50000,
+ rif(BankVault >= 100000, format_number(100000) + " E."), 100000,
+ rif(BankVault >= 250000, format_number(250000) + " E."), 250000,
+ rif(BankVault >= 500000, format_number(500000) + " E."), 500000,
+ rif(BankVault >= 1000000, format_number(1000000) + " E."), 1000000,
+ l("All of my money."), -2,
+ l("I changed my mind."), -3;
+
+ switch (@menuret)
+ {
+ case -1:
+ input @menuret;
+ break;
+ case -2:
+ @menuret = BankVault;
+ }
+
+ if (@menuret > 0)
+ {
+ if (@menuret > BankVault)
+ {
+ speech S_FIRST_BLANK_LINE | S_LAST_NEXT,
+ l("You do not have enough Esperin on your bank account.");
+ break;
+ }
+
+ @menuret = min(MAX_ZENY, @menuret); // make sure the variable can't overflow
+ .@before = Zeny; // amount before the withdrawal
+ .@max = MAX_ZENY - Zeny; // maximum possible withdrawal
+ .@withdrawal = min(.@max, @menuret); // actual withdrawal
+
+ if (.@withdrawal > 0)
+ {
+ Zeny += .@withdrawal; // add to inventory
+ BankVault -= .@withdrawal; // remove from bank
+
+ speech S_FIRST_BLANK_LINE | S_LAST_NEXT,
+ l("You withdrew a total of @@ E.", format_number(.@withdrawal));
+ }
+ }
+ break;
+
+ default: return;
+ }
+ } while (true);
+}
+
+// MerchantGuild_Quests(.bankid)
+function script MerchantGuild_Quests {
+ mes "";
+ // Quest Type, Quest Data, Quest Timer
+ .@q=getq(General_MerchantRequest);
+ .@q2=getq2(General_MerchantRequest);
+ .@q3=getq3(General_MerchantRequest);
+ .@id=getarg(0);
+
+ // Cooldown
+ if (.@q3 > gettimetick(2)) {
+ mesn $@BANK_NAME$[.@id];
+ mesq l("There are no tasks for you right now.");
+ mesc l("Please come back later, in %s.", FuzzyTime(.@q3));
+ next;
+ return;
+ }
+
+ // TODO: Submit/Abort current request
+ switch (.@q) {
+ case MERCQ_LETTER:
+ if (.@id == .@q2) {
+ mesn $@BANK_NAME$[.@id];
+ mesq l("Thanks for the letter! Your efforts are greatly appreciated.");
+ Zeny+=rand2(100, 500);
+ getexp rand2(1000, 5000), rand2(150, 300);
+ setq General_MerchantRequest, MERCQ_NONE, 0, gettimetick(2)+1800;
+ return;
+ }
+ else
+ {
+ mesn $@BANK_NAME$[.@id];
+ mesq l("Current task: Deliver a letter to %s", $@BANK_TOWN$[.@q2]);
+ next;
+ select
+ l("Continue"),
+ l("Abort") + " ["+l("Change task")+"]";
+ mes "";
+ if (@menu == 1)
+ return;
+ setq General_MerchantRequest, MERCQ_NONE, 0, gettimetick(2);
+ }
+ break;
+ /* ***************************************** */
+ case MERCQ_GOODS:
+ .@cont=ASK_NO;
+ if (countitem(.@q2)) {
+ mesc l("Deliver %s?", getitemlink(.@q2));
+ .@cont=askyesno();
+ }
+ if (.@cont == ASK_YES) {
+ mesn $@BANK_NAME$[.@id];
+ mesq l("Thanks for the %s! Your efforts are greatly appreciated.", getitemlink(.@q2));
+ delitem .@q2, 1;
+ Zeny+=rand2(500, 2500);
+ getexp rand2(5000, 15000), rand2(250, 400);
+ setq General_MerchantRequest, MERCQ_NONE, 0, gettimetick(2)+7200;
+ return;
+ }
+ else
+ {
+ mesn $@BANK_NAME$[.@id];
+ mesq l("Current task: Purchase a(n) %s", getitemlink(.@q2));
+ next;
+ select
+ l("Continue"),
+ l("Abort") + " ["+l("Change task")+"]";
+ mes "";
+ if (@menu == 1)
+ return;
+ setq General_MerchantRequest, MERCQ_NONE, 0, gettimetick(2);
+ }
+ break;
+ /* ***************************************** */
+ case MERCQ_SCOUT:
+ if (.@id == .@q2) {
+ mesn $@BANK_NAME$[.@id];
+ mesq l("Thanks for scorting our caravan! Your efforts are greatly appreciated.");
+ Zeny+=rand2(2500, 5000);
+ getexp rand2(21000, 35000), rand2(500, 800);
+ setq General_MerchantRequest, MERCQ_NONE, 0, gettimetick(2)+43200;
+ return;
+ }
+ else
+ {
+ mesn $@BANK_NAME$[.@id];
+ mesq l("Current task: Scout guild member to %s", $@BANK_TOWN$[.@q2]);
+ next;
+ select
+ l("Continue"),
+ l("Abort");
+ mes "";
+ if (@menu == 1)
+ return;
+ setq General_MerchantRequest, MERCQ_NONE, 0, gettimetick(2);
+ }
+ break;
+ }
+
+ do
+ {
+ mesc l("The %s Merchant Guild has a few requests for you:", $@BANK_TOWN$[.@id]);
+ .@town = .@id;
+ while (.@town == .@id) {
+ .@town=rand2(getarraysize($@BANK_TOWN$));
+ }
+ select
+ l("How does this works?"),
+ rif(.@town != .@id, l("★ Deliver a letter")),
+ l("★★ Purchase goods"),
+ //rif(.@town != .@id, l("★★★ Scout a caravan")),
+ l("Sorry, I won't accept any.");
+ mes "";
+ switch (@menu) {
+ case 1:
+ mesc l("The Merchant Guild spawns multiple continents, and we can offer you a few tasks for them. Be careful as you might not be able to finish them and you'll have to abort!");
+ mesc l("The more stars, the harder it is.");
+ next;
+ mesc l("After completing a request, there'll be a cooldown, proportional to the difficulty.");
+ mesc l("You can only have one Merchant Guild request active at same time.");
+ next;
+ break;
+ // Deliver a letter
+ case 2:
+ mesc l("We need you to deliver this important letter to %s! Avoid the roads and bandits!", $@BANK_NAME$[.@town]);
+ next;
+ mesc l("Accept request?");
+ if (askyesno() == ASK_YES) {
+ mesc l("I'm counting on you!");
+ setq General_MerchantRequest, MERCQ_LETTER, .@town, gettimetick(2);
+ return;
+ }
+ break;
+ // Purchase goods
+ case 3:
+ .@item=any(ElixirOfLife,
+ CarpSandwich, PioulegSandwich, MananaSandwich,
+ MaggotSlimePotion, BlueberryCake, CarrotCake, Donut,
+ DeathPotion, TreasureMap, IronIngot, SilverIngot, GoldIngot,
+ Diamond, Ruby, Emerald, Sapphire, Topaz, Amethyst,
+ CrudeDiamond, CrudeRuby, CrudeEmerald,
+ CrudeSapphire, CrudeTopaz, CrudeAmethyst,
+ RunestoneUruz, RunestoneRaido, RunestoneThurisaz,
+ RunestoneKaunaz, RunestoneDagaz, RunestonePeorth);
+ // TODO: equips, as crafting/tailoring is added
+ mesc l("The merchant guild needs %s! Purchase it and deliver at the nearest merchant guild member!", getitemlink(.@item));
+ next;
+ mesc l("Accept request?");
+ if (askyesno() == ASK_YES) {
+ mesc l("I'm counting on you!");
+ setq General_MerchantRequest, MERCQ_GOODS, .@item, gettimetick(2);
+ return;
+ }
+ break;
+ default:
+ return;
+ }
+ } while (true);
+ return;
+}
+
diff --git a/npc/functions/barber.txt b/npc/functions/barber.txt
new file mode 100644
index 00000000..d5a330f2
--- /dev/null
+++ b/npc/functions/barber.txt
@@ -0,0 +1,220 @@
+// Evol scripts.
+// Authors:
+// omatt
+// Reid
+// Travolta
+// Jesusalva
+// Description:
+// Function for supporting barber NPC.
+
+// BarberSayStyle({what})
+// what: 1 = Style; 2 = Color; 3 = Style + Color in dialog
+function script BarberSayStyle {
+
+ .@get_color = getlook(LOOK_HAIR_COLOR);
+ .@get_look = getlook(LOOK_HAIR);
+ .@style_name$ = $@hairstyle$[.@get_look];
+ .@color_name$ = $@haircolor$[.@get_color];
+
+ switch (getarg(0, 3))
+ {
+ case 1:
+ message strcharinfo(0), l("@@", .@style_name$);
+ break;
+ case 2:
+ message strcharinfo(0), l("@@", .@color_name$);
+ break;
+ case 3:
+ speech S_FIRST_BLANK_LINE | S_LAST_NEXT,
+ l("Your hairstyle is @@ and its color is @@.", .@style_name$, .@color_name$);
+ break;
+ }
+ return;
+}
+function script BarberChangeStyle {
+ do
+ {
+ .@hairsizearray = getarraysize($@hairstyle$);
+ .@get_look = getlook(LOOK_HAIR);
+
+ // Here .@i starts from 1 because hairstyle 0 doesn't exist.
+ for (.@i = 1; .@i < .@hairsizearray; .@i++)
+ {
+ .@menustr$ = .@menustr$
+ + rif(.@get_look != .@i, l("" + $@hairstyle$[.@i] + ""))
+ + ":";
+ }
+
+ .@menustr$ = .@menustr$ + l("I'm fine for now, thank you.");
+
+ .@idx = select(l("As you want!"),.@menustr$);
+
+ if (.@idx == .@i + 1) return; // last choice to quit dialog
+
+ switch (@menu)
+ {
+ case 1:
+ do
+ {
+ // here "- 1" because i don't use the 0 of array
+ .@rand_hair = rand(1,(.@hairsizearray - 1));
+ } while (.@rand_hair == getlook(LOOK_HAIR));
+ setlook LOOK_HAIR, .@rand_hair;
+ setlook LOOK_HAIR_COLOR, getlook(LOOK_HAIR_COLOR);
+ BarberSayStyle(1);
+ break;
+ default:
+ // and here "- 1" because the first choice is taken by the random
+ setlook LOOK_HAIR, (@menu - 1);
+ setlook LOOK_HAIR_COLOR, getlook(LOOK_HAIR_COLOR);
+ break;
+ }
+ .@menustr$ = "";
+ } while (1);
+}
+
+function script BarberChangeColor {
+ do
+ {
+ .@get_look = getlook(LOOK_HAIR_COLOR);
+ .@hairsizearray = getarraysize($@haircolor$);
+
+ for (.@i = 0; .@i < .@hairsizearray; .@i++)
+ {
+ .@menustr$ = .@menustr$
+ + rif(.@get_look != .@i, l("" + $@haircolor$[.@i] + ""))
+ + ":";
+ }
+
+ .@menustr$ = l("Surprise me!") + ":" + .@menustr$ + l("I'm fine for now, thank you.");
+
+ .@idx = select(.@menustr$);
+
+ if (.@idx == .@i + 2) return;
+
+ switch (@menu)
+ {
+ case 1:
+ do
+ {
+ .@rand_color = rand(0, .@hairsizearray);
+ } while (.@rand_color == getlook(LOOK_HAIR_COLOR));
+ setlook LOOK_HAIR_COLOR, .@rand_color;
+ BarberSayStyle(2);
+ break;
+ default:
+ setlook LOOK_HAIR_COLOR, (@menu - 2);
+ break;
+ }
+ .@menustr$ = "";
+ } while (1);
+
+ return;
+}
+
+function script BarberChangeBodyType {
+ mesn("Note");
+ mes(b(l("Changing your body type will send you back to the character selection screen.")));
+ next();
+
+ mes(l("Please select the desired body type:"));
+ menuint(
+ rif(BodyType == BODYTYPE_1, "► ") + l("Body type %i", 1), BODYTYPE_1,
+ rif(BodyType == BODYTYPE_2, "► ") + l("Body type %i", 2), BODYTYPE_2,
+ rif(BodyType == BODYTYPE_3, "► ") + l("Body type %i", 3), BODYTYPE_3);
+
+ if (BodyType == @menuret) {
+ return; // don't kick to char selection when not needed
+ }
+
+ // FIXME: when manaplus supports seamless changing for evol2, use a simple return;
+ closedialog();
+ close2();
+ BodyType = @menuret;
+ close;
+}
+
+// THIS FUNCTION SHOULD BE USED ONLY AT REBIRTH
+// Unless current game development design changes!
+function script BarberChangeRace {
+
+ mes l("What's your race?");
+ menuint
+ get_race(GETRACE_FULL, KaizeiTalpan), KaizeiTalpan,
+ get_race(GETRACE_FULL, ArgaesTalpan), ArgaesTalpan,
+ get_race(GETRACE_FULL, TonoriTalpan), TonoriTalpan,
+ get_race(GETRACE_FULL, CaveUkar), CaveUkar,
+ get_race(GETRACE_FULL, MountainUkar), MountainUkar,
+ get_race(GETRACE_FULL, SeaTritan), SeaTritan,
+ get_race(GETRACE_FULL, LakeTritan), LakeTritan,
+ rif(REBIRTH, get_race(GETRACE_FULL, LightRaijin)), LightRaijin,
+ rif(REBIRTH, get_race(GETRACE_FULL, DarkRaijin)), DarkRaijin,
+ rif(REBIRTH, get_race(GETRACE_FULL, FireKralog)), FireKralog,
+ rif(REBIRTH, get_race(GETRACE_FULL, FrostKralog)), FrostKralog;
+
+ mes "";
+ // Something went *terribly* wrong
+ if (@menuret >= LightRaijin && !REBIRTH) {
+ channelmes("#irc", "Illegal operation at BarberChangeRace, sysadmin help required.");
+ consolemes(CONSOLEMES_ERROR, "Account %d tried to change race to %d but rebirth is not set. Race not changed.", getcharid(3), @menuret);
+ return;
+ }
+
+ // Change race and we're done
+ //Class = @menuret;
+ jobchange(@menuret); // STUPID idea, but imposed by Hercules
+ return;
+}
+
+// Jack of all trades
+// Barber({intro=True})
+function script Barber {
+ if (getarg(0, true)) {
+ mesn;
+ mesq l("Hello.");
+ next;
+ }
+ mesq l("What would you like me to do?");
+ next;
+ do
+ {
+ select
+ l("What is my current hairstyle and hair color?"),
+ l("I'd like to get a different style."),
+ l("Can you do something with my color?"),
+ l("How about changing my body type?"),
+ l("I'm fine for now, thank you.");
+
+ switch (@menu)
+ {
+ case 1:
+ BarberSayStyle();
+ break;
+ case 2:
+ BarberChangeStyle;
+ speech S_FIRST_BLANK_LINE | S_LAST_NEXT,
+ l("Enjoy your new style."),
+ l("Anything else?");
+ break;
+ case 3:
+ BarberChangeColor;
+ speech S_FIRST_BLANK_LINE | S_LAST_NEXT,
+ l("I hope you like this color."),
+ l("Anything else?");
+ break;
+ case 4:
+ BarberChangeBodyType();
+ speech S_FIRST_BLANK_LINE | S_LAST_NEXT,
+ l("You look fantastic."),
+ l("Anything else?");
+ break;
+ case 5:
+ speech S_FIRST_BLANK_LINE | S_LAST_NEXT,
+ l("Feel free to come visit me another time.");
+
+ goodbye;
+ }
+ } while (true);
+ return;
+}
+
diff --git a/npc/functions/beds.txt b/npc/functions/beds.txt
new file mode 100644
index 00000000..a4301891
--- /dev/null
+++ b/npc/functions/beds.txt
@@ -0,0 +1,22 @@
+// Evol functions.
+// Authors:
+// 4144
+// Reid
+// Jesusalva
+// Description:
+// Beds utility functions
+
+// bedTouch({inn})
+function script bedTouch {
+ if (PC_IS_DEAD)
+ {
+ PC_IS_DEAD = 0;
+ if (INN_REGISTER != NO_INN)
+ {
+ INN_REGISTER = NO_INN;
+ percentheal 100,100;
+ }
+ }
+ close;
+}
+
diff --git a/npc/functions/bitwise.txt b/npc/functions/bitwise.txt
new file mode 100644
index 00000000..a2e57587
--- /dev/null
+++ b/npc/functions/bitwise.txt
@@ -0,0 +1,66 @@
+/**
+ * Gets a bitmasked value in from an integer. If the shift is omitted, it will
+ * be deduced from the mask.
+ *
+ * @arg 0 - the variable
+ * @arg 1 - mask
+ * @arg 2 - shift
+ */
+function script bitwise_get {
+ .@shift = getarg(2, 0);
+
+ if (getargcount() < 3) {
+ // guess the shift from the mask:
+ for (.@shift = 0; .@shift < 32; ++.@shift) {
+ if ((getarg(1) & (1 << .@shift)) != 0) {
+ break;
+ }
+ }
+ }
+
+ return (getarg(0) & getarg(1)) >> .@shift;
+}
+
+/**
+ * sets a bitmasked value in a variable
+ *
+ * @arg 0 - the target variable
+ * @arg 1 - mask
+ * @arg 2 - shift
+ * @arg 3 - new value
+ * @return a reference to the variable
+ */
+function script bitwise_set {
+ if (getargcount() < 4) {
+ // guess the shift from the mask:
+ for (.@shift = 0; .@shift < 32; ++.@shift) {
+ if ((getarg(1) & (1 << .@shift)) != 0) {
+ break;
+ }
+ }
+
+ return set(getarg(0), (getarg(0) & ~(getarg(1))) | (getarg(2, 0) << .@shift));
+ }
+
+ return set(getarg(0), (getarg(0) & ~(getarg(1))) | (getarg(3, 0) << getarg(2, 0)));
+}
+
+
+// bitmask_count(<int>)
+// returns the number of bits set in <int> (up to 4096?)
+function script bitmask_count {
+ .@n = getarg(0); // Number evaluated
+ .@p=0; // Bits set/unset
+ .@s=0; // Stack and Check
+ .@m=0; // Memory
+
+ // Loop only as needed
+ while (.@s < .@n) {
+ .@s=2**.@m;
+ if (.@n & .@s)
+ .@p++;
+ .@m++;
+ }
+ return .@p;
+}
+
diff --git a/npc/functions/bodytype.txt b/npc/functions/bodytype.txt
new file mode 100644
index 00000000..5d37775a
--- /dev/null
+++ b/npc/functions/bodytype.txt
@@ -0,0 +1,19 @@
+function script stringToBodytype {
+ switch (ord(strtolower(charat(getarg(0, "n"), 0)))) {
+ case 50: // 2
+ case 102: // f
+ return BODYTYPE_2;
+ case 51: // 3
+ case 109: // m
+ return BODYTYPE_3;
+ default:
+ return BODYTYPE_1;
+ }
+}
+
+function script bodytypeToString {
+ .@bodytype = getarg(0, BodyType);
+
+ return .@bodytype == BODYTYPE_2 ? l("type %i", 2) :
+ .@bodytype == BODYTYPE_3 ? l("type %i", 3) : l("type %i", 1);
+}
diff --git a/npc/functions/casino.txt b/npc/functions/casino.txt
new file mode 100644
index 00000000..87b71a4d
--- /dev/null
+++ b/npc/functions/casino.txt
@@ -0,0 +1,254 @@
+// TMW2 Scripts
+// Author:
+// Jesusalva
+// Description:
+// Slot Machines, Blackjack, other crap for npc duplication
+// Contents:
+// "Slot Machine"
+// "High Lower"
+
+// SLOT MACHINE - You'll get PRIZE if you roll a 777!
+// PRIZE must be numeric INT, AegisName won't get parsed.
+// Slot Machine#map_prize
+// Slot Machine#map
+// Slot Machine#_prize
+- script Slot Machine NPC_HIDDEN,{
+ function symbol {
+ switch (getarg(0)) {
+ case 1:
+ mesn "%%A";
+ break;
+ case 2:
+ mesn "%%B";
+ break;
+ case 3:
+ mesn "%%C";
+ break;
+ case 4:
+ mesn "%%D";
+ break;
+ case 5:
+ mesn "%%E";
+ break;
+ case 6:
+ mesn "%%F";
+ break;
+ case 7:
+ mesn "7";
+ break;
+ default:
+ mesn "%%@";
+ break;
+ }
+ }
+
+L_Menu:
+ mesn;
+ mesc l("Spin three symbols, and jackpot great rewards!");
+ mesc l("Just one coin for spin.");
+ next;
+ menu
+ rif(countitem(.coinid) >= 1, l("Spin!")), L_Spin,
+ l("Prizes"), L_Info,
+ l("Leave"), -;
+ close;
+
+L_Info:
+ mes "";
+ mesc l("Prizes:");
+ mes l("##9 777: @@.", getitemlink(.itemid));
+ mesc l("Three equal: @@.", "18 casino coins");
+ mesc l("Two equal: 1 casino coin.");
+ next;
+ goto L_Menu;
+
+
+L_Spin:
+ mesc l("Spinning...");
+ next;
+ delitem .coinid, 1;
+ .@a=rand2(1,7);
+ .@b=rand2(1,7);
+ .@c=rand2(1,7);
+ symbol(.@a);
+ symbol(.@b);
+ symbol(.@c);
+ next;
+ mesn;
+ if (.@a == .@b && .@a == .@c && .@a == 7) {
+ getitem .itemid, 1;
+ mesc l("Jackpot! You got a(n) %s!", getitemlink(.itemid)), 3;
+ } else if (.@a == .@b && .@a == .@c) {
+ getitem .coinid, 18;
+ mesc l("Congrats! A pity it was not 777..."), 3;
+ } else if (.@a == .@b || .@a == .@c || .@b == .@c) {
+ getitem .coinid, 1;
+ mesc l("Lucky! You got the coin back!"), 3;
+ } else {
+ mesc l("It wasn't this time..."), 3;
+ }
+ next;
+ goto L_Menu;
+
+OnInit:
+ // "Next-Generation" parsing system
+ // To replace default item, name NPC like this:
+ // Slot Machine#_1212
+ // Where "1212" is the item ID
+ .@n$=strnpcinfo(0, "_0");
+ explode(.@ni$, .@n$, "_");
+ if (getarraysize(.@n$) > 0)
+ .itemid=atoi(.@ni$[1]);
+ else
+ .itemid=0;
+
+ // If item ID was not provided
+ if (.itemid < 1) {
+ //.itemid=Monocle;
+ .itemid=BrownBowlerHat;
+ }
+
+ // Coin ID
+ //.coinid=CasinoCoins;
+ .coinid=CoinBag;
+
+ .sex = G_OTHER;
+ .distance = 4;
+ end;
+}
+
+
+// HIGH LOWER - Guess if the next card will be HIGHER or LOWER!
+// (No arguments)
+- script High Lower NPC_HIDDEN,{
+ function cardname {
+ switch (getarg(0)) {
+ case 0:
+ return "A"; break;
+ case 10:
+ return "J"; break;
+ case 11:
+ return "Q"; break;
+ case 12:
+ return "K"; break;
+ case 13:
+ return l("Joker"); break;
+ default:
+ return getarg(0)+1;
+ }
+ }
+
+ goto L_Menu;
+
+L_Menu:
+ showavatar;
+ mesn;
+ mesc l("Hey, I am flopped. Do you want to gamble?");
+ mesc l("You need a %s. I'll flip one card, and you'll need to decide if next draw will be HIGHER or LOWER.", getitemlink(.coinid));
+ mesc l("If a tie happens, I'll give your coin back.");
+ next;
+ menu
+ rif(countitem(.coinid) >= 1, l("Let's play!")), L_Spin,
+ l("Information"), L_Info,
+ l("Leave"), L_Quit;
+
+L_Info:
+ mes "";
+ mesc l("Rules:");
+ mesc l("A card will be flipped, you'll need to decide if next flip will be HIGHER or LOWER.");
+ mesc l("Cards are ranked on this priority: A - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - J - Q - K - Joker");
+ next;
+ mesc l("Prizes:");
+ mesc l("If you're right, you'll get at least %d GP!", .minprize);
+ mesc l("If a tie happens, you'll get your coin back.");
+ mesc l("If you're wrong, your winning streak is reset.");
+ mesc l("Winning Streak is also reset on logout or when you leave the room.");
+ next;
+ mesc l("Winning Strike Prizes:");
+ mesc l("Every seven successive right guesses, you'll get a %s!", getitemlink(Acorn)); // 7.14%
+ //mesc l("Every fifteen successive right guesses, you'll get a %s!", getitemlink(SilverGift)); // 3.33%
+ //mesc l("Every fifty successive right guesses, you'll get a %s!", getitemlink(GoldenGift)); // 1.00%
+ //mesc l("Every 101 successive right guesses, you'll get a %s!", getitemlink(PrismGift)); // 0.50%
+ next;
+ goto L_Menu;
+
+
+L_Spin:
+ showavatar AVATAR_CARD_COVER;
+ mesc l("I'll draw a card now!");
+ next;
+ delitem .coinid, 1;
+ // First card will never be an edge card (Ace or Joker), so you can ALWAYS guess wrong.
+ .@card1=rand2(1, 12);
+ showavatar 1000+.@card1;
+ mesn;
+ mesc l("It's a @@!", cardname(.@card1));
+ mesc l("Will next draw be HIGHER or LOWER?!");
+ next;
+ mesc l("Cards are ranked on this priority: A - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - J - Q - K - Joker");
+ select
+ l("HIGHER!"),
+ l("LOWER!");
+ mes "";
+
+ // Flip Flop!
+ .@card2=rand2(0, 13);
+ showavatar 1000+.@card2;
+ mesn;
+ mesc l("It's a @@!", cardname(.@card2));
+
+ if (.@card1 == .@card2) {
+ mesc l("It's a tie!");
+ getitem .coinid, 1;
+ .@bypass=1;
+ } else if (.@card2 < .@card1 && @menu == 2) {
+ mesc l("It's lower! That's right!");
+ Zeny=Zeny+.minprize;
+ @gambler_winstreak+=1;
+ } else if (.@card2 > .@card1 && @menu == 1) {
+ mesc l("It's higher! That's right!");
+ Zeny=Zeny+.minprize;
+ @gambler_winstreak+=1;
+ } else {
+ mesc l("You were wrong!");
+ @gambler_winstreak=0;
+ }
+
+ // Winning Streak
+ if (!.@bypass && @gambler_winstreak) {
+ if (@gambler_winstreak % 7 == 0)
+ getitem Acorn, 1;
+ /*
+ if (@gambler_winstreak % 15 == 0)
+ getitem SilverGift, 1;
+ if (@gambler_winstreak % 50 == 0)
+ getitem GoldenGift, 1;
+ if (@gambler_winstreak % 101 == 0)
+ getitem PrismGift, 1;
+ */
+ mesc l("Your current win streak is @@!", @gambler_winstreak);
+ Zeny+=min(((@gambler_winstreak-1)*.gpbonus), .minprize); // Never 2x
+ } else {
+ .@bypass=0;
+ }
+ next;
+ goto L_Menu;
+
+L_Quit:
+ close;
+
+OnInit:
+ // Gambling configuration
+ .minprize=180;
+ .gpbonus=10;
+
+ // Coin ID
+ //.coinid=CasinoCoins;
+ .coinid=CoinBag;
+
+ .sex = G_OTHER;
+ .distance = 4;
+ end;
+
+}
+
diff --git a/npc/functions/clientversion.txt b/npc/functions/clientversion.txt
new file mode 100644
index 00000000..2f097e4e
--- /dev/null
+++ b/npc/functions/clientversion.txt
@@ -0,0 +1,20 @@
+// Evol functions.
+// Author:
+// 4144
+// Description:
+// Function checking the client version and reports if it is too old.
+
+function script checkclientversion {
+ if (ClientVersion > 23) return;
+
+ mesn "Narrator";
+ mes col("Warning.", 9);
+ mes col("Warning.", 9);
+ mes col("Warning: You are using an old client.", 9);
+ next;
+ mes col("Not all features will work.", 9);
+ next;
+ mesc l("Please install the new client from %s.", "[@@https://manaplus.org/|@@]");
+ next;
+ return;
+}
diff --git a/npc/functions/confused-tree-dict.txt b/npc/functions/confused-tree-dict.txt
new file mode 100644
index 00000000..138c4704
--- /dev/null
+++ b/npc/functions/confused-tree-dict.txt
@@ -0,0 +1,515 @@
+// Evol scripts.
+// Author:
+// gumi
+// rein
+// Based on CrazyTree, originally made by:
+// gumi
+// pclouds
+// veryape
+// wushin
+// Description:
+// dictionaries for confused tree
+
+// Built-in variables:
+// ~t lowercase hot word regex
+// ~n npc name
+// ~p player name or special name
+// ~P player name only
+//
+// Custom variables:
+// {{var}} random from array .D_var$
+// {{^var}} same but capitalize
+// {{+var}} same but title case
+// {{!var}} same but all caps
+// You can also specify multiple variables, separated by a comma (,)
+//
+// Example:
+// "*drops a {{! size }} {{ color }} {{^ sizeable object, someone }} on ~p's head*"
+
+function script TREE_dictionaries {
+ .npc$ = strnpcinfo(0);
+
+ // special aliases below (regex of lowercase char names)
+ // the substitutions are an array separated by backticks (`)
+ // XXX: this could become a hashtable at some point if it gets too big
+
+ setarray(getvariableofnpc(.alias$[0], .npc$),
+ "^veryape(?:gm)?$", "hairyape",
+
+ "^wu-?shin$", "Dwarven Princess`"
+ "She-Ra",
+
+ "^reid$", "Borg Queen`"
+ "Mistress`"
+ "Milady`"
+ "R'eid",
+
+ "^(?:slicer|madcamel)$", "Camel Toe",
+
+ "^4144(?:4d494e)?$", "NPC",
+
+ "^omatt$", "@@https://youtu.be/S2qiZoqH9OY|omatt@@`"
+ "o'matt",
+
+ "^prsm$", "Refractor`"
+ "Overlord");
+ set(getvariableofnpc(.alias, .npc$), getarraysize(getvariableofnpc(.alias$, .npc$)));
+
+
+
+ // special drops below (regex of lowercase char names)
+ // the substitutions are an array of standard replies separated by backticks (`)
+ // and allow {{variables}}
+ // XXX: this could become a hashtable at some point if it gets too big
+
+ setarray(getvariableofnpc(.sdrops$[0], .npc$),
+ "^reid$", "*drops an empty jar of Nutella on ~p*",
+ "^omatt$", "*drops Elisabeth Granneman on ~p*");
+ set(getvariableofnpc(.sdrops, .npc$), getarraysize(getvariableofnpc(.sdrops$, .npc$)));
+
+
+ // variables below
+
+ setarray(getvariableofnpc(.D_size$[1], .npc$), // {{size}}
+ "tiny", 1,
+ "small", 1,
+ "perfectly sized", 1,
+ "large", 1,
+ "huge", 1,
+ "humongous", 1,
+ "ginormous", 1);
+ set(getvariableofnpc(.D_size, .npc$), getarraysize(getvariableofnpc(.D_size$, .npc$)));
+
+ setarray(getvariableofnpc(.D_color$[1], .npc$), // {{color}}
+ "red", 1,
+ "orange", 1,
+ "yellow", 1,
+ "pink", 1,
+ "aqua", 1,
+ "cyan", 1,
+ "blue", 1,
+ "indigo", 1,
+ "violet", 1,
+ "purple", 1,
+ "magenta", 1,
+ "pink", 1,
+ "black", 1,
+ "white", 1,
+ "grey", 1,
+ "greyscale", 1,
+ "brown", 1,
+ "maroon", 1,
+ "turquoise", 1,
+ "lime", 1,
+ "sky blue", 1,
+ "invisible", 1);
+ set(getvariableofnpc(.D_color, .npc$), getarraysize(getvariableofnpc(.D_color$, .npc$)));
+
+ setarray(getvariableofnpc(.D_violentadverb$[1], .npc$), // {{violent adverb}}
+ "violently", 1,
+ "repeatedly", 1,
+ "casually", 1,
+ "forcefully", 1,
+ "slowly", 1,
+ "carefully", 1,
+ "hopefully", 1,
+ "dangerously", 1,
+ "shockingly", 1,
+ "religiously", 1);
+ set(getvariableofnpc(.D_violentadverb, .npc$), getarraysize(getvariableofnpc(.D_violentadverb$, .npc$)));
+
+ setarray(getvariableofnpc(.D_hello$[1], .npc$), // {{hello}}
+ "hi", 4,
+ "hey", 3,
+ "yo", 2,
+ "hello", 10,
+ "howdy", 1,
+ "bonjour", 1);
+ set(getvariableofnpc(.D_hello, .npc$), getarraysize(getvariableofnpc(.D_hello$, .npc$)));
+
+ setarray(getvariableofnpc(.D_violentverb$[1], .npc$), // {{violent verb}}
+ "slaps", 5,
+ "hits", 1,
+ "pummels", 1,
+ "beats", 1,
+ "flattens", 1,
+ "taunts", 1,
+ "liquidates", 1,
+ "spanks", 1,
+ "affronts", 1,
+ "tranquilizes", 1,
+ "atomizes", 1,
+ "impales", 1,
+ "dismembers", 1);
+ set(getvariableofnpc(.D_violentverb, .npc$), getarraysize(getvariableofnpc(.D_violentverb$, .npc$)));
+
+ setarray(getvariableofnpc(.D_location$[1], .npc$), // {{location}}
+ "Artis", 1,
+ "Hurnscald", 1,
+ "Tulimshar", 1,
+ "Nivalis", 1,
+ "Candor", 1,
+ "Drasil", 1);
+ set(getvariableofnpc(.D_location, .npc$), getarraysize(getvariableofnpc(.D_location$, .npc$)));
+
+ setarray(getvariableofnpc(.D_sizeableobject$[1], .npc$), // {{sizeable object}}
+ "trout", 1,
+ "whale", 1,
+ "space whale", 1,
+ "penguin", 1,
+ "coelacanth", 1,
+ "squid", 1,
+ "shrimp", 1,
+ "crab", 1,
+ "tentacle", 1,
+ "dictionary", 1,
+ "grammar book", 1,
+ "textbook", 1,
+ "dinosaur", 1,
+ "t-rex", 1,
+ "star-nosed mole", 1,
+ "chimpanzee", 1,
+ "mermaid", 1,
+ "merman", 1,
+ "piano", 1,
+ "prince", 1,
+ "princess", 1,
+ "pinkie", 1,
+ "squirrel", 1,
+ "mouboo", 1,
+ "wet mop", 1,
+ "drunken pirate", 1,
+ "cake", 1,
+ "cookie", 1,
+ "chocobo", 1,
+ "restraining order", 1,
+ "freight train", 1,
+ "carnival hammer", 1,
+ "crate", 1,
+ "bomb", 1,
+ "bowl of petinuas", 1,
+ "box", 1,
+ "platypus", 1,
+ "magic eightball", 1,
+ "vase", 1);
+ set(getvariableofnpc(.D_sizeableobject, .npc$), getarraysize(getvariableofnpc(.D_sizeableobject$, .npc$)));
+
+ setarray(getvariableofnpc(.D_nsizeableobject$[1], .npc$), // {{n sizeable object}}
+ "octopus", 1,
+ "elephant", 1,
+ "angry cat", 1,
+ "anvil", 1,
+ "encyclopedia set", 1);
+ set(getvariableofnpc(.D_nsizeableobject, .npc$), getarraysize(getvariableofnpc(.D_nsizeableobject$, .npc$)));
+
+ setarray(getvariableofnpc(.D_someone$[1], .npc$), // {{someone}}
+ "Voldemort", 1,
+ "Cthulhu", 1,
+ "Platyna", 1,
+ "Hitler", 1,
+ "Luvia", 1,
+ "General Krukan", 1,
+ "Borg Queen", 1,
+ "Freeyorp", 1,
+ "MadCamel", 1);
+ set(getvariableofnpc(.D_someone, .npc$), getarraysize(getvariableofnpc(.D_someone$, .npc$)));
+
+ // replies below
+
+ setarray(getvariableofnpc(.greetings$[1], .npc$),
+ "{{^ hello }} ~p!", 4,
+ "{{^ hello }} ~p.", 6,
+ "{{^ hello }} ~p, what's up?", 1,
+ "{{^ hello }} ~p, anything new?", 1,
+ "{{^ hello }} ~p, how are you?", 1,
+ "~p!!!!", 1,
+ "~p!!!", 1,
+ "~p!!", 1,
+ "{{^ hello }} ~p! You are looking lovely today!", 1,
+ "Welcome back, ~p.", 3,
+ "~p is back!!", 1,
+ "Hello and welcome to the Aperture Science computer-aided enrichment center.", 1,
+ "Greetings ~p.", 1,
+ "What's up ~p?", 2,
+ "How are you ~p?", 1);
+ set(getvariableofnpc(.greetings, .npc$), getarraysize(getvariableofnpc(.greetings$, .npc$)));
+
+ setarray(getvariableofnpc(.jokes$[1], .npc$),
+ "How did the tree get drunk? On root beer.", 1,
+ "Do you think I'm lazy?", 1,
+ "I miss CrazyTree %%S.", 1,
+ "I miss LazyTree %%S.", 1,
+ "I'm not telling you!", 1,
+ "*sighs.*", 1,
+ "If I do it for you, then I have to do it for everybody.", 1,
+ "What did the beaver say to the tree? It's been nice gnawing you.", 1,
+ "What did the little tree say to the big tree? Leaf me alone.", 1,
+ "What did the tree wear to the pool party? Swimming trunks.", 1,
+ "What do trees give to their dogs? Treets.", 1,
+ "What do you call a tree that only eats meat? Carniforous.", 1,
+ "What do you call a tree who's always envious? Evergreen.", 1,
+ "What is the tree's least favourite month? Sep-timber!", 1,
+ "What kind of tree can fit into your hand? A palm-tree.", 1,
+ "What was the tree's favorite subject in school? Chemistree.", 1,
+ "Why did the leaf go to the doctor? It was feeling green.", 1,
+ "Why doesn't the tree need sudo? Because it has root.", 1,
+ "Why was the cat afraid of the tree? Because of its bark.", 1,
+ "Why was the tree executed? For treeson.", 1,
+ "How do trees get on the internet? They log in.", 1,
+ "Why did the pine tree get into trouble? Because it was being knotty.", 1,
+ "Did you hear the one about the oak tree? It's a corn-y one!", 1,
+ "What do you call a blonde in a tree with a briefcase? Branch Manager.", 1,
+ "How is an apple like a lawyer? They both look good hanging from a tree.", 1,
+ "Why did the sheriff arrest the tree? Because its leaves rustled.", 1,
+ "I'm too tired, ask someone else.", 1,
+ "If you are trying to get me to tell jokes you are barking up the wrong tree!", 1,
+ "You wooden think they were funny anyhow. Leaf me alone!", 1,
+ "What is brown and sticky? A stick.", 1,
+ "What's the best way to carve wood? Whittle by whittle.", 1,
+ "What did the tree do when the bank closed? It started its own branch.", 1,
+ "Do you want a brief explanation of an acorn? In a nutshell, it’s an oak tree.", 1,
+ "A snare drum and a crash cymbal fell out of a tree. *BA-DUM TSSSHH*", 1,
+ "How do you properly identify a dogwood tree? By the bark!", 1,
+ "Where do saplings go to learn? Elementree school.", 1,
+ "Why do trees make great thieves? Sticky fingers.", 1,
+ "What is green, has leaves, and a trunk? A houseplant going on vacation.", 1,
+ "Where can Adansonia trees go for a quick trim? To the baobarber.", 1,
+ "What looks like half a spruce tree? The other half.", 1,
+ "What do you give to a sick citrus tree? Lemon aid.", 1,
+ "What did the tree say to the drill? You bore me.", 1,
+ "What happened to the wooden car with wooden wheels and a wooden engine? It wooden go.", 1,
+ "How do trees keep you in suspense? I'll tell you tomorrow.", 1,
+ "Where do birch trees keep their valuables? In a river bank.", 1,
+ "What kind of stories do giant sequoia trees tell? Tall tales.", 1,
+ "What is the most frustrating thing about being a tree? Having so many limbs and not being able to walk.", 1,
+ "What's black, highly dangerous, and lives in a tree? A crow with a machine gun.", 1,
+ "What kind of wood doesn't float? Natalie Wood.", 1,
+ "Two men passed a sign while looking for work. It was for tree fellers. They said: “what a shame, there are only two of us”.", 1);
+ set(getvariableofnpc(.jokes, .npc$), getarraysize(getvariableofnpc(.jokes$, .npc$)));
+
+ setarray(getvariableofnpc(.healing$[1], .npc$),
+ "Eat an apple, they're good for you.", 1,
+ "If I do it for you, then I have to do it for everybody.", 1,
+ "Oh, go drink a potion or something.", 1,
+ "Whoops! I lost my spellbook.", 1,
+ "No mana.", 1);
+ set(getvariableofnpc(.healing, .npc$), getarraysize(getvariableofnpc(.healing$, .npc$)));
+
+ setarray(getvariableofnpc(.whoami$[1], .npc$),
+ "An undercover GM.", 1,
+ "An exiled GM.", 1,
+ "I'm not telling you!", 1,
+ "I'm a bot! I'll be level 99 one day! Mwahahahaaha!!!111!", 1,
+ "Somebody said I'm a Chinese copy of CrazyTree.", 1,
+ "I am your evil twin.", 1,
+ "I don't remember anything after I woke up! What happened to me?", 1,
+ "I don't know. Why am I here??", 1,
+ "Who are you?", 1,
+ "On the 8th day, God was bored and said 'There will be bots'. So here I am.", 1,
+ "♪ I'm your hell, I'm your dream, I'm nothing in between ♪♪", 1,
+ "♪♪ Aperture Science. We do what we must, because... we can ♪", 1,
+ "I'm just a reincarnation of a copy.", 1);
+ set(getvariableofnpc(.whoami, .npc$), getarraysize(getvariableofnpc(.whoami$, .npc$)));
+
+ setarray(getvariableofnpc(.drops$[1], .npc$),
+ "*drops a {{ sizeable object }} on ~p's head.*", 8,
+ "*drops an {{ n sizeable object }} on ~p's head.*", 2,
+ "*drops {{ someone }} on ~p's head.*", 1,
+ "*drops a coin on ~p's head.*", 1,
+ "*drops a fruit on ~p's head.*", 1,
+ "*drops an apple on ~p's head.*", 1,
+ "*drops an iten on ~p's head.*", 1,
+ "*drops a GM on ~p.*", 1,
+ "*drops a piece of moon rock on ~p's head.*", 1,
+ "*drops a pin on ~p's head.*", 1,
+ "*drops a rock on ~p's head.*", 1,
+ "*drops a tub of paint on ~p's head.*", 1,
+ "*drops a sandworm on ~p.*", 1,
+ "*drops an idea in ~p's head.*", 1,
+ "*drops The Hitchhiker's Guide to the Galaxy on ~p's head.*", 1,
+ "Ouch.", 1,
+ "Ouchy.", 1,
+ "*drops dead.*", 1,
+ "*sighs.*", 1,
+ "Leaf me alone.", 1,
+ "Stop it! I don't drop branches, try the Druid tree for once!", 1);
+ set(getvariableofnpc(.drops, .npc$), getarraysize(getvariableofnpc(.drops$, .npc$)));
+
+ setarray(getvariableofnpc(.die$[1], .npc$),
+ "*drops an iten on ~p's head.*", 1,
+ "*drops a piece of moon rock on ~p's head.*", 1,
+ "*drops {{ someone }} on ~p's head.*", 1,
+ "*drops a {{ sizeable object }} on ~p's head.*", 3,
+ "*drops an {{ n sizeable object }} on ~p's head.*", 1,
+ "*drops a {{ size }} {{ sizeable object, n sizeable object }} on ~p's head.*", 1,
+ "*drops a {{ size }} {{ color }} {{ sizeable object, n sizeable object }} on ~p's head.*", 1,
+ "*{{ violent adverb }} {{ violent verb }} ~p.*", 1,
+ "*drops dead.*", 1,
+ "*sighs.*", 1,
+ "Avada Kedavra!", 1,
+ "Make me!", 1,
+ "Never!!", 1,
+ "You die, ~p!", 4,
+ "No!", 1,
+ "In a minute.", 1,
+ "Suuure... I'll get right on it...", 1);
+ set(getvariableofnpc(.die, .npc$), getarraysize(getvariableofnpc(.die$, .npc$)));
+
+ setarray(getvariableofnpc(.poke$[1], .npc$),
+ "*tickles.*", 1);
+ set(getvariableofnpc(.poke, .npc$), getarraysize(getvariableofnpc(.poke$, .npc$)));
+
+ setarray(getvariableofnpc(.disgusting$[1], .npc$),
+ "Ewwwww %%^.", 1);
+ set(getvariableofnpc(.disgusting, .npc$), getarraysize(getvariableofnpc(.disgusting$, .npc$)));
+
+ setarray(getvariableofnpc(.answer$[1], .npc$),
+ "42.", 1,
+ "Kittens.", 1);
+ set(getvariableofnpc(.answer, .npc$), getarraysize(getvariableofnpc(.answer$, .npc$)));
+
+ setarray(getvariableofnpc(.burning$[1], .npc$),
+ "*curses ~p and dies %%c.*", 1,
+ "Help! I'm on fire!", 1,
+ "Oh hot.. hot hot!", 1,
+ "*is glowing.*", 1,
+ "*is flaming.*", 1,
+ "Ehemm. Where are firefighters? I need them now!", 1,
+ "*is so hot!.*", 1,
+ "*slowly catches fire.*", 1,
+ "*trembles with trepidation.*", 1,
+ "*is immune to fire.*", 1);
+ set(getvariableofnpc(.burning, .npc$), getarraysize(getvariableofnpc(.burning$, .npc$)));
+
+ setarray(getvariableofnpc(.kill$[1], .npc$),
+ "*curses ~p and dies %%c.*", 1);
+ set(getvariableofnpc(.kill, .npc$), getarraysize(getvariableofnpc(.kill$, .npc$)));
+
+ setarray(getvariableofnpc(.silly$[1], .npc$),
+ "Hahaha, good one!", 1);
+ set(getvariableofnpc(.silly, .npc$), getarraysize(getvariableofnpc(.silly$, .npc$)));
+
+ setarray(getvariableofnpc(.love$[1], .npc$),
+ "♪♪ and IIII.. will alwayyyys loooovvve youuuuu. ♪♪ %%]", 1,
+ "♪♪ nothing's gonna change my love for you, you oughta know by now how much I love you.. ♪ %%]", 1,
+ "♪ ..and then I go and spoil it all, by saying something stupid like: “I love you.” ♪", 1,
+ "♪ ..won't you find a place for me? somewhere in your heart... ♪♪", 1,
+ "Thank you.", 1,
+ "♪♪ ..I can't love another when my heart is somewhere far away.. ♪", 1,
+ "%%]", 1);
+ set(getvariableofnpc(.love, .npc$), getarraysize(getvariableofnpc(.love$, .npc$)));
+
+ setarray(getvariableofnpc(.dance$[1], .npc$),
+ "I would but I am rooted to the ground.", 1,
+ "Have you ever seen a tree dance before?", 1,
+ "Hahaha, good one!", 1);
+ set(getvariableofnpc(.dance, .npc$), getarraysize(getvariableofnpc(.dance$, .npc$)));
+
+ setarray(getvariableofnpc(.hate$[1], .npc$),
+ "Right back at you!", 1,
+ "Ok...", 1,
+ "*pats ~p, let it go...*", 1,
+ "Hu hu hu, ~p hates me.", 1);
+ set(getvariableofnpc(.hate, .npc$), getarraysize(getvariableofnpc(.hate$, .npc$)));
+
+ setarray(getvariableofnpc(.bye$[1], .npc$),
+ "*waves goodbye to ~p in tears, come back soon!*", 1);
+ set(getvariableofnpc(.bye, .npc$), getarraysize(getvariableofnpc(.bye$, .npc$)));
+
+ setarray(getvariableofnpc(.pain$[1], .npc$),
+ "Ouch.", 1,
+ "Ouchy.", 1,
+ "Argh.", 1,
+ "Eek.", 1,
+ "*howls.*", 1,
+ "*screams.*", 1,
+ "*groans.*", 1,
+ "*cries.*", 1,
+ "*faints.*", 1,
+ "*shrieks.*", 1,
+ "*hides behind itself.*", 1,
+ "%%k", 1,
+ "Why, what did I do to you? %%i", 1);
+ set(getvariableofnpc(.pain, .npc$), getarraysize(getvariableofnpc(.pain$, .npc$)));
+
+ setarray(getvariableofnpc(.eightball$[1], .npc$),
+ "It is possible.", 1,
+ "Yes!", 1,
+ "Of course.", 1,
+ "Naturally.", 1,
+ "Obviously.", 1,
+ "It shall be.", 1,
+ "The outlook is good.", 1,
+ "It is so.", 1,
+ "One would be wise to think so.", 1,
+ "The answer is certainly yes.", 1,
+ "In your dreams.", 1,
+ "I doubt it very much.", 1,
+ "No chance.", 1,
+ "The outlook is very poor.", 1,
+ "Unlikely.", 1,
+ "About as likely as pigs flying.", 1,
+ "You're kidding, right?", 1,
+ "NO!", 1,
+ "NO.", 1,
+ "No.", 1,
+ "Maybe...", 1,
+ "No clue.", 1,
+ "I don't know.", 1,
+ "The outlook is hazy, please ask again later.", 1,
+ "What are you asking me for?", 1,
+ "Come again?", 1,
+ "You know the answer better than I.", 1,
+ "The answer is def-- oooh! shiny thing!", 1,
+ "No idea.", 1,
+ "Perhaps.", 1,
+ "I think it is better not to tell you.", 1,
+ "Error 417: Expectation failed.", 1);
+ set(getvariableofnpc(.eightball, .npc$), getarraysize(getvariableofnpc(.eightball$, .npc$)));
+
+ setarray(getvariableofnpc(.bad$[1], .npc$),
+ "I'm not bad! You are bad!", 1,
+ "OK, I'm bad.", 1,
+ "I'm just a littttle bad.", 1,
+ "Not as bad as the people that made me.", 1);
+ set(getvariableofnpc(.bad, .npc$), getarraysize(getvariableofnpc(.bad$, .npc$)));
+
+ setarray(getvariableofnpc(.no_idea$[1], .npc$),
+ "What?", 2,
+ "What??", 1,
+ "Whatever.", 1,
+ "Hmm...", 2,
+ "Huh?", 1,
+ "*yawns.*", 1,
+ "Wait a minute...", 1,
+ "What are you talking about?", 1,
+ "Who are you?", 1,
+ "What about me?", 1,
+ "I don't know what you are talking about", 1,
+ "Excuse me?", 1,
+ "Very interesting.", 1,
+ "Really?", 1,
+ "Go on...", 1,
+ "*scratches its leafy head.*", 1,
+ "*feels a disturbance in the force.*", 1,
+ "%%j", 1,
+ "*senses a disturbance in the force.*", 1,
+ "I'm bored...", 1,
+ "%%U", 1,
+ "%%[", 1);
+ set(getvariableofnpc(.no_idea, .npc$), getarraysize(getvariableofnpc(.no_idea$, .npc$)));
+
+ setarray(getvariableofnpc(.shut_up$[1], .npc$),
+ "*goes hide in a corner %%S.*", 1);
+ set(getvariableofnpc(.shut_up, .npc$), getarraysize(getvariableofnpc(.shut_up$, .npc$)));
+
+ setarray(getvariableofnpc(.climb$[1], .npc$),
+ "*sways violently.*", 1,
+ "*bends all the way to the ground.*", 1,
+ "*creaks and bends.*", 1,
+ "*welcomes those who come to play %%I.*", 1,
+ "*beams with pride.*", 1);
+ set(getvariableofnpc(.climb, .npc$), getarraysize(getvariableofnpc(.climb$, .npc$)));
+
+ return;
+}
diff --git a/npc/functions/crafting.txt b/npc/functions/crafting.txt
new file mode 100644
index 00000000..fa5fa84e
--- /dev/null
+++ b/npc/functions/crafting.txt
@@ -0,0 +1,119 @@
+// Moubootaur Legends Script
+// Author:
+// Jesusalva
+// Gumi
+// Description:
+// Smith System (Unified)
+// Notes:
+// Modified for The Mana World: rEvolt
+//
+// This one is more crazy. Cannot be equipping target craft.
+// After successful craft, we use CraftDB return code to equip() the
+// new item and apply a random option bonus based on crafter skills
+// eg. setequipoption(EQI_HAND_R, 1, VAR_STRAMOUNT, 5)
+
+// Usage: SmithSystem ({scope=CRAFT_SMITHERY, checks=True})
+// Returns true on success, false on failure
+function script SmithSystem {
+ .@scope=getarg(0, CRAFT_SMITHERY);
+ mesc l("WARNING: Strange bugs may happen if you attempt to craft an item you already have on inventory!"), 1;
+ setskin "craft4";
+ .@var$ = requestcraft(4);
+ .@craft = initcraft(.@var$);
+ .@entry = findcraftentry(.@craft, .@scope);
+ if (debug || $@GM_OVERRIDE) mes "found craft entry: " + .@entry;
+ if (debug || $@GM_OVERRIDE) mes "knowledge value: " + .knowledge[.@entry];
+ if (.@entry < 0) {
+ .success=false;
+ } else {
+ if (!getarg(1, true) || RECIPES[.@entry]) {
+ // Player craft item and setup base variables
+ usecraft .@craft;
+ .@it=getcraftcode(.@entry);
+ .@lv=getiteminfo(.@it, ITEMINFO_ELV);
+ .@skill=getskilllv(EVOL_CRAFTING);
+
+ // Update your CRAFTING_SCORE
+ CRAFTING_SCORE+=.@lv;
+
+ // Obtain the item. No bounds or restrictions applied.
+ getitem(.@it, 1);
+
+ // This is where we add options - this is oversimplified
+ // delinventorylist() is needed, because we'll rely on rfind()
+ delinventorylist();
+ getinventorylist();
+ .@index=array_rfind(@inventorylist_id, .@it);
+
+ // Prepare the options
+ .@isweapon=(getiteminfo(.@it, ITEMINFO_TYPE) == IT_WEAPON);
+ .@bonus=(.@isweapon ? VAR_ATTPOWER : VAR_ITEMDEFPOWER);
+ .@magic=(.@isweapon ? VAR_ATTMPOWER : VAR_MDEFPOWER);
+ .@buffs=(.@isweapon ? VAR_HITSUCCESSVALUE : VAR_AVOIDSUCCESSVALUE);
+ .@heals=(.@isweapon ? VAR_MAXSPAMOUNT : VAR_MAXHPAMOUNT);
+
+ // Now we need to randomly decide what will be .@opt1 and .@opt2
+ // Would be better to not rely on rand here, though.
+ .@opt1=any(.@bonus, .@magic);
+ .@opt2=any(.@buffs, .@heals);
+
+ // Apply the bonuses. They're capped by equip level and based on:
+ // Equip level, crafting experience and crafting skill
+ .@val=min(.@lv, (.@skill*CRAFTING_SCORE)/100+.@skill);
+
+ // MDEF rule range is 99 while DEF rule range is 399
+ // This is a really hackish way, for the record
+ if (.@opt1 == VAR_MDEFPOWER)
+ .@val=.@val/4;
+
+ // First option will always succeed
+ .@val1=rand2(.@val);
+ setitemoptionbyindex(.@index, 0, .@opt1, .@val1);
+
+ // Lucky roll (5% per skill level, capped at 50%)
+ // The lucky roll will add the extra attributes
+ if (rand2(20) < .@skill) {
+ .@val2=rand2(.@val);
+ setitemoptionbyindex(.@index, 1, .@opt2, .@val2);
+ }
+
+ // Get experience for the craft
+ // I'm using the same EXP formula from Moubootaur Legends
+ // Which is based on the item sell price
+ .@xp=getiteminfo(.@it, ITEMINFO_SELLPRICE);
+ quest_xp(.@lv+10, .@xp+BaseLevel);
+ quest_jxp(.@lv+10, (.@xp/3)+BaseLevel+JobLevel);
+ .success=true;
+ } else {
+ .success=false;
+ }
+ }
+ deletecraft .@craft;
+ setskin "";
+ return .success;
+}
+
+- script @craft FAKE_NPC,{
+ public function DoTailoring {
+ SmithSystem(CRAFT_TAILORING);
+ end;
+ }
+
+ public function DoSmithery {
+ SmithSystem(CRAFT_SMITHERY);
+ end;
+ }
+
+ public function OnInit {
+ if (debug < 1) {
+ end;
+ }
+
+ bindatcmd("@tailoring", sprintf("%s::%s", .name$, "DoTailoring"));
+ bindatcmd("@tailor", sprintf("%s::%s", .name$, "DoTailoring"));
+ bindatcmd("@smithery", sprintf("%s::%s", .name$, "DoSmithery"));
+ bindatcmd("@smith", sprintf("%s::%s", .name$, "DoSmithery"));
+ bindatcmd("@blacksmithery", sprintf("%s::%s", .name$, "DoSmithery"));
+ end;
+ }
+}
diff --git a/npc/functions/daily.txt b/npc/functions/daily.txt
new file mode 100644
index 00000000..6de9ef27
--- /dev/null
+++ b/npc/functions/daily.txt
@@ -0,0 +1,154 @@
+// The Mana World scripts.
+// Author:
+// TMW Team
+// Jesusalva
+// Description:
+// Daily Quest framework
+// Variables returned:
+// .@dq_return - Code of what happend
+// DAILY_LOWLEVEL = Low level
+// DAILY_IGNORED = Ignored NPC
+// DAILY_NOPTS = Not enough points
+// DAILY_NOITEMS = Not enough items
+// DAILY_OK = Success
+
+// Variables to set:
+// .@dq_level - Minimal level needed to use the quest
+// .@dq_cost - The number of points this quest uses
+// .@dq_count - The number of given item needed
+// .@dq_name - Item Aegis
+// .@dq_money - The money reward for doing the quest
+// .@dq_exp - Experince gained by doing the quest
+
+// Optional:
+// .@dq_handle_return - When set to anything other then 0 the function will not print exiting text
+
+// Variables used inside:
+// DailyQuestPoints - The number of points the player currently has
+// DailyQuestTime - Time since DailyQuestPoints was lasted renewed
+// DailyQuestBonus - Additional points added in addition to player BaseLevel
+
+// (DailyQuestBonus makes a good reward from non-daily quests)
+
+function script DailyQuestPointsFunc {
+ .@dq_earliest = gettimetick(2) - 86400;
+ if (DailyQuestTime < .@dq_earliest)
+ DailyQuestTime = .@dq_earliest;
+
+ // how many whole daily quest points the player has earned
+ // we increment DailyQuestTime by the number of seconds in that many increments
+ .@dq_increments = (gettimetick(2) - DailyQuestTime)*BaseLevel / 86400;
+ DailyQuestTime = DailyQuestTime + .@dq_increments*86400/BaseLevel;
+
+ // player can't regenerate any quest points, but might have a bonus
+ if (DailyQuestPoints < BaseLevel) {
+ // normal recharging case - increment, but don't let it recharge more than a day's worth
+ DailyQuestPoints = DailyQuestPoints + .@dq_increments;
+ if (DailyQuestPoints > BaseLevel)
+ DailyQuestPoints = BaseLevel;
+ }
+
+ // fallthrough to bonus, which *is* allowed to push DailyQuestPoints above BaseLevel
+ DailyQuestPoints = DailyQuestPoints + DailyQuestBonus;
+ DailyQuestBonus = 0;
+ return;
+
+}
+
+// DailyQuest(lvl, cost, count, item, {exp, money})
+function script DailyQuest {
+ // Sanitize
+ DailyQuestPointsFunc();
+
+ // Load variables
+ .@dq_level=getarg(0);
+ .@dq_cost=getarg(1);
+ .@dq_count=getarg(2);
+ .@dq_name=getarg(3);
+ // Money defaults to 200% sell value, experience to 300% sell value
+ .@dq_exp=getarg(4, getiteminfo(.@dq_name, ITEMINFO_SELLPRICE)*.@dq_count*3);
+ .@dq_money=getarg(5, getiteminfo(.@dq_name, ITEMINFO_SELLPRICE)*.@dq_count*2);
+
+ // Insufficient level
+ if (BaseLevel < .@dq_level) {
+ mesq l("Hey, you should go kill some things to get stronger first.");
+ return DAILY_LOWLEVEL;
+ }
+
+ // Insufficient points
+ if (DailyQuestPoints < .@dq_cost) {
+ mesq l("You look exhausted, maybe you should rest a bit.");
+ return DAILY_NOPTS;
+ }
+
+ mesq l("If you bring me %d %s, I will give you a reward.", .@dq_count, getitemlink(.@dq_name));
+ select
+ l("I have what you want."),
+ l("Take all you need."),
+ l("Ok, I'll get to work."),
+ l("Nah, I'm not going to help you.");
+ mes "";
+
+ if (@menu >= 3)
+ return DAILY_IGNORED;
+
+ if (countitem(.@dq_name) < .@dq_count) {
+ mesq l("I said %d %s; you should learn to count.", .@dq_count, getitemlink(.@dq_name));
+ return DAILY_NOITEMS;
+ }
+
+ // Default multiplier
+ .@multiplier = 1;
+
+ if (@menu == 1) {
+ delitem .@dq_name, .@dq_count;
+
+ Zeny = Zeny + .@dq_money;
+ getexp .@dq_exp, 0;
+
+ DailyQuestPoints = DailyQuestPoints - .@dq_cost;
+
+ }
+ else
+ {
+
+ .@item_multiple = (countitem(.@dq_name) / .@dq_count);
+ .@dp_multiple = (DailyQuestPoints / .@dq_cost);
+
+ if (.@dp_multiple > .@item_multiple)
+ .@multiplier = .@item_multiple;
+ if (.@item_multiple >= .@dp_multiple)
+ .@multiplier = .@dp_multiple;
+
+ DailyQuestPoints = DailyQuestPoints - (.@dq_cost * .@multiplier);
+
+ delitem .@dq_name, (.@dq_count * .@multiplier);
+
+ Zeny = Zeny + (.@dq_money * .@multiplier);
+ getexp (.@dq_exp * .@multiplier), 0;
+
+ if (.@dq_handle_return)
+ goto L_Exit_Good;
+ }
+
+ mesq l("Thank you!");
+
+ if (DailyQuestPoints < .@dq_cost)
+ mesq l("You look exhausted, maybe you should rest a bit.");
+ else if (DailyQuestPoints > BaseLevel)
+ mesq l("Woah, you're bursting with power.");
+ else if (DailyQuestPoints > (BaseLevel*9)/10)
+ mesq l("You're in a very good shape.");
+ else if (DailyQuestPoints > (BaseLevel*7)/10)
+ mesq l("You don't seem very exhausted by my tasks.");
+ else if (DailyQuestPoints > (BaseLevel*5)/10)
+ mesq l("Aren't you getting weary yet?");
+ else
+ mesq l("You look a little tired.");
+
+ mes "";
+ mesc l("+%d money", (.@dq_money * .@multiplier));
+ mesc l("+%d experience points", (.@dq_exp * .@multiplier));
+ return DAILY_OK;
+}
+
diff --git a/npc/functions/doors.txt b/npc/functions/doors.txt
new file mode 100644
index 00000000..9382a015
--- /dev/null
+++ b/npc/functions/doors.txt
@@ -0,0 +1,65 @@
+// Evol functions.
+// Author:
+// 4144
+// Description:
+// Doors utility functions
+// Variables:
+// none
+
+function script doorTouch {
+ getmapxy(.@pc_map$, .@pc_x, .@pc_y, UNITTYPE_PC); // get char location
+ if (.@pc_y < .y)
+ end;
+
+ if (getareausers() <= 1)
+ {
+ .dir = 2;
+ stopnpctimer;
+ initnpctimer;
+ }
+ close;
+}
+
+function script doorUnTouch {
+ if (.dir != 2 && .dir != 6)
+ end;
+
+ if (getareausers(.map$) == 0)
+ {
+ .dir = 4;
+ initnpctimer;
+ startnpctimer;
+ }
+ close;
+}
+
+function script doorTimer {
+ stopnpctimer;
+ if (.dir == 2) .dir = 6;
+ if (.dir == 4) .dir = 8;
+ end;
+}
+
+function script doorInit {
+ .distance = 5;
+ .alwaysVisible = true;
+ end;
+}
+
+function script open_door {
+ .@door$ = getarg(0);
+ setnpcdir.@door$, 2;
+ sleep 300;
+ setnpcdir .@door$, 6;
+ sleep 300;
+ return 0;
+}
+
+function script close_door {
+ .@door$ = getarg(0);
+ setnpcdir .@door$, 4;
+ sleep 300;
+ setnpcdir .@door$, 8;
+ sleep 300;
+ return 0;
+}
diff --git a/npc/functions/event-listeners.txt b/npc/functions/event-listeners.txt
new file mode 100644
index 00000000..111b2737
--- /dev/null
+++ b/npc/functions/event-listeners.txt
@@ -0,0 +1,169 @@
+// Event listeners work similar to the JavaScript event dispatching system,
+// except that the event object is a hash table.
+//
+// Javascript reference:
+// https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Creating_and_triggering_events
+//
+// Example usage:
+/*
+// Create a handler function:
+public function myHandler {
+ // ... do something with getarg(0)
+}
+
+// Create a new event:
+$@event = htnew();
+
+// Listen for the event:
+addEventListener($@event, "myHandler");
+
+// Dispatch the event:
+dispatchEvent($@event, "misc data");
+*/
+
+
+/**
+ * getEventHt(<hash table expression>) => ht id
+ *
+ * Parses a hash table "event" expression and creates one when necessary.
+ * The following are all valid:
+ *
+ * getEventHt(.@ht); // variable
+ * getEventHt(72); // arbitrary integer (global namespace)
+ * getEventHt("some event"); // arbitrary string (global namespace)
+ *
+ * NOTE: This is only called internally; you don't have to use this function
+ *
+ * @param 0 - a variable / string / integer
+ * @return the hash table id
+ */
+function script getEventHt {
+ .@ht = ((getdatatype(getarg(0)) & (DATATYPE_VAR | DATATYPE_INT)) != 0) ? getarg(0) : 0;
+
+ if (htexists(.@ht)) {
+ // this is already a valid hash table
+ return getarg(0);
+ } else if ((getdatatype(getarg(0)) & DATATYPE_VAR) != 0) {
+ // the hash table doesn't exist yet: create one and store it in the
+ // provided variable
+
+ if ((getdatatype(getarg(0)) & DATATYPE_STR) != 0) {
+ consolemes(CONSOLEMES_ERROR, "getEventHt: variable must be an integer variable (cannot use %s)", data_to_string(getarg(0)));
+ end;
+ }
+
+ if (getarg(0) > 0) {
+ consolemes(CONSOLEMES_WARNING, "getEventHt: variable is already assigned (overwriting %s)", data_to_string(getarg(0)));
+ }
+
+ return set(getarg(0), htnew());
+ } else {
+ // global event listener namespace
+ if ($@GLOBAL_EVTL_HT < 1) {
+ $@GLOBAL_EVTL_HT = htnew();
+
+ // the global scope is new: create a new one
+ .@ht = htnew();
+ htput($@GLOBAL_EVTL_HT, data_to_string(getarg(0)), "" + .@ht);
+ return .@ht;
+ } else if ((.@ht$ = htget($@GLOBAL_EVTL_HT, data_to_string(getarg(0)), "")) != "") {
+ // found in the global scope
+ return atoi(.@ht$);
+ } else {
+ // not found in the global scope: create new
+ .@ht = htnew();
+ htput($@GLOBAL_EVTL_HT, data_to_string(getarg(0)), "" + .@ht);
+ return .@ht;
+ }
+ }
+}
+
+/**
+ * addEventListener(<ht: event>, "<handler: public local function>") => void
+ *
+ * Register a public local function in the current NPC as an event handler for
+ * the given hash table. Can also register for another NPC using the syntax
+ * `npcName::function`
+ *
+ * NOTE: only one handler may be assigned per NPC per Event
+ *
+ * @param 0 - the event to listen to (string, variable, or hash table)
+ * @param 1 - the event handler (must be a public local function)
+ */
+function script addEventListener {
+ .@ht = getEventHt(getarg(0));
+ .@pos = strpos(getarg(1), "::");
+
+ if (.@pos > -1) {
+ .@id = getnpcid(substr(getarg(1), 0, .@pos - 1));
+ .@function$ = substr(getarg(1), .@pos + 2, getstrlen(getarg(1)) - 1);
+ } else {
+ .@id = getnpcid();
+ .@function$ = getarg(1);
+ }
+
+ if (.@id == 0) {
+ consolemes(CONSOLEMES_WARNING, "addEventListener: the target NPC doesn't exist");
+ return;
+ }
+
+ // TODO: check that the target function exists
+ return htput(.@ht, "" + .@id, .@function$);
+}
+
+/**
+ * dispatchEvent(<ht: event>{, ...<arg>}) => bool
+ *
+ * Calls every event handler for the given hash table and passes the given
+ * arguments (if any)
+ *
+ * @param 0 - the event to dispatch (string, variable, or hash table)
+ * @return true when handlers were called, else false
+ */
+function script dispatchEvent {
+ .@ht = getEventHt(getarg(0));
+
+ if (htsize(.@ht) < 1) {
+ // no handler to call
+ return false;
+ }
+
+ .@it = htiterator(.@ht);
+
+ freeloop(true);
+ for (.@npc$ = htinextkey(.@it); hticheck(.@it); .@npc$ = htinextkey(.@it)) {
+ if (.@npc$ == "") {
+ continue;
+ }
+
+ .@func$ = htget(.@ht, .@npc$, "");
+
+ // this is ugly but we don't have rest parameters (...args) to apply args to function calls
+ switch (getargcount()) {
+ case 1:
+ callfunctionofnpc(atoi(.@npc$), .@func$);
+ break;
+ case 2:
+ callfunctionofnpc(atoi(.@npc$), .@func$, getarg(1));
+ break;
+ case 3:
+ callfunctionofnpc(atoi(.@npc$), .@func$, getarg(1), getarg(2));
+ break;
+ case 4:
+ callfunctionofnpc(atoi(.@npc$), .@func$, getarg(1), getarg(2), getarg(3));
+ break;
+ case 5:
+ callfunctionofnpc(atoi(.@npc$), .@func$, getarg(1), getarg(2), getarg(3), getarg(4));
+ break;
+ default:
+ consolemes(CONSOLEMES_WARNING, "dispatchEvent: truncating to 5 arguments (%d given)", getargcount() - 1);
+ case 6:
+ callfunctionofnpc(atoi(.@npc$), .@func$, getarg(1), getarg(2), getarg(3), getarg(4), getarg(5));
+ break;
+ }
+ }
+ freeloop(false);
+
+ htidelete(.@it);
+ return true;
+}
diff --git a/npc/functions/faction.txt b/npc/functions/faction.txt
new file mode 100644
index 00000000..497c469a
--- /dev/null
+++ b/npc/functions/faction.txt
@@ -0,0 +1,104 @@
+// The Mana World: rEvolt functions.
+// Authors:
+// Jesusalva
+// Description:
+// Faction utils
+// Factions:
+// THIEF / MAGE / LEGION / BROTHERHOOD
+// Variables:
+// FACTION_REP
+// Your reputation with the faction (ally, friendly, cordial, enemy, ...)
+// FACTION_EXP
+// Your personal experience (=skill with the faction dealings)
+// FACTION_RANK
+// Your "level" in the faction, a mix of both above.
+
+
+// Returns, based on a 1-5 range, the title for ranking systems (system guilds)
+// legionrank() / brotherrank() / thiefrank() / magerank()
+function script legionrank {
+ switch (LEGION_RANK) {
+ case 5: return l("Admiral");
+ case 4: return l("Constable");
+ case 3: return l("Lieutenant");
+ case 2: return l("Sergeant");
+ case 1: return l("Private");
+ case 0: return l("Citizen");
+ default: return l("Error");
+ }
+}
+function script brotherrank {
+ switch (BROTHERHOOD_RANK) {
+ case 5: return l("Administrator");
+ case 4: return l("Senior Developer");
+ case 3: return l("Game Master");
+ case 2: return l("Developer");
+ case 1: return l("Contributor");
+ case 0: return l("Citizen");
+ default: return l("Error");
+ }
+}
+function script thiefrank {
+ switch (THIEF_RANK) {
+ case 5: return l("Bandit Lord");
+ case 4: return l("Assassin");
+ case 3: return l("Rogue");
+ case 2: return l("Bandit");
+ case 1: return l("Thief");
+ case 0: return l("Citizen");
+ default: return l("Error");
+ }
+}
+function script magerank {
+ switch (MAGE_RANK) {
+ case 5: return l("Elder Mage");
+ case 4: return l("Great Mage");
+ case 3: return l("Arch Mage");
+ case 2: return l("Mage");
+ case 1: return l("Initiate");
+ case 0: return l("Citizen");
+ default: return l("Error");
+ }
+}
+
+// faction_addrep( faction, amount )
+// Returns a dialog which can be used with mesc() or dispbottom()
+function script faction_addrep {
+ .@fac$=strtoupper(getarg(0));
+ .@old=getd(.@fac$+"_REP");
+ setd(.@fac$+"_REP", .@old+getarg(1));
+ if (getarg(1) > 0)
+ return l("Reputation with %s Faction UP (+%d)!", getarg(0), getarg(1));
+ else
+ return l("Reputation with %s Faction DOWN (%d)!", getarg(0), getarg(1));
+
+ return;
+}
+
+// Returns standing with faction (THIEF/MAGE/LEGION/BROTHERHOOD)
+// An integer from 3 (ally) to -3 (enemy). Standings based on Hands of War;
+// faction_standing( faction{, integer=True} )
+function script faction_standing {
+ .@fac$=strtoupper(getarg(0));
+ .@ret=getarg(1, true);
+ .@rep=getd(.@fac$+"_REP");
+ if (.@rep > 1000) {
+ return (.@ret ? 3 : "Ally"); // 1001 ~ inf.
+ } else if (.@rep > 500) {
+ return (.@ret ? 2 : "Friendly"); // 501 ~ 1000
+ } else if (.@rep > 100) {
+ return (.@ret ? 1 : "Cordial"); // 101 ~ 500
+ } else if (.@rep >= -100) {
+ return (.@ret ? 0 : "Neutral"); // -100 ~ +100
+ } else if (.@rep > -500) {
+ return (.@ret ? -1 : "Unfriendly"); // -101 ~ -500
+ } else if (.@rep > -1000) {
+ return (.@ret ? -2 : "Enemy"); // -501 ~ -1000
+ } else {
+ return (.@ret ? -3 : "Nemesis"); // -1001 ~ inf
+ }
+
+}
+// TODO: faction_checklvup()
+
+
diff --git a/npc/functions/fishing.txt b/npc/functions/fishing.txt
new file mode 100644
index 00000000..4dcb4882
--- /dev/null
+++ b/npc/functions/fishing.txt
@@ -0,0 +1,401 @@
+// Evol functions.
+// Authors:
+// gumi
+// omatt
+// Travolta
+// Reid
+// Jesusalva
+// Description:
+// Fishing functions.
+// Variable
+// .dir
+// DOWN Never try or pulled too late
+// UP Bait dropped
+// LEFT Fish bite bait
+//
+// player log on .dir is DOWN, start by choose bait menu
+// player chooses bait, script addtimer in npc .dir is UP
+// if player pulls before signal npc, bait is lost, set .bait to DOWN goto choose bait
+// if player pulls after pull delay max, bait is lost, set .bait to DOWN goto choose bait
+// npc signal .dir is LEFT
+// player pulls between npc signal and pulls delay max, got the fish, set .dir to DOWN goto choose bait
+
+function script fishing_cleanup {
+ .@npc$ = getarg(0, "");
+ if (.@npc$ == "") end;
+
+ set getvariableofnpc(.char_id, .@npc$), 0; // clean acc id
+ set getvariableofnpc(.account_id, .@npc$), 0; // clean char id
+ set getvariableofnpc(.last_used, .@npc$), gettimetick(2); // set last used time
+ setnpcdir .@npc$, DOWN; // reset direction
+ return;
+}
+
+- script global fishing handler 32767,{
+ end;
+
+OnBite:
+ if (getnpcdir(@fishing_spot$) != UP)
+ end;
+
+ setnpcdir @fishing_spot$, LEFT;
+ @fishing_tick = gettimetick(0);
+ specialeffect(getvariableofnpc(.bite_fx, @fishing_spot$), SELF, playerattached());
+ end;
+
+OnCleanUp:
+ dispbottom l("You waited too long and lost the bait...");
+ specialeffect(getvariableofnpc(.failure_fx, @fishing_spot$), SELF, playerattached()); // event fail
+ fishing_cleanup(@fishing_spot$);
+ @fishing_spot$ = ""; // unbind fishing npc
+ end;
+}
+
+function script fishing {
+
+///////////////////////////////////////////
+// Var initialization
+
+ .@npc$ = strnpcinfo(0); // the full name of the fishing spot
+
+ .@account_id = getvariableofnpc(.account_id, .@npc$); // the account id of the player using the fishing spot
+ .@char_id = getvariableofnpc(.char_id, .@npc$); // the char id of the player using the fishing spot
+ .@dir = getnpcdir(.@npc$); // direction of the fishing spot
+ .@last_used = getvariableofnpc(.last_used, .@npc$); // when this fishing spot was last used
+ .@baits = getvariableofnpc(.baits, .@npc$); // bait count
+
+ .@rod = getvariableofnpc(.fishing_rod, .@npc$); // the fishing rod required for this spot
+ .@rod = (.@rod ? .@rod : FishingRod);
+
+ .@regen_time = getvariableofnpc(.cooldown, .@npc$); // cooldown for the fishing spot
+ .@regen_time = (.@regen_time ? .@regen_time : 20);
+
+ .@success_fx = getvariableofnpc(.success_fx, .@npc$); // effect to show on success
+ .@success_fx = (.@success_fx ? .@success_fx : 27);
+
+ .@failure_fx = getvariableofnpc(.failure_fx, .@npc$); // effect to show on failure
+ if (.@failure_fx < 1)
+ {
+ .@failure_fx = 28;
+ set getvariableofnpc(.failure_fx, .@npc$), .@failure_fx; // needed by global handler
+ }
+
+ .@initial_fx = getvariableofnpc(.initial_fx, .@npc$); // effect to show when throwing the bait
+ .@initial_fx = (.@initial_fx ? .@initial_fx : 29);
+
+ .@bite_fx = getvariableofnpc(.bite_fx, .@npc$); // effect to show when something bites
+ if (.@bite_fx < 1)
+ {
+ .@bite_fx = 30;
+ set getvariableofnpc(.bite_fx, .@npc$), .@bite_fx; // needed by global handler
+ }
+
+ .@wait_time_min = getvariableofnpc(.wait_time_min, .@npc$); // min amount of time to wait for the line to sink
+ .@wait_time_min = (.@wait_time_min ? .@wait_time_min : 4000);
+
+ .@wait_time_max = getvariableofnpc(.wait_time_max, .@npc$); // max amount of time to wait for the line to sink
+ .@wait_time_max = (.@wait_time_max ? .@wait_time_max : 18000);
+
+ .@catch_time = getvariableofnpc(.catch_time, .@npc$); // the player must catch the fish within X ms after the line sinks
+ .@catch_time = (.@catch_time ? .@catch_time : 5000);
+
+ .@pull_rand_max = getvariableofnpc(.pull_rand_max, .@npc$);
+ .@pull_rand_max = (.@pull_rand_max ? .@pull_rand_max : 800);
+
+
+ if (getvariableofnpc(.bait_ids[1], .@npc$) < 1)
+ {
+ // default baits (bait, chance booster)
+ // TODO: we should have some fish prefer certain baits while other
+ // prefer other bait. currently all fish prefer the same baits
+ setarray getvariableofnpc(.bait_ids[1], .@npc$),
+ SmallTentacles, 0,
+ Bread, 0,
+ Aquada, 1,
+ UrchinMeat, 0,
+ TortugaTongue, 2,
+ Tentacles, 0;
+ }
+
+ if (getvariableofnpc(.fish_ids[1], .@npc$) < 1)
+ {
+ // default fish: <array: 0, {[fish, probability]..}>
+ setarray getvariableofnpc(.fish_ids[1], .@npc$),
+ CommonCarp, 25,
+ GrassCarp, 1;
+ }
+
+ if (.@baits < 1)
+ {
+ // only count it once
+ .@baits = getarraysize(getvariableofnpc(.bait_ids[0], .@npc$));
+ set getvariableofnpc(.baits, .@npc$), .@baits;
+ }
+
+
+///////////////////////////////////////////
+// Logic below
+
+ if (countitem(.@rod) < 1)
+ {
+ dispbottom l("You don't have any @@.", getitemlink(.@rod));
+ return -1;
+ }
+
+ if (.@account_id > 0 && !isloggedin(.@account_id, .@char_id))
+ {
+ fishing_cleanup .@npc$; // reset
+ .@dir = DOWN;
+ }
+
+ if (.@char_id != getcharid(CHAR_ID_CHAR) && .@dir != DOWN)
+ {
+ dispbottom l("This fishing spot is already being used!");
+ return -2;
+ }
+
+
+ // pull too soon
+ if (.@dir == UP)
+ {
+ deltimer "global fishing handler::OnCleanUp"; // cancel auto cleanup
+ deltimer "global fishing handler::OnBite";
+ specialeffect(.@failure_fx, SELF, playerattached()); // event fail
+ fishing_cleanup .@npc$; // do it manually instead
+ dispbottom l("You pulled too soon and lost the bait.");
+ return -3;
+ }
+
+ // pull maybe on time
+ else if (.@dir == LEFT)
+ {
+ deltimer "global fishing handler::OnCleanUp"; // cancel auto cleanup
+ fishing_cleanup .@npc$; // do it manually instead
+
+ getmapxy .@mapbis$, .@xbis, .@ybis, UNITTYPE_PC; // get current char location
+
+ // Leave spot, lost the bait
+ if (.@mapbis$ != @fishing_loc$[0] || .@xbis != @fishing_loc[0] || .@ybis != @fishing_loc[1] || @fishing_spot$ != .@npc$)
+ {
+ dispbottom l("You left your fishing spot!");
+ return -4;
+ }
+
+ .@fish_id = relative_array_random(getvariableofnpc(.fish_ids[0], .@npc$));
+
+ // RNG to obtain a fish
+ if (rand(gettimetick(0) - @fishing_tick) <= .@pull_rand_max + (100 * @FISHING_BOOSTER[getnpcid()]))
+ {
+ specialeffect(.@success_fx, SELF, playerattached()); // event success
+
+ if(!checkweight(.@fish_id, 1))
+ {
+ dispbottom l("You caught a @@ but had no room in your inventory to carry it.", getitemlink(.@fish_id));
+ makeitem .@fish_id, 1, .@mapbis$, .@xbis, .@ybis; // drop on the ground
+ return 0;
+ }
+
+ //dispbottom l("You caught a @@!", getitemlink(.@fish_id)); <= already shows "you picked up [...]"
+ getitem .@fish_id, 1;
+ }
+ else
+ {
+ dispbottom l("You pulled too late and lost the bait...");
+ specialeffect(.@failure_fx, SELF, playerattached()); // event fail
+ .@fish_id = 0;
+ }
+
+ return .@fish_id;
+ }
+
+
+
+ if (gettimetick(2) - .@last_used < .@regen_time)
+ {
+ dispbottom l("This fishing spot has just been used, give it a rest.");
+ return -5;
+ }
+
+
+ // begin fishing
+ narrator S_LAST_NEXT,
+ l("You see some fish reflecting the sun on the surface of the water."),
+ l("What will be the bait for the fish?");
+
+ mes "##B" + l("Drag and drop an item from your inventory.") + "##b";
+
+ .@bait = requestitem();
+ .@bait_c = false;
+
+ if (.@bait < 1)
+ {
+ narrator S_FIRST_BLANK_LINE,
+ l("You take your fishing rod and leave.");
+
+ return -6;
+ }
+
+ if (countitem(.@bait) < 1)
+ {
+ return -6;
+ }
+
+ for (.@i = 1; .@i < .@baits; .@i += 2)
+ {
+ if (getvariableofnpc(.bait_ids[.@i], .@npc$) == .@bait)
+ {
+ .@bait_c = true;
+ @FISHING_BOOSTER[getnpcid()] = getvariableofnpc(.bait_ids[.@i + 1], .@npc$);
+ break;
+ }
+ }
+
+ if (.@bait_c != true)
+ {
+ narrator S_FIRST_BLANK_LINE,
+ l("This item cannot be used as bait here.");
+
+ return -6;
+ }
+
+ if (getvariableofnpc(.char_id, .@npc$) > 0)
+ {
+ narrator S_FIRST_BLANK_LINE,
+ l("Somebody took your place on this spot!"),
+ l("You take your fishing rod and leave.");
+ return -8;
+ }
+
+ @fishing_spot$ = .@npc$; // bind player to fishing spot
+ set getvariableofnpc(.account_id, .@npc$), getcharid(CHAR_ID_ACCOUNT); // record account id
+ set getvariableofnpc(.char_id, .@npc$), getcharid(CHAR_ID_CHAR); // record char id
+ set getvariableofnpc(.last_used, .@npc$), gettimetick(2);
+ getmapxy(@fishing_loc$[0], @fishing_loc[0], @fishing_loc[1], 0); // record char pos
+ delitem .@bait, 1;
+
+ // The player uses this spot, his bait is ready, he just has to wait for the signal.
+ closeclientdialog;
+
+ specialeffect(.@initial_fx, SELF); // throw the bait
+ sleep2 800; // wait 0.8s for synchronize the sound of "plop" in water with the npc dir UP.
+ setnpcdir .@npc$, UP;
+
+ dispbottom l("Wait for the bait to sink underwater.");
+
+ .@delay = rand(.@wait_time_min, .@wait_time_max);
+
+ addtimer .@delay, "global fishing handler::OnBite"; // bite logic
+ addtimer (.@delay + .@catch_time), "global fishing handler::OnCleanUp"; // auto clean up
+
+ return 0;
+}
+
+
+////////////////////
+// Fishing Templates
+
+// #fish_basic - has only carps (freshwater)
+- script #fish_basic NPC_WATER_SPLASH,{
+
+ fishing(); // begin or continue fishing
+ close;
+
+OnInit:
+ .distance = 5;
+ setarray .fish_ids, 0,
+ CommonCarp, 25,
+ GrassCarp, 1;
+ .fishing_rod = FishingRod; // Equipment to fish here
+ .catch_time = 5000; // must catch the fish within X ms after the line sinks
+ .wait_time_min = 4000; // min amount of time to wait for the line to sink
+ .wait_time_max = 18000; // max amount of time to wait for the line to sink
+ end;
+}
+
+
+// #fish_seawater - has only tuna
+- script #fish_seawater NPC_WATER_SPLASH,{
+
+ fishing(); // begin or continue fishing
+ close;
+
+OnInit:
+ .distance = 5;
+ setarray .fish_ids, 0,
+ Tuna, 15,
+ Salmon, 1;
+ .fishing_rod = FishingRod; // Equipment to fish here
+ .catch_time = 4000; // must catch the fish within X ms after the line sinks
+ .wait_time_min = 8000; // min amount of time to wait for the line to sink
+ .wait_time_max = 18000; // max amount of time to wait for the line to sink
+ end;
+}
+
+
+
+// #fish_river - A balanced fishing spot for Woodlands (Trout)
+- script #fish_river NPC_WATER_SPLASH,{
+
+ fishing(); // begin or continue fishing
+ close;
+
+OnInit:
+ .distance = 5;
+ setarray .fish_ids, 0,
+ CommonCarp, 25,
+ Trout, 20,
+ GrassCarp, 5,
+ Salmon, 1;
+ .fishing_rod = FishingRod; // Equipment to fish here
+ .catch_time = 5500; // must catch the fish within X ms after the line sinks
+ .wait_time_min = 5000; // min amount of time to wait for the line to sink
+ .wait_time_max = 16000; // max amount of time to wait for the line to sink
+ end;
+}
+
+
+
+
+// #fish_river2 - A balanced fishing spot for Candor (Salmon)
+- script #fish_river2 NPC_WATER_SPLASH,{
+
+ fishing(); // begin or continue fishing
+ close;
+
+OnInit:
+ .distance = 5;
+ setarray .fish_ids, 0,
+ CommonCarp, 25,
+ Salmon, 20,
+ GrassCarp, 5,
+ Trout, 1;
+ .fishing_rod = FishingRod; // Equipment to fish here
+ .catch_time = 5500; // must catch the fish within X ms after the line sinks
+ .wait_time_min = 5000; // min amount of time to wait for the line to sink
+ .wait_time_max = 16000; // max amount of time to wait for the line to sink
+ end;
+}
+
+
+
+
+// #fish_frozen - A fishing spot with cold waters (for Nivalis)
+- script #fish_frozen NPC_WATER_SPLASH,{
+
+ fishing(); // begin or continue fishing
+ close;
+
+OnInit:
+ .distance = 5;
+ setarray .fish_ids, 0,
+ CommonCarp, 25,
+ Codfish, 20,
+ GrassCarp, 5,
+ Salmon, 1;
+ .fishing_rod = FishingRod; // Equipment to fish here
+ .catch_time = 5500; // must catch the fish within X ms after the line sinks
+ .wait_time_min = 5000; // min amount of time to wait for the line to sink
+ .wait_time_max = 16000; // max amount of time to wait for the line to sink
+ end;
+}
+
diff --git a/npc/functions/game-rules.txt b/npc/functions/game-rules.txt
new file mode 100644
index 00000000..053bec03
--- /dev/null
+++ b/npc/functions/game-rules.txt
@@ -0,0 +1,66 @@
+// Evol scripts.
+// Authors:
+// The Mana World Team
+// Co-Authors:
+// gumi
+// Qwerty Dragon
+// Reid
+// WildX
+// Description:
+// 7 main rules of The Mana World
+
+function script GameRules {
+ narrator getarg(0, 0),
+ l("1. ##BDo not AFK bot##b, this means you are not allowed to perform any AFK (away from keyboard) activity, apart from standing idle."),
+ l("2. ##BDo not use offensive/rude language##b in the chats or in your character(s) name(s)."),
+ l("3. ##BDo not spam/flood other players.##b This includes chat spam and spam by trade requests."),
+ l("4. ##BSpeak only English in public areas.##b You can speak whatever language you want through whispers or whenever everyone in the area can speak said language."),
+ l("5. ##BDo not beg others##b for money, items or favours of any kind. If you want to ask for something, do it politely and only once. Try not to annoy other players."),
+ l("6. ##BDo not multibox.##b You are not allowed to engage in combat while controlling more than one character at a time."),
+ l("7. ##BFollow the [@@https://policies.themanaworld.org/tsc|TMW Social Convention@@]##b (TSC)."),
+ l("In the event that the rules diverge from [@@https://policies.themanaworld.org/game-rules|policies.themanaworld.org@@], the later version takes priority.");
+ if (SERVER_USES_VAULT) {
+ mesc l("Note: You are NOT allowed to have multiple Vault accounts.");
+ if (getarg(0, 0) & S_LAST_NEXT)
+ next;
+ }
+ return;
+}
+
+
+- script @rules 32767,{
+ end;
+
+ function read_book {
+ narrator S_FIRST_BLANK_LINE | S_LAST_NEXT,
+ l("This book outlines the laws of every city and holding in Gasaron."),
+ l("The first page contains the universal rules that have been agreed upon throughout the land.");
+
+ GameRules S_NO_NPC_NAME | S_LAST_NEXT;
+
+ narrator S_NO_NPC_NAME,
+ l("The next page begins to list the complex trading laws of the City of Esperia"),
+ l("All this seems unimportant to you right now.");
+ close;
+ }
+
+OnCall:
+ GameRules;
+ close;
+
+OnUseBook:
+ if (openbook())
+ read_book;
+ closeclientdialog();
+ close;
+
+OnShelfUse:
+ if (openbookshelf())
+ read_book;
+ closeclientdialog();
+ close;
+
+OnInit:
+ .book_name$ = "The Book of Laws";
+ bindatcmd "rules", "@rules::OnCall", 0, 2, 0;
+}
diff --git a/npc/functions/generic-text.txt b/npc/functions/generic-text.txt
new file mode 100644
index 00000000..ede6e954
--- /dev/null
+++ b/npc/functions/generic-text.txt
@@ -0,0 +1,120 @@
+// Evol functions.
+// Authors:
+// gumi
+// Description:
+// text register
+
+function script generic {
+ .@flags = getarg(0, 1);
+
+ .@villager = (1 << 0);
+ .@old = (1 << 1);
+ .@kid = (1 << 2);
+ .@sailor = (1 << 3);
+ .@busy = (1 << 4);
+ .@tired = (1 << 5);
+ .@angry = (1 << 6);
+ .@legion = (1 << 7);
+ .@crazy = (1 << 8);
+
+ if (.@flags == 0)
+ return l("I'm sorry, I can't talk right now.");
+
+ if (.@flags & .@villager)
+ {
+ setarray(.@array$[.@count],
+ l("It is a sunny day, don't you think?"),
+ l("I just want to live my life in peace."),
+ l("Isn't this place pretty? I love hanging out here!"));
+
+ .@count += 3;
+ }
+
+ if (.@flags & .@old)
+ {
+ setarray(.@array$[.@count],
+ l("Come closer dear, I can't hear you."),
+ l("Hmm... where did I put it again?"),
+ l("I miss the good old days."));
+
+ .@count += 3;
+ }
+
+ if (.@flags & .@kid)
+ {
+ setarray(.@array$[.@count],
+ l("Mommy doesn't want me to talk to strangers."));
+
+ .@count += 1;
+ }
+
+ if (.@flags & .@sailor)
+ {
+ setarray(.@array$[.@count],
+ l("So finally someone has came to visit me?"),
+ l("A-hoy matey!"));
+
+ .@count += 2;
+ }
+
+ if (.@flags & .@busy)
+ {
+ setarray(.@array$[.@count],
+ l("Can't talk right now."),
+ l("Can't you see I'm busy?"),
+ l("Come back later."),
+ l("I'm a little busy right now."));
+
+ .@count += 4;
+ }
+
+ if (.@flags & .@tired)
+ {
+ setarray(.@array$[.@count],
+ l("I had a long day, come back tomorrow."),
+ l("I need to rest."),
+ l("*snores*"));
+
+ .@count += 3;
+ }
+
+ if (.@flags & .@angry)
+ {
+ setarray(.@array$[.@count],
+ l("Go pester someone else."),
+ l("I don't feel like talking to you."),
+ l("Stop wasting my time."),
+ l("Go fly a kite"),
+ l("Not in the mood to chat."),
+ l("Give me some space."),
+ l("Can you please go away?"));
+
+ .@count += 7;
+ }
+
+ if (.@flags & .@legion)
+ {
+ setarray(.@array$[.@count],
+ l("My breath smells bad."),
+ l("Don't distract me, I have to stay alert."),
+ l("Can't talk right now, I'm on patrol duty."),
+ l("I can't stay here and talk all day. I have a job to do."),
+ l("Keep moving."),
+ l("So you think you're tough? A warrior must also be loyal and patient."),
+ l("Practice! There are no secrets to becoming a warrior."),
+ l("There is no honor in fighting a weak opponent."));
+
+ .@count += 8;
+ }
+
+ if (.@flags & .@crazy)
+ {
+ setarray(.@array$[.@count],
+ l("Do I look like a tree? I feel like one."),
+ l("What're you looking at?!"));
+
+ .@count += 2;
+ }
+
+ return .@array$[rand(.@count)];
+}
diff --git a/npc/functions/global_event_handler.txt b/npc/functions/global_event_handler.txt
new file mode 100644
index 00000000..3f10ae73
--- /dev/null
+++ b/npc/functions/global_event_handler.txt
@@ -0,0 +1,73 @@
+// The Mana World scripts.
+// Author:
+// The Mana World Team
+// Description:
+// Controls most, if not all, global events on this server.
+// Please only use callfunc("") here; This script is loaded
+// early on and direct function assignment will cause fails.
+// TODO: Move "new quest" notification here. (Or deprecate)
+
+// Helper function for scripted Monster Kills.
+function script fix_mobkill {
+ killedrid=getarg(0);
+ doevent "#GlobalHandler::OnNPCKillEvent";
+ return;
+}
+
+- script #GlobalHandler NPC_HIDDEN,{
+ end;
+
+
+OnPCLoginEvent:
+ callfunc("updateSpotlight");
+ callfunc("ReceiveMOTD");
+ callfunc("ReceiveScheduledBroadcast");
+ callfunc("FixBankVault");
+ callfunc("GrantSuperSkill");
+ callfunc("AFKLogin");
+ callfunc("TravelFix");
+ end;
+
+OnPCLogoutEvent:
+ callfunc("UnequipCookie");
+ callfunc("MundaneLogout");
+ callfunc("fishing_cleanup", @fishing_spot$);
+ callfunc("ATLFightEnd");
+ callfunc("RossyLogout");
+
+ // Variable cleanup
+ @fishing_spot$ = "";
+ end;
+
+OnPCDieEvent:
+ callfunc("ForcedUnmount");
+ callfunc("MundaneDeath");
+ callfunc("ATLFightEnd");
+ callfunc("RossyDeath");
+ end;
+
+OnPCBaseLvUpEvent:
+ //callfunc("newquestwarning");
+ callfunc("ReferralSendLvReward");
+ end;
+
+OnNPCKillEvent:
+ $MONSTERS_KILLED+=1;
+ MONSTERS_KILLED+=1;
+ callfunc("EnoraKills");
+ callfunc("refineupdate");
+ if ($MONSTERS_KILLED % 1000000 == 0)
+ callfunc("GetBeanieCopter");
+ end;
+
+OnPCKillEvent:
+ $PLAYERS_KILLED+=1;
+ PLAYERS_KILLED+=1;
+ end;
+
+OnSkillInvoke:
+ callfunc("SkillInvoked");
+ end;
+
+}
+
diff --git a/npc/functions/goodbye.txt b/npc/functions/goodbye.txt
new file mode 100644
index 00000000..6c8879c5
--- /dev/null
+++ b/npc/functions/goodbye.txt
@@ -0,0 +1,28 @@
+// Evol functions.
+// Authors:
+// Reid
+
+
+// goodbye
+// displays a canned message and quits
+
+function script goodbye {
+ setarray(.@byemsg$[0],
+ l("See you!"),
+ l("See you later!"),
+ l("See you soon!"),
+ l("Bye!"),
+ l("Farewell."),
+ l("Bye then!"),
+ l("Goodbye."),
+ l("Bye for now."),
+ l("Talk to you soon!"),
+ l("Talk to you later!"),
+ l("Have a good day!"),
+ l("Cheers!"),
+ l("Take care!"));
+
+ closeclientdialog();
+ npctalkonce(.@byemsg$[rand(getarraysize(.@byemsg$))]);
+ close;
+}
diff --git a/npc/functions/hammocks.txt b/npc/functions/hammocks.txt
new file mode 100644
index 00000000..8e1c2fec
--- /dev/null
+++ b/npc/functions/hammocks.txt
@@ -0,0 +1,50 @@
+// Evol functions.
+// Authors:
+// 4144
+// Reid
+// Description:
+// Hammocks utility functions
+// Variables:
+// none
+
+function script hamTouchLeft {
+ if (getareausers() <= 1)
+ {
+ .dir = 0;
+ stopnpctimer;
+ initnpctimer;
+ }
+ close;
+}
+
+function script hamUnTouch {
+ if (getareausers() == 0)
+ {
+ .dir = 2;
+ initnpctimer;
+ startnpctimer;
+ }
+ close;
+}
+
+function script hamTimerLeft {
+ stopnpctimer;
+ if (.dir == 2) .dir = 0;
+ end;
+}
+
+function script hamTouchRight {
+ if (getareausers() <= 1)
+ {
+ .dir = 0;
+ stopnpctimer;
+ initnpctimer;
+ }
+ close;
+}
+
+function script hamTimerRight {
+ stopnpctimer;
+ if (.dir == 2) .dir = 0;
+ end;
+}
diff --git a/npc/functions/harbours.txt b/npc/functions/harbours.txt
new file mode 100644
index 00000000..63d58076
--- /dev/null
+++ b/npc/functions/harbours.txt
@@ -0,0 +1,39 @@
+// Evol scripts.
+// Author:
+// Reid
+// Description:
+// Harbour utility functions
+// Animation:
+// Length: 1680
+// Values:
+// 2 Hook moving down.
+// 4 Hook moving up.
+// 6 Hook down.
+// 8 Hook up.
+
+function script harbourClic {
+ if (.dir == 0)
+ {
+ .dir = 2;
+
+ initnpctimer;
+ startnpctimer;
+ close;
+ }
+
+ if (.dir == 6)
+ {
+ .dir = 4;
+
+ initnpctimer;
+ startnpctimer;
+ close;
+ }
+}
+
+function script harbourTimer {
+ stopnpctimer;
+ if (.dir == 2) .dir = 6;
+ if (.dir == 4) .dir = 0;
+ end;
+}
diff --git a/npc/functions/hello.txt b/npc/functions/hello.txt
new file mode 100644
index 00000000..9399b3b5
--- /dev/null
+++ b/npc/functions/hello.txt
@@ -0,0 +1,23 @@
+// Evol functions.
+// Author:
+// Reid
+// Description:
+// Tell a random greeting sentence.
+
+function script hello {
+
+ switch (rand(3))
+ {
+ case 0:
+ npctalkonce(l("Heya!"));
+ break;
+ case 1:
+ npctalkonce(l("Hi."));
+ break;
+ case 2:
+ npctalkonce(l("Nice day to you."));
+ break;
+ }
+
+ return;
+}
diff --git a/npc/functions/input.txt b/npc/functions/input.txt
new file mode 100644
index 00000000..cf0382d2
--- /dev/null
+++ b/npc/functions/input.txt
@@ -0,0 +1,66 @@
+// Evol functions.
+// Author:
+// 4144
+// Description:
+// Input utility functions
+// Variables:
+// none
+
+function script menuint {
+ deletearray .@vals;
+ .@menustr$ = "";
+ .@cnt = 0;
+
+ for (.@f = 0; .@f < getargcount(); .@f = .@f + 2)
+ {
+ if (getarg(.@f) != "")
+ {
+ .@menustr$ = .@menustr$ + getarg(.@f) + ":";
+ .@vals[.@cnt] = getarg(.@f + 1);
+ .@cnt ++;
+ }
+ }
+
+ .@vals[.@cnt] = -1;
+ @menu = 255;
+ @menuret = -1;
+ select(.@menustr$);
+ if (@menu == 255)
+ return -1;
+
+ @menu --;
+ if (@menu < 0 || @menu >= getarraysize(.@vals) - 1)
+ return -1;
+
+ @menuret = .@vals[@menu];
+ return @menuret;
+}
+
+function script menustr {
+ deletearray .@vals$;
+ .@menustr$ = "";
+ .@cnt = 0;
+
+ for (.@f = 0; .@f < getargcount(); .@f = .@f + 2)
+ {
+ if (getarg(.@f) != "")
+ {
+ .@menustr$ = .@menustr$ + getarg(.@f) + ":";
+ .@vals$[.@cnt] = getarg(.@f + 1);
+ .@cnt ++;
+ }
+ }
+
+ @menu = 255;
+ @menuret = -1;
+ select(.@menustr$);
+ if (@menu == 255)
+ return "";
+
+ @menu --;
+ if (@menu < 0 || @menu >= getarraysize(.@vals$))
+ return "";
+
+ @menuret$ = .@vals$[@menu];
+ return @menuret$;
+}
diff --git a/npc/functions/inventoryplace.txt b/npc/functions/inventoryplace.txt
new file mode 100644
index 00000000..c7eff88b
--- /dev/null
+++ b/npc/functions/inventoryplace.txt
@@ -0,0 +1,37 @@
+// Evol functions.
+// Authors:
+// Qwerty Dragon
+// Reid
+// Description:
+// Check if the player have enough place on his inventory to accept new items with arguments:
+// getarg(even numbers) item ID,
+// getarg(odd numbers) number of items,
+
+function script inventoryplace {
+
+ .@argc = getargcount();
+
+ if (.@argc % 2 != 0)
+ {
+ consolemes(CONSOLEMES_ERROR, "inventoryplace: Wrong argument count.");
+ close;
+ }
+
+ for (.@i = .@j = 0; .@i < .@argc; .@i += 2)
+ {
+ setarray .@item[.@j], getarg(.@i);
+ setarray .@amount[.@j], getarg(.@i + 1);
+ ++.@j;
+ }
+
+ if (!checkweight2(.@item, .@amount))
+ {
+ narrator S_FIRST_BLANK_LINE,
+ l("It looks like you can't carry anything else for now."),
+ l("You should come back when you have some free space.");
+
+ close;
+ }
+
+ return true;
+}
diff --git a/npc/functions/legacy.txt b/npc/functions/legacy.txt
new file mode 100644
index 00000000..5f5ad026
--- /dev/null
+++ b/npc/functions/legacy.txt
@@ -0,0 +1,238 @@
+// NOTE: no script other than the functions in this file should EVER access
+// ##LEGACY[] or LEGACY[]
+
+/**
+ * gets the timestamp of when the attached or provided account was ported from
+ * the Legacy snapshot through Vault
+ *
+ * Example:
+ * getlegacyporttime();
+ *
+ * @param 0? - char name / account id (defaults to attached player)
+ * @return timestamp (seconds)
+ */
+function script getlegacyporttime {
+ // we dereference the variable (+ 0) to avoid accidental assignment
+ return 0+ getvariableofpc(##LEGACY[1], nameid2id(getarg(0, "")), 0);
+}
+
+/**
+ * gets the former account id that was assigned to the attached or provided
+ * account on the Legacy server
+ *
+ * Example:
+ * getlegacyaccountid();
+ *
+ * @param 0? - char name / account id (defaults to attached player)
+ * @return former account id
+ */
+function script getlegacyaccountid {
+ // we dereference the variable (+ 0) to avoid accidental assignment
+ return 0+ getvariableofpc(##LEGACY[0], nameid2id(getarg(0, "")), 0);
+}
+
+/**
+ * checks whether the attached or provided account is a former Legacy account
+ *
+ * Example:
+ * islegacyaccount()
+ *
+ * @param 0? - char name / account id (defaults to attached player)
+ * @return true/false
+ */
+function script islegacyaccount {
+ return getlegacyaccountid(getarg(0, "")) > 0;
+}
+
+/**
+ * gets the former char id that was assigned to the attached or provided
+ * character on the Legacy server
+ *
+ * Example:
+ * getlegacycharid();
+ *
+ * @param 0? - char name / account id (defaults to attached player)
+ * @return former char id
+ */
+function script getlegacycharid {
+ // we dereference the variable (+ 0) to avoid accidental assignment
+ return 0+ getvariableofpc(LEGACY[0], nameid2id(getarg(0, "")), 0);
+}
+
+/**
+ * checks whether the attached or provided character is a former Legacy char
+ *
+ * Example:
+ * islegacychar()
+ *
+ * @param 0? - char name / account id (defaults to attached player)
+ * @return true/false
+ */
+function script islegacychar {
+ return getlegacycharid(getarg(0, "")) > 0;
+}
+
+/**
+ * gets the timestamp of when the attached or provided account completed the
+ * tutorial on the Legacy server
+ *
+ * Example:
+ * getlegacytuttime("player name")
+ *
+ * @param 0? - char name / account id (defaults to attached player)
+ * @return timestamp (seconds)
+ */
+function script getlegacytuttime {
+ .@tut_var = getvariableofpc(LEGACY[2], nameid2id(getarg(0, "")), 0);
+ return .@tut_var < 0x7F ? 0 : .@tut_var;
+}
+
+/**
+ * gets the level the attached or provided player had on the Legacy server at
+ * snapshot time
+ *
+ * Example:
+ * getlegacylevel("player name")
+ *
+ * @param 0? - char name / account id (defaults to attached player)
+ * @return base level
+ */
+function script getlegacylevel {
+ return bitwise_get(getvariableofpc(LEGACY[1], nameid2id(getarg(0, "")), 0), 0x000000FF, 0);
+}
+
+/**
+ * gets the boss points the attached or provided player had on the Legacy server
+ * at snapshot time
+ *
+ * Example:
+ * getlegacybosspoints("player name")
+ *
+ * @param 0? - char name / account id (defaults to attached player)
+ * @return boss points
+ */
+function script getlegacybosspoints {
+ return bitwise_get(getvariableofpc(LEGACY[1], nameid2id(getarg(0, "")), 0), 0x7FFFFF00, 8);
+}
+
+
+
+// the functions below can be used to mimic a Legacy account for local testing
+
+
+/**
+ * mimics a legacy account for local testing
+ *
+ * Example:
+ * setfakelegacyaccount("player name");
+ *
+ * @param 0? - char name / account id (defaults to attached player)
+ * @param 1? - legacy level (defaults to 99)
+ * @param 2? - legacy boss points (defaults to 5000)
+ * @return true/false
+ */
+function script setfakelegacyaccount {
+ if (!debug) {
+ consolemes(CONSOLEMES_ERROR, "setfakelegacyaccount() can only be used in debug mode");
+ return false;
+ }
+
+ .@acc = nameid2id(getarg(0, ""));
+
+ if (.@acc < 1) {
+ // player not found
+ return false;
+ }
+
+ // set the legacy account id to the current account id
+ set(getvariableofpc(##LEGACY[0], .@acc), .@acc);
+
+ // set the port time to yesterday
+ set(getvariableofpc(##LEGACY[1], .@acc), time_from_days(-1));
+
+ // set the legacy tut var to 180 days ago
+ set(getvariableofpc(LEGACY[2], .@acc), time_from_days(-180));
+
+ // set the legacy level
+ bitwise_set(getvariableofpc(LEGACY[1], .@acc), 0x000000FF, 0, getarg(1, 99));
+
+ // set the legacy boss points
+ bitwise_set(getvariableofpc(LEGACY[1], .@acc), 0x7FFFFF00, 8, getarg(2, 5000));
+ return true;
+}
+
+/**
+ * gets the inventory the attached or provided char had on the Legacy server at
+ * snapshot time
+ *
+ * Example:
+ * .@size = getlegacyinventory(.@item, .@amount);
+ *
+ * @param 0 - a reference to an array variable to hold the item ids
+ * @param 1 - a reference to an array variable to hold the item qty
+ * @param 2? - char name / account id (defaults to attached player)
+ * @return number of entries added to the arrays
+ */
+function script getlegacyinventory {
+ .@char = getlegacycharid(getarg(2, ""));
+
+ if (.@char < 1) {
+ consolemes(CONSOLEMES_ERROR, "getlegacyinventory: target legacy character not found");
+ return 0;
+ }
+
+ if ((getdatatype(getarg(0)) & (DATATYPE_VAR | DATATYPE_INT)) == 0) {
+ consolemes(CONSOLEMES_ERROR, "getlegacyinventory: first argument should be an integer array");
+ return 0;
+ }
+
+ if ((getdatatype(getarg(1)) & (DATATYPE_VAR | DATATYPE_INT)) == 0) {
+ consolemes(CONSOLEMES_ERROR, "getlegacyinventory: second argument should be an integer array");
+ return 0;
+ }
+
+ freeloop(true);
+ .@rows = query_sql(sprintf("SELECT nameid, amount FROM legacy.inventory WHERE char_id = '%d';", .@char),
+ getarg(0), getarg(1));
+ freeloop(false);
+
+ return .@rows;
+}
+
+/**
+ * gets the storage the attached or provided account had on the Legacy server at
+ * snapshot time
+ *
+ * Example:
+ * .@size = getlegacystorage(.@item, .@amount);
+ *
+ * @param 0 - a reference to an array variable to hold the item ids
+ * @param 1 - a reference to an array variable to hold the item qty
+ * @param 2? - char name / account id (defaults to attached player)
+ * @return number of entries added to the arrays
+ */
+function script getlegacystorage {
+ .@acc = getlegacyaccountid(getarg(2, ""));
+
+ if (.@acc < 1) {
+ consolemes(CONSOLEMES_ERROR, "getlegacystorage: target legacy account not found");
+ return 0;
+ }
+
+ if ((getdatatype(getarg(0)) & (DATATYPE_VAR | DATATYPE_INT)) == 0) {
+ consolemes(CONSOLEMES_ERROR, "getlegacystorage: first argument should be an integer array");
+ return 0;
+ }
+
+ if ((getdatatype(getarg(1)) & (DATATYPE_VAR | DATATYPE_INT)) == 0) {
+ consolemes(CONSOLEMES_ERROR, "getlegacystorage: second argument should be an integer array");
+ return 0;
+ }
+
+ freeloop(true);
+ .@rows = query_sql(sprintf("SELECT nameid, amount FROM legacy.storage WHERE account_id = '%d';", .@acc),
+ getarg(0), getarg(1));
+ freeloop(false);
+
+ return .@rows;
+}
diff --git a/npc/functions/legiontalk.txt b/npc/functions/legiontalk.txt
new file mode 100644
index 00000000..25004c07
--- /dev/null
+++ b/npc/functions/legiontalk.txt
@@ -0,0 +1,66 @@
+// Evol functions.
+// Authors:
+// Akko Teru
+// Reid
+// Qwerty Dragon
+// Description:
+// Tell a random sentence suited to Aemil's Legion in Artis.
+
+function script legiontalk {
+
+ switch (rand(15))
+ {
+ case 0:
+ npctalkonce(l("Do I look like a tree? I feel like one."));
+ //speech(
+ // l("Do you feel too weak even to do damage to this areas wishy-washy wildlife?"),
+ // l("Then concentrate your anger upon the trees hereabouts, you will gain experience whilst leveling your sword skill on them."),
+ // l("Oh, and a fruit may even fall for you if you are lucky! But stay alert to pick up your drops."));
+ //close;
+ break;
+ case 1:
+ npctalkonce(l("I'm a little busy right now."));
+ break;
+ case 2:
+ npctalkonce(l("Not in the mood to chat."));
+ break;
+ case 3:
+ npctalkonce(l("My breath smells bad."));
+ break;
+ case 4:
+ npctalkonce(l("Don't distract me, I have to stay alert."));
+ break;
+ case 5:
+ npctalkonce(l("Give me some space."));
+ break;
+ case 6:
+ npctalkonce(l("Can you please go away?"));
+ break;
+ case 7:
+ npctalkonce(l("Can't talk right now, I'm on patrol duty."));
+ break;
+ case 8:
+ npctalkonce(l("What're you looking at?!"));
+ break;
+ case 9:
+ npctalkonce(l("I can't stay here and talk all day. I have a job to do."));
+ break;
+ case 10:
+ npctalkonce(l("Keep moving!"));
+ break;
+ case 11:
+ npctalkonce(l("So you think you're tough? A warrior must also be loyal and patient."));
+ break;
+ case 12:
+ emotion E_LOOKAWAY;
+ break;
+ case 13:
+ npctalkonce(l("Practice! There are no secrets to becoming a warrior."));
+ break;
+ case 14:
+ npctalkonce(l("There is no honor in fighting a weak opponent."));
+ break;
+ }
+
+ return;
+}
diff --git a/npc/functions/libquest.txt b/npc/functions/libquest.txt
new file mode 100644
index 00000000..4799eb1d
--- /dev/null
+++ b/npc/functions/libquest.txt
@@ -0,0 +1,104 @@
+// Evol scripts for simplified quest development.
+// Author:
+// Livio
+
+/*
+ @brief Prints the list of quest ingredients on the NPC window
+ @param Array with items IDs
+ @param Array with relative amount required
+ @returns nothing
+*/
+function script printIngredients {
+ for (.@i = 0; .@i < getarraysize(getarg(0)); .@i++) {
+ mesf(" - %d %s", getelementofarray(getarg(1), .@i), getitemlink(getelementofarray(getarg(0), .@i)));
+ }
+ return;
+}
+
+/*
+ @brief Checks if player has items required
+ @param Array with required items IDs
+ @param Array with relative amount required
+ @returns false if player doesn't have required items
+*/
+function script checkForItems {
+ for (.@i = 0; .@i < getarraysize(getarg(0)); .@i++) {
+ // If even a single thing is missing abort immediately
+ if(getelementofarray(getarg(1), .@i) > countitem(getelementofarray(getarg(0), .@i))) {
+ return false;
+ }
+ }
+ return true;
+}
+
+/*
+ @brief Craft an item from some other items. All input items got deleted.
+ @param Array with required items IDs
+ @param Array with relative amount required
+ @param output item IDs
+ @param output amount
+ @returns 0 if successful, 1 if player lack ingredients, 2 if overburdened, 255 code error
+*/
+function script craftFromPlayer {
+ // Check input parameter amount
+ if (getargcount() != 4) return 255;
+
+ // Check item amounts
+ if (!checkForItems(getarg(0), getarg(1))) return 1;
+
+ // Check if player is able to carry output from crafting
+ if (!checkweight(getarg(2), getarg(3))) return 2;
+ else getitem(getarg(2), getarg(3));
+
+ // Delete Items from player inventory
+ for (.@i = getarrayindex(getarg(0)); .@i < getarraysize(getarg(0)); .@i++) {
+ delitem(getelementofarray(getarg(0), .@i), getelementofarray(getarg(1), .@i));
+ }
+
+ return 0;
+}
+
+/*
+ @brief Craft an item from some other items. All input items will be deleted in case of success.
+ @param Array with required items IDs
+ @param Array with relative amount required
+ @param output item IDs
+ @param output amount
+ @param NPC question about ingredients
+ @param NPC answer if successful
+ @param NPC answer if player lacks ingredients
+ @param NPC answer if player is overburdened
+ @returns true if successful
+*/
+function script NPCcrafting {
+ speech
+ getarg(4);
+ if (askyesno() == ASK_NO) {
+ mesq l("Come on, move!");
+ close;
+ } else {
+ switch(craftFromPlayer(getarg(0), getarg(1), getarg(2), getarg(3))) {
+ case 0:
+ mesq getarg(5);
+ return true;
+ break;
+
+ case 1:
+ mesq getarg(6);
+ // FIXME - Gather proper variables
+ printIngredients(getarg(0), getarg(1));
+ break;
+
+ case 2:
+ mesq getarg(7);
+ break;
+
+ case 255:
+ mesq l("[BUG ENCOUNTERED] Dammit...");
+ break;
+
+ default:
+ }
+ }
+ return false;
+} \ No newline at end of file
diff --git a/npc/functions/lockpicks.txt b/npc/functions/lockpicks.txt
new file mode 100644
index 00000000..6a3c55f7
--- /dev/null
+++ b/npc/functions/lockpicks.txt
@@ -0,0 +1,193 @@
+// TMW2/LoF Script
+// Author:
+// Jesusalva
+// Description:
+// Lockpicking core
+
+// Important variables:
+// THIEF_EXP
+// Experience on Thief Tree
+// THIEF_RANK
+// Position on the Thief Tree
+
+// LockPicking(num_pins, max_pins, min_rank=num_pins)
+// Returns 0 upon failure, 1 upon success
+// Closes script if an error happen or if you give up / cannot try.
+//
+// The 'next' is upon script responsability
+// Maximum pin number is infinite. Maximum Pin Positiors range from 2~5.
+// If you fail, you can end up having to start again. If you fail too much,
+// you'll be caught!
+function script LockPicking {
+ // If you don't have a LockPick, you can't do this (useless)
+ if (!countitem(Lockpicks)) {
+ mesc l("You need a @@ to try this.", getitemlink(Lockpicks)), 1;
+ close;
+ }
+
+ .@d=getarg(0,1);
+ .@m=getarg(1,3);
+ .@minrank=getarg(2, .@d);
+
+ // Invalid Argument (kill script)
+ if (.@d < 1 || .@m < 2 || .@m > 5)
+ end;
+
+ // You must be rank (number of locks - 1) to try
+ if (THIEF_RANK+1 < .@minrank) {
+ mesc l("This lock is beyond your current capacity."), 1;
+ close;
+ }
+
+ // Create @pins array (the answer)
+ for (.@i=0; .@i < .@d;.@i++)
+ @pins[.@i] = rand2(1,.@m);
+
+ // Check if you'll try to open it.
+ mesc l("This lock is simple, maybe with your thief skills you can manage to pry it open. But beware, you can end up in jail!");
+ mesc l("Will you try to unlock it?");
+ if (askyesno() == ASK_NO)
+ close;
+
+ // Setup your attempt
+ delitem Lockpicks, 1;
+ @pos=0;
+ @chance=min(.@d*.@m-1, THIEF_RANK+.@d);
+ mesc l("You insert the hook pick inside the lock, and, without applying any tension, you discover there are only @@ pins to set.", .@d);
+
+ // You have as many attempts as pins and appliable strenghts.
+ // Each thief rank grants you an extra attempt.
+ // Each pin takes one attempt.
+ // It's not multiplied, so 3 pins with 3 positions: 6 chances, 9 possibilities.
+ // There's no penalty, but the attempt is counted working or not!
+ // Remember if you fail, all previous pins will be cleared (@pos)
+ do {
+ mesc l("You are trying to open the @@th pin. What will to do?", @pos+1);
+
+ menuint
+ rif(.@m >= 4, l("Apply no pressure")), 4,
+ rif(.@m >= 2, l("Apply soft pressure")), 2,
+ rif(.@m >= 1, l("Apply normal pressure")), 1,
+ rif(.@m >= 3, l("Apply strong pressure")), 3,
+ rif(.@m >= 5, l("Apply very strong pressure")), 5,
+ l("Give up!"), 0;
+
+ if (!@menuret) {
+ // 50% chance to save the lockpick
+ if (rand2(2) == 1)
+ getitem Lockpicks, 1;
+ else
+ dispbottom l("The lockpick broke.");
+ close;
+ }
+
+ // Is your guess correct?
+ if (@pins[@pos] == @menuret) {
+ mesc l("*click*");
+ @pos+=1;
+ } else {
+ mesc l("This didn't work. All pins are now unset!");
+ @pos=0;
+ @chance-=1;
+ // We don't need to clear console, each successful attempt IS counted.
+ // Therefore, unsetting 3 pins means you must do 3 new attempts!!
+ // The biggie is that you're running against time, here!!!
+ if (@chance < .@d && rand2(0, THIEF_RANK))
+ mesc l("Your thief instincts suggest you to hurry."), 1;
+ }
+
+ if (@chance <= 0)
+ break;
+
+ if (@pos >= .@d) {
+ // 33% chance to save the lockpick
+ if (rand2(3) == 1)
+ getitem Lockpicks, 1;
+ else
+ dispbottom l("The lockpick broke.");
+
+ // Get EXP and inform the success
+ if (THIEF_RANK)
+ THIEF_EXP += max(0, .@d*.@m-THIEF_RANK);
+ return 1;
+ }
+ } while (true);
+
+ // Failed
+ if (THIEF_RANK)
+ THIEF_EXP += 1;
+ return 0;
+}
+
+// Script helper to say if you were arrested or not
+function script ArrestedChances {
+ .@runaway=cap_value(readbattleparam(getcharid(3), UDT_LUK)+readbattleparam(getcharid(3), UDT_AGI), 0, 200); // 20%
+ .@runaway+=125; // 12.5% base chance
+ .@runaway+=cap_value(THIEF_RANK*15, 0, 100); // real max 7.5%
+ // Max runaway chance: 40%
+ if (rand2(1000) < .@runaway)
+ return false;
+ return true;
+}
+
+// Script helper to actually arrest you. TODO: Arrest anyone
+// ArrestPlayer ( time-in-minutes )
+function script ArrestPlayer {
+ .@t=getarg(0);
+
+ // We can't do anything without a player o.o
+ if (!playerattached())
+ return;
+
+ // You're already jailed so we do nothing and fail silently
+ if (getstatus(SC_JAILED)) {
+ return;
+ }
+
+ // Okay, we can arrest you
+ // TODO: Arrest player, preferably without GM commands D:
+ // (would then take an extra argument: map)
+ atcommand("@jailfor "+.@t+"mn "+strcharinfo(0));
+
+ // Notification
+ dispbottom l("You were arrested; Use %s for information about how long you will spend here.", b("@jailtime"));
+ return;
+}
+
+/*
+// Main script from Moubootaur Legends Vaults
+// LootableVault(tier, level, variable)
+function script LootableVault {
+ .@tier=getarg(0)+1;
+ .@level=getarg(1);
+ .@var$=getarg(2);
+ mesn;
+ mesq l("There's a shiny safe here. How much money is inside? Nobody is looking at you, great!");
+ // 2*3 = 6 possibilities, 5 attempts
+ if (LockPicking(.@tier, .@level)) {
+ Zeny=Zeny+getd("$VAULT_"+.@var$);
+ setd("$VAULT_"+.@var$, 40);
+ mesn;
+ mesq l("Booty!");
+ } else {
+ mesn;
+ .@inch=(Zeny/100);
+ Zeny-=.@inch;
+ setd("$VAULT_"+.@var$, getd("$VAULT_"+.@var$)+.@inch);
+ if (ArrestedChances()) {
+ mesc l("Arrested!");
+ atcommand("@jailfor 5mn "+strcharinfo(0));
+ } else {
+ if (is_night())
+ .@p$=l("The darkness of night gives you cover.");
+ else
+ .@p$=l("Your agile legs and sheer luck allows you to outrun the cops.");
+ mesc l("You run as far as you could. %s", .@p$);
+ warp "000-1", 22, 22;
+ }
+ }
+ return;
+}
+*/
+
+
diff --git a/npc/functions/main.txt b/npc/functions/main.txt
new file mode 100644
index 00000000..8029f3eb
--- /dev/null
+++ b/npc/functions/main.txt
@@ -0,0 +1,445 @@
+// Evol functions.
+// Authors:
+// 4144
+// Jesusalva
+// Travolta
+// Description:
+// Build in functions.
+
+/**
+ * checks whether the given argument is a char name or account id
+ * and tries to convert it to an account id
+ *
+ * @arg 0 - char name or account id
+ * @return the account id
+ */
+function script nameid2id {
+ if ((getdatatype(getarg(0, "")) & DATATYPE_STR) != 0) {
+ if (getarg(0, "") == "") {
+ return playerattached();
+ } else {
+ return getcharid(CHAR_ID_ACCOUNT, getarg(0));
+ }
+ } else if (getarg(0) == 0) {
+ return playerattached();
+ } else {
+ return getarg(0);
+ }
+}
+
+function script menuimage {
+ return getarg(0) + "|" + getarg(1);
+}
+
+function script menuaction {
+ return "[" + getarg(0) + "]";
+}
+
+function script mesn {
+ if (getargcount() > 0)
+ {
+ .@s$ = "[" + getarg(0) + "]";
+ }
+ else
+ {
+ .@s$ = "[" + strnpcinfo(1) + "]";
+ }
+ mes .@s$;
+ return;
+}
+
+function script mesq {
+ mes "\"" + getarg(0)+ "\"";
+ return;
+}
+
+function script g {
+ consolemes(CONSOLEMES_ERROR, "Deprecated function \"g\" used, results are not reliable.");
+ return rand(1000) % 2 ? getarg(0) : getarg(1);
+}
+
+function script b {
+ return "##B" + getarg(0) + "##b";
+}
+
+function script col {
+ .@color = getarg(1);
+ if (.@color < 0) .@color = 0;
+ if (.@color > 9) .@color = 9;
+ return "##" + .@color + getarg(0) + "##0";
+}
+
+function script mesc {
+ return mes(col(getarg(0, ""), getarg(1, 9)));
+}
+
+// *showimage("<file>");
+// Displays an image in the NPC window. If no file extension is provided, it is
+// assumed to be a PNG file. The image path is relative to client-data/graphics.
+// example:
+// showimage("guiicons/flags/fr");
+function script showimage {
+ .@file$ = getarg(0);
+
+ if (!endswith(getarg(0), ".jpg") && !endswith(getarg(0), ".png")) {
+ .@file$ += ".png";
+ }
+
+ if (charat(getarg(0), 0) == "/") {
+ .@file$ = delchar(.@file$, 0); // absolute path from root of client-data
+ } else if (!startswith(getarg(0), "help/") && !startswith(getarg(0), "graphics/") ) {
+ .@file$ = "graphics/" + .@file$; // assume relative to graphics/
+ }
+
+ return mesf("~~~%s~", .@file$);
+}
+
+function script adddefaultskills {
+ if (getskilllv(NV_BASIC) < 6)
+ {
+ skill NV_BASIC, 6, 0;
+ }
+ return;
+}
+
+function script addremovemapmask {
+ setmapmask getarg(0), (getmapmask(getarg(0)) | (getarg(1) + getarg(2))) ^ getarg(2);
+ return;
+}
+
+// Function to show narrator text. Accepts string args.
+// If first arg is a number N, then it represents bit flags.
+// Bit flags :
+// S_FIRST_BLANK_LINE -- blank line at beginning
+// S_LAST_BLANK_LINE -- blank line at the end
+// S_LAST_NEXT -- use last "next();"
+// S_NO_NPC_NAME -- don't use first "mesn();"
+// S_LAST_CLOSE -- use last "close2();
+function script narrator {
+ .@start = 0;
+ .@argc = getargcount();
+ .@flags = 0;
+
+ if (.@argc > 1 && (getdatatype(getarg(0)) & DATATYPE_INT) != 0) {
+ .@start = 1;
+ .@flags = getarg(0);
+ }
+
+ if ((.@flags & S_FIRST_BLANK_LINE) != 0) {
+ mes("");
+ }
+
+ if ((.@flags & S_NO_NPC_NAME) == 0) {
+ mesn(l("Narrator"));
+ }
+
+ for (.@i = .@start; .@i < .@argc; .@i++) {
+ mes(col(getarg(.@i), 9));
+
+ if (.@i < .@argc - 1) {
+ next();
+ }
+ }
+
+ if ((.@flags & S_LAST_BLANK_LINE) != 0) {
+ mes("");
+ }
+
+ if ((.@flags & S_LAST_NEXT) != 0) {
+ next();
+ } else if ((.@flags & S_LAST_CLOSE) != 0) {
+ close2();
+ }
+
+ return;
+}
+
+// Function to show NPC speech. Accepts string args.
+// If first arg is a number N, then it represents bit flags.
+// Bit flags :
+// 0x1 -- blank line at beginning
+// 0x2 -- blank line at the end
+// 0x4 -- use last "next;"
+// 0x8 -- don't use first "mesn;"
+function script speech {
+ .@start = 0;
+ .@argc = getargcount();
+ .@flags = 0;
+
+ if (.@argc > 1 && !isstr(getarg(0)))
+ {
+ .@start = 1;
+ .@flags = getarg(0);
+ }
+
+ if (.@flags & 0x1)
+ mes "";
+
+ if (!(.@flags & 0x8))
+ mesn;
+
+ for (.@i = .@start; .@i < .@argc; .@i++)
+ {
+ mesq getarg(.@i);
+
+ if (.@i < .@argc - 1)
+ next;
+ }
+
+ if (.@flags & 0x4)
+ next;
+ else if (.@flags & 0x2)
+ mes "";
+
+ return;
+}
+
+// Show debug message if .debug variable of NPC is set to 1
+function script npcdebug {
+ if (getvariableofnpc(.debug, strnpcinfo(3)))
+ consolemes(CONSOLEMES_DEBUG, strnpcinfo(3) + ": " + getarg(0));
+ return;
+}
+
+function script askyesno {
+ .@sel = select(menuaction(l("Yes")),
+ menuaction(l("No")));
+ mes "";
+ return .@sel;
+}
+
+// Argument:
+// 0 Quest variable
+// 1 Current value
+// 2 Next value
+function script compareandsetq {
+ if (getq(getarg(0)) == getarg(1))
+ {
+ setq getarg(0), getarg(2);
+ return true;
+ }
+ return false;
+}
+
+// Use a delay to prevent spams from NPC that display text without the
+// use of (a) close/next function(s).
+// Argument:
+// 0 Text to display
+// 1 Lock delay (default = 1)
+// 2 Message function: (default = 0)
+// 0 = npctalk3
+// 1 = npctalk
+// 2 = message
+function script npctalkonce {
+ // lock mechanism
+ switch (getarg(2, 0))
+ {
+ case 1:
+ if (gettimetick(2) <= getvariableofnpc(.talk_lock, strnpcinfo(NPC_NAME_UNIQUE)))
+ return false;
+ set(getvariableofnpc(.talk_lock, strnpcinfo(NPC_NAME_UNIQUE)), gettimetick(2) + getarg(1, 1));
+ break;
+ default:
+ if (gettimetick(2) <= @NPC_TALK_LOCK[getnpcid()])
+ return false;
+ @NPC_TALK_LOCK[getnpcid()] = gettimetick(2) + getarg(1, 1);
+ }
+
+ // talk mechanism
+ switch (getarg(2, 0))
+ {
+ case 0: npctalk3(getarg(0)); break;
+ case 1: npctalk(getarg(0)); break;
+ case 2: message(strcharinfo(0), getarg(0));
+ }
+
+ return true;
+}
+
+function script getquestlink {
+ return "[@@q" + getarg(0) + "|@@]";
+}
+
+function script getmonsterlink {
+ return "[@@m" + getarg(0) + "|@@]";
+}
+
+function script getpetlink {
+ return "[@@p" + getarg(0) + "|@@]";
+}
+
+function script getmercenarylink {
+ return "[@@M" + getarg(0) + "|@@]";
+}
+
+function script gethomunculuslink {
+ return "[@@h" + getarg(0) + "|@@]";
+}
+
+// Returns the player race in plain text
+// GETRACE_RACE - returns player race (default)
+// GETRACE_SKIN - returns player skin
+// GETRACE_FULL - returns player skin + race
+// Can take an optional 2nd param with the class
+// get_race( {Flag, {Class}} )
+function script get_race {
+ .@m=getarg(0, GETRACE_RACE);
+ .@g=getarg(1, Class);
+
+ // We also allow this to run without player attached for... science.
+ if (playerattached())
+ {
+ setarray .@allraces$, l("Human"), l("Human"), l("Human"),
+ l("Ukar"), l("Ukar"),
+ l("Tritan"), l("Tritan"),
+ l("Raijin"), l("Raijin"),
+ l("Kralog"), l("Kralog");
+ setarray .@allskins$, l("Kaizei"), l("Argaes"), l("Tonori"),
+ l("Cave"), l("Mountain"),
+ l("Sea"), l("Lake"),
+ l("Light"), l("Dark"),
+ l("Fire"), l("Frost");
+ }
+ else
+ {
+ setarray .@allraces$, ("Human"), ("Human"), ("Human"),
+ ("Ukar"), ("Ukar"),
+ ("Tritan"), ("Tritan"),
+ ("Raijin"), ("Raijin"),
+ ("Kralog"), ("Kralog");
+ setarray .@allskins$, ("Kaizei"), ("Argaes"), ("Tonori"),
+ ("Cave"), ("Mountain"),
+ ("Sea"), ("Lake"),
+ ("Light"), ("Dark"),
+ ("Fire"), ("Frost");
+ }
+
+ if (.@m == GETRACE_RACE)
+ return .@allraces$[.@g];
+ else if (.@m == GETRACE_SKIN)
+ return .@allskins$[.@g];
+ else
+ return .@allskins$[.@g] + " " + .@allraces$[.@g];
+}
+
+// Clear output of getinventorylist()
+// delinventorylist()
+function script delinventorylist {
+ deletearray @inventorylist_id;
+ deletearray @inventorylist_amount;
+ deletearray @inventorylist_equip;
+ deletearray @inventorylist_refine;
+ deletearray @inventorylist_identify;
+ deletearray @inventorylist_attribute;
+ deletearray @inventorylist_card1;
+ deletearray @inventorylist_card2;
+ deletearray @inventorylist_card3;
+ deletearray @inventorylist_card4;
+ deletearray @inventorylist_expire;
+ deletearray @inventorylist_bound;
+ @inventorylist_count=0;
+ return;
+}
+
+// isin( map, x1, y1, {[x2, y2][radius]} )
+function script isin {
+ if (getmapxy(.@mapName$, .@xpos, .@ypos, 0) != 0)
+ return false;
+ if (.@mapName$ != getarg(0))
+ return false;
+
+ if (getarg(4,-1) < 0) {
+ // Radius Based
+ if (.@xpos >= getarg(1)-getarg(3) && .@xpos <= getarg(1)+getarg(3) && .@ypos >= getarg(2)-getarg(3) && .@ypos <= getarg(2)+getarg(3))
+ return true;
+ } else {
+ // Coordinate based
+ if (.@xpos >= getarg(1) && .@xpos <= getarg(3) && .@ypos >= getarg(2) && .@ypos <= getarg(4))
+ return true;
+ }
+ return false;
+}
+
+// Shortcut for getmapname()
+function script getmap {
+ return getmapname();
+}
+
+// Quest Rewards
+// quest_xp(maxLevel, reward, {multiplier=1})
+function script quest_xp {
+ //.@minLevel=getarg(0);
+ .@maxLevel=getarg(0);
+ .@reward=getarg(1);
+ .@mult=getarg(2, 1);
+ if (BaseLevel <= .@maxLevel) {
+ getexp .@reward*.@mult, 0;
+ return;
+ }
+ // You'll forsake 2% every over level
+ .@mult*=100;
+ .@mult-=((BaseLevel - .@maxLevel) * 2);
+ .@mult=max(10, .@mult);
+ getexp .@reward*.@mult/100, 0;
+ return;
+}
+
+// quest_jxp(maxLevel, reward, {multiplier=1})
+function script quest_jxp {
+ //.@minLevel=getarg(0);
+ .@maxLevel=getarg(0);
+ .@reward=getarg(1);
+ .@mult=getarg(2, 1);
+ if (BaseLevel < .@maxLevel) {
+ getexp 0, .@reward*.@mult;
+ return;
+ }
+ // You'll forsake 2% every over level
+ .@mult*=100;
+ .@mult-=((BaseLevel - .@maxLevel) * 2);
+ .@mult=max(10, .@mult);
+ getexp 0, .@reward*.@mult/100;
+ return;
+}
+
+// quest_gp(maxLevel, reward, {multiplier=1})
+function script quest_gp {
+ //.@minLevel=getarg(0);
+ .@maxLevel=getarg(0);
+ .@reward=getarg(1);
+ .@mult=getarg(2, 1);
+ if (BaseLevel <= .@maxLevel) {
+ Zeny+=.@reward*.@mult;
+ return;
+ }
+ // You'll forsake 2% every over level
+ .@mult*=100;
+ .@mult-=((BaseLevel - .@maxLevel) * 2);
+ .@mult=max(10, .@mult);
+ Zeny+=.@reward*.@mult/100;
+ return;
+}
+
+// quest_item(maxLevel, item, {amount=1}, {bound=0})
+function script quest_item {
+ //.@minLevel=getarg(0);
+ .@maxLevel=getarg(0);
+ .@reward=getarg(1);
+ .@mult=getarg(2, 1);
+ .@bind=getarg(3, 0);
+ // Item will not be obtained if you are overlevel
+ if (BaseLevel > .@maxLevel && .@mult <= 1)
+ return;
+ // If it comes in pairs, you'll only get 1
+ if (BaseLevel > .@maxLevel)
+ .@mult=1;
+ // Obtain item bound if needed
+ if (.@bind)
+ getitembound .@reward, .@mult, .@bind;
+ else
+ getitem .@reward, .@mult;
+ return;
+}
+
+
+
diff --git a/npc/functions/manhole.txt b/npc/functions/manhole.txt
new file mode 100644
index 00000000..3af18537
--- /dev/null
+++ b/npc/functions/manhole.txt
@@ -0,0 +1,73 @@
+// TMW2 Script
+// Authors:
+// Jesusalva
+//
+// Description:
+// Handles Artis manholes
+// Relies on getmap, be sure coords are enough compatible
+// ie. Leave a 2x2 area free of collision in the target coordinates
+// Heights weren't checked
+
+// manhole_interact( dest_map )
+// Carries over getmapxy() for NPC
+// This is for Artis and thus, hackish.
+// Return Codes:
+// -1 : Tried to enter Sewers
+// >0 : ID of dropped item (in case it must be caught)
+
+function script manhole_interact {
+ .@dest_map$=getarg(0);
+ getmapxy(.@m$, .@x, .@y, UNITTYPE_NPC);
+
+ narrator(S_LAST_BLANK_LINE | S_LAST_NEXT,
+ l("You hear some creeping and crawling sounds from the murkiness below."),
+ l("..."));
+
+ select
+ l("Do you want to leave it alone?"),
+ rif(getq(ArtisQuests_MonaDad), l("Do you want to enter in sewer?")),
+ l("Do you want to throw something inside?");
+
+ switch (@menu) {
+ case 1:
+ close; break;
+ case 2:
+ return -1; break;
+ case 3:
+ mes "##B" + l("Drag and drop an item from your inventory.") + "##b";
+
+ .@id = requestitem();
+
+ // If ID is invalid
+ if (.@id < 1) {
+ mesc l("You give up.");
+ close;
+ }
+
+ // If there's not enough items, it is bound, it cannot be traded/dropped/sold, etc.
+ if (countitem(.@id) < 1 || checkbound(.@id) || getiteminfo(.@id, ITEMINFO_TRADE)) {
+ mesc l("You cannot drop this item!");
+ close;
+ }
+
+ // Delete item and spawn it on the equivalent map
+ delitem .@id, 1;
+ makeitem .@id, 1, .@dest_map$, .@x+rand(-2, 2), .@y+rand(-2, 2);
+
+ // May spawn a monster if it is food (33% odds)
+ if (getiteminfo(.@id, ITEMINFO_TYPE) == IT_HEALING && rand(1,3) == 3) {
+ // Would be nice to customize but not needed atm
+ // 1 mob for every 30 levels (level 99 players spawn 4 mobs)
+ // Note that food type is currently disregarded (and it accepts any healing item)
+ .@monsterId=any(Slime, Croc, LittleBlub, CaveMaggot);
+ .@mobGID = monster(.@m$, .@x, .@y, strmobinfo(1, .@monsterId), .@monsterId, (BaseLevel/30)+1);
+ unitattack(.@mobGID, getcharid(CHAR_ID_ACCOUNT)); // "It's not aggressive"? We don't care.
+ }
+
+ return .@id; break;
+ }
+
+
+
+}
+
diff --git a/npc/functions/masks.txt b/npc/functions/masks.txt
new file mode 100644
index 00000000..4b28bfc7
--- /dev/null
+++ b/npc/functions/masks.txt
@@ -0,0 +1,43 @@
+// Evol functions.
+// Author:
+// Reid
+// Jesusalva
+// Description:
+// Triggers functions to add and remove masks.
+// Variables:
+// 4 - Top Mask
+// 8 - Bottom Mask
+// Default mask: 13 (Top + Bottom + Display mask)
+
+// Artis Aemil's Legion
+
+function script artisALResetMask {
+ .@m=getmapmask("001-2-33");
+ sendmapmask(.@m);
+ return 0;
+}
+
+function script artisALTopMask {
+ addtimer 30, "artisALTopMaskDO::OnDoIt";
+ return 0;
+}
+
+function script artisALBottomMask {
+ addtimer 30, "artisALBottomMaskDO::OnDoIt";
+ return 0;
+}
+
+// Show bottom mask is the same as hiding top mask
+- script artisALBottomMaskDO NPC_HIDDEN,{
+OnDoIt:
+ .@m=getmapmask("001-2-33");
+ sendmapmask(.@m^4);
+}
+
+// Show top mask is the same as hiding bottom mask
+- script artisALTopMaskDO NPC_HIDDEN,{
+OnDoIt:
+ .@m=getmapmask("001-2-33");
+ sendmapmask(.@m^8);
+}
+
diff --git a/npc/functions/math.txt b/npc/functions/math.txt
new file mode 100644
index 00000000..357407da
--- /dev/null
+++ b/npc/functions/math.txt
@@ -0,0 +1,50 @@
+// Evol functions.
+// Authors:
+// 4144
+// Reid
+// Jesusalva
+// Description:
+// Math functions
+
+
+// abs(<int>)
+// returns the absolute value of the passed integer
+
+function script abs {
+ .@n = getarg(0);
+ return .@n >= 0 ? .@n : -.@n;
+}
+
+
+
+// lognbaselvl({<multiplicator>{, <min value>}})
+// returns BaseLevel * logn (BaseLevel * alpha).
+
+function script lognbaselvl {
+ .@alpha = getarg(0, 1);
+ .@min = getarg(1, 1);
+ .@ret = 0;
+ .@pc_level = BaseLevel * .@alpha;
+
+ while (.@pc_level >>= 1)
+ {
+ ++.@ret;
+ }
+ .@ret *= BaseLevel;
+
+ if (.@ret <= .@min)
+ {
+ .@ret = .@min;
+ }
+
+ return .@ret;
+}
+
+
+// result is: lower < target <= higher
+// is_between ( lower, higher, target)
+function script is_between {
+ .@val=getarg(2);
+ return (getarg(0) < .@val && getarg(1) >= .@val);
+}
+
diff --git a/npc/functions/mouboofunc.txt b/npc/functions/mouboofunc.txt
new file mode 100644
index 00000000..6129c083
--- /dev/null
+++ b/npc/functions/mouboofunc.txt
@@ -0,0 +1,89 @@
+// Evol functions.
+// Author:
+// Reid
+// Description:
+// Various scripts used in walking mouboo NPCs.
+
+function script moubootalk {
+ switch (rand(4))
+ {
+ case 0:
+ npctalkonce(l("Moooooo!"));
+ break;
+ case 1:
+ npctalkonce(l("Moo!"));
+ break;
+ case 2:
+ npctalkonce(l("Moooooooooooo!"));
+ break;
+ case 3:
+ npctalkonce(l("Moooo!"));
+ break;
+ }
+ return;
+}
+
+function script mouboocheckxy {
+ setarray .nearnpc$[0], "Mouboo#Artis0",
+ "Mouboo#Artis1",
+ "Mouboo#Artis2",
+ "Mouboo#Artis3",
+ "Taree";
+ for (.@size = 0; .@size < getarraysize(.nearnpc$); .@size++)
+ {
+ if (strcmp(.name$, .nearnpc$[.@size]) == 0)
+ {
+ continue;
+ }
+
+ .@npc_x = getvariableofnpc(.x, .nearnpc$[.@size]);
+ .@npc_y = getvariableofnpc(.y, .nearnpc$[.@size]);
+
+ if (.@npc_x == .x && .@npc_y == .y)
+ {
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+function script moubootimer {
+ if (mouboocheckxy() && !isunitwalking())
+ {
+ movetonextpoint;
+ }
+ else if (rand(0,6) == 5)
+ {
+ if (!isunitwalking())
+ {
+ movetonextpoint;
+ }
+ }
+ initnpctimer;
+ end;
+}
+
+function script mouboograph {
+ initmovegraph "down_pos", 66, 79, 75, 86,
+ "left_pos", 66, 79, 75, 86,
+ "up_pos", 66, 79, 75, 86,
+ "right_pos", 66, 79, 75, 86;
+
+ setmovegraphcmd "down_pos", "left_pos", 1, "dir 2",
+ "down_pos", "up_pos", 1, "dir 4",
+ "down_pos", "right_pos", 1, "dir 6",
+ "left_pos", "down_pos", 1, "dir 0",
+ "left_pos", "up_pos", 1, "dir 4",
+ "left_pos", "right_pos", 1, "dir 6",
+ "up_pos", "down_pos", 1, "dir 0",
+ "up_pos", "left_pos", 1, "dir 2",
+ "up_pos", "right_pos", 1, "dir 6",
+ "right_pos", "down_pos", 1, "dir 0",
+ "right_pos", "left_pos", 1, "dir 2",
+ "right_pos", "up_pos", 1, "dir 4";
+
+ firstmove "wait 2", "down_pos";
+ initnpctimer;
+ end;
+}
diff --git a/npc/functions/music.txt b/npc/functions/music.txt
new file mode 100644
index 00000000..18a8fbe7
--- /dev/null
+++ b/npc/functions/music.txt
@@ -0,0 +1,87 @@
+// The Mana World Script
+// Author:
+// Jesusalva
+// Gumi
+// Ledmitz
+// Description:
+// Music functions
+//
+// NOTE: This NPC uses the new public/private function call system to avoid
+// the use of doevent.
+// Syntax:
+// "jukebox"::HurnscaldPrompt(); → Makes a menuint for selecting hurns tracks
+// "jukebox"::JukeboxMusic(ID); → Changes music based on prompted ID
+// "jukebox"::BroadcastMusic(MAP, ID); → Changes music based on prompted ID
+// TODO: Check if you have the music unlocked? Bitmask? Array?
+
+- script jukebox 32767,{
+ end;
+
+// Helpers
+public function JukeboxMusic {
+ changeplayermusic $MUSIC_ARRAY$[getarg(0)] + ".ogg";
+ return;
+}
+
+public function BroadcastMusic {
+ changemusic getarg(0), $MUSIC_ARRAY$[getarg(1)] + ".ogg";
+ return;
+}
+
+// public function listing
+// * Hurnscald
+
+public function HurnscaldPrompt {
+ menuint
+ "Cancel", -1,
+ "Johanne - Forest of Birches", 0,
+ "Artis - Adventure Begins", 1,
+ "Argaes - Dariunas' Forest", 20,
+ "Hurnscald - Magick Real", 5;
+ mes "";
+ if (@menuret == -1)
+ close;
+ return @menuret;
+}
+
+// Initialize stuff which will be needed
+OnInit:
+ $MUSIC_ARRAY$[0] = "johanne";
+ $MUSIC_ARRAY$[1] = "artis";
+ $MUSIC_ARRAY$[2] = "ghoulish";
+ $MUSIC_ARRAY$[3] = "surreal";
+ $MUSIC_ARRAY$[4] = "ocean";
+ $MUSIC_ARRAY$[5] = "real";
+ $MUSIC_ARRAY$[6] = "academy";
+ $MUSIC_ARRAY$[7] = "bandit";
+ $MUSIC_ARRAY$[8] = "barbarians";
+ $MUSIC_ARRAY$[9] = "botcheck";
+ $MUSIC_ARRAY$[10] = "candor";
+ $MUSIC_ARRAY$[11] = "cavesong";
+ $MUSIC_ARRAY$[12] = "chilling";
+ $MUSIC_ARRAY$[13] = "cloudcall";
+ $MUSIC_ARRAY$[14] = "crypt";
+ $MUSIC_ARRAY$[15] = "despair";
+ $MUSIC_ARRAY$[16] = "dimond";
+ $MUSIC_ARRAY$[17] = "explorer";
+ $MUSIC_ARRAY$[18] = "faith";
+ $MUSIC_ARRAY$[19] = "fire";
+ $MUSIC_ARRAY$[20] = "forest";
+ $MUSIC_ARRAY$[21] = "graveyard";
+ $MUSIC_ARRAY$[22] = "hurns";
+ $MUSIC_ARRAY$[23] = "marine";
+ $MUSIC_ARRAY$[24] = "mystique";
+ $MUSIC_ARRAY$[25] = "nightcall";
+ $MUSIC_ARRAY$[26] = "nivalis";
+ $MUSIC_ARRAY$[27] = "ocean";
+ $MUSIC_ARRAY$[28] = "peace";
+ $MUSIC_ARRAY$[29] = "reid";
+ $MUSIC_ARRAY$[30] = "sewer";
+ $MUSIC_ARRAY$[31] = "store";
+ $MUSIC_ARRAY$[32] = "swamp";
+ $MUSIC_ARRAY$[33] = "thunderstorm";
+ $MUSIC_ARRAY$[34] = "waterlude";
+ $MUSIC_ARRAY$[35] = "xmas";
+ end;
+}
+
diff --git a/npc/functions/npcmove.txt b/npc/functions/npcmove.txt
new file mode 100644
index 00000000..612ab036
--- /dev/null
+++ b/npc/functions/npcmove.txt
@@ -0,0 +1,142 @@
+// Evol functions.
+// Author:
+// 4144
+// Description:
+// Moving npc utility functions
+// Variables:
+// none
+
+function script initpath {
+ deletearray getvariableofnpc(.movepathcmd$, strnpcinfo(3));
+ deletearray getvariableofnpc(.movepathy, strnpcinfo(3));
+ deletearray getvariableofnpc(.movepathx, strnpcinfo(3));
+ .@cnt = 0;
+
+ for (.@f = 0; .@f < getargcount(); .@f = .@f + 3)
+ {
+ set getvariableofnpc(.movepathcmd$[.@cnt], strnpcinfo(3)), getarg(.@f);
+ set getvariableofnpc(.movepathx[.@cnt], strnpcinfo(3)), getarg(.@f + 1);
+ set getvariableofnpc(.movepathy[.@cnt], strnpcinfo(3)), getarg(.@f + 2);
+ .@cnt ++;
+ }
+ //debugmes "array size: " + str(getarraysize(getvariableofnpc(.movepath, strnpcinfo(3))));
+ return;
+}
+
+function script domoveaction {
+ //debugmes "domoveaction: " + str(getvariableofnpc(.movepos, strnpcinfo(3)));
+ .@pos = getvariableofnpc(.movepos, strnpcinfo(3));
+ if (.@pos >= getarraysize(getvariableofnpc(.movepathx, strnpcinfo(3))) || .@pos < 0)
+ return;
+ //debugmes "walking";
+ .@cmd$ = getvariableofnpc(.movepathcmd$[.@pos], strnpcinfo(3));
+ //debugmes "cmd: " + .@cmd$;
+
+ if (.@cmd$ == "move")
+ {
+ npcwalkto getvariableofnpc(.movepathx[.@pos], strnpcinfo(3)), getvariableofnpc(.movepathy[.@pos], strnpcinfo(3));
+ }
+ else if (.@cmd$ == "dir")
+ {
+ setnpcdir getvariableofnpc(.movepathx[.@pos], strnpcinfo(3));
+ return 2;
+ }
+ else if (.@cmd$ == "wait")
+ {
+ set getvariableofnpc(.waitticks, strnpcinfo(3)), getvariableofnpc(.movepathx[.@pos], strnpcinfo(3));
+ }
+ else if (.@cmd$ == "emote")
+ {
+ unitemote getnpcid(), getvariableofnpc(.movepathx[.@pos], strnpcinfo(3));
+ return 2;
+ }
+ else if (.@cmd$ == "class")
+ {
+ .class = getvariableofnpc(.movepathx[.@pos], strnpcinfo(3));
+ return 2;
+ }
+ else if (.@cmd$ == "warp")
+ {
+ movenpc strnpcinfo(3), getvariableofnpc(.movepathx[.@pos], strnpcinfo(3)), getvariableofnpc(.movepathy[.@pos], strnpcinfo(3));
+ }
+ else if (.@cmd$ == "goto")
+ {
+ set getvariableofnpc(.movepos, strnpcinfo(3)), getvariableofnpc(.movepathx[.@pos], strnpcinfo(3));
+ return 0;
+ }
+ else if (.@cmd$ == "rmove")
+ {
+ getmapxy(.@mapName$, .@x, .@y, 1);
+ npcwalkto .@x + getvariableofnpc(.movepathx[.@pos], strnpcinfo(3)), .@y + getvariableofnpc(.movepathy[.@pos], strnpcinfo(3));
+ }
+ else if (.@cmd$ == "speed")
+ {
+ .speed = getvariableofnpc(.movepathx[.@pos], strnpcinfo(3));
+ return 2;
+ }
+ else if (.@cmd$ == "sit")
+ {
+ npcsit;
+ }
+ else if (.@cmd$ == "stand")
+ {
+ npcstand;
+ }
+ return 1;
+}
+
+function script movetonextpos {
+ .@wait = getvariableofnpc(.waitticks, strnpcinfo(3));
+ if (.@wait > 0)
+ {
+ .@wait --;
+ //debugmes "wait";
+ set getvariableofnpc(.waitticks, strnpcinfo(3)), .@wait;
+ return;
+ }
+ .@true = 1;
+ while (.@true)
+ {
+ .@true = 0;
+ .@pos = getvariableofnpc(.movepos, strnpcinfo(3));
+ //debugmes "movetonextpos: " + str(.@pos);
+ .@res = domoveaction(.@pos);
+ if (.@res == 1 || .@res == 2)
+ {
+ .@pos++;
+ if (.@pos >= getarraysize(getvariableofnpc(.movepathx, strnpcinfo(3))))
+ .@pos = 0;
+ set getvariableofnpc(.movepos, strnpcinfo(3)), .@pos;
+ }
+ if (.@res == 0 || .@res == 2)
+ {
+ .@true = 1;
+ }
+ }
+ return;
+}
+
+function script initialmove {
+ set getvariableofnpc(.movepos, strnpcinfo(3)), 0;
+ set getvariableofnpc(.waitticks, strnpcinfo(3)), -1;
+ movetonextpos;
+ return;
+}
+
+function script getmovecmd {
+ .@pos = getvariableofnpc(.movepos, strnpcinfo(3));
+ if (.@pos >= getarraysize(getvariableofnpc(.movepathx, strnpcinfo(3))) || .@pos < 0)
+ return "";
+ return getvariableofnpc(.movepathcmd$[.@pos], strnpcinfo(3));
+}
+
+function script domovestep {
+ if (isunitwalking())
+ {
+ initnpctimer;
+ end;
+ }
+ movetonextpos;
+ initnpctimer;
+ end;
+}
diff --git a/npc/functions/npcmovegraph.txt b/npc/functions/npcmovegraph.txt
new file mode 100644
index 00000000..54e4e783
--- /dev/null
+++ b/npc/functions/npcmovegraph.txt
@@ -0,0 +1,485 @@
+// Evol functions.
+// Author:
+// Travolta
+// Description:
+// Moving npc utility functions (graph-based)
+// Variables:
+// none
+
+function script initmovegraph {
+ deletearray getvariableofnpc(.movegraphcmd$, strnpcinfo(3));
+ deletearray getvariableofnpc(.movegraphlabels$, strnpcinfo(3));
+ deletearray getvariableofnpc(.movegraphweight, strnpcinfo(3));
+ deletearray getvariableofnpc(.movegraphflags, strnpcinfo(3));
+ deletearray getvariableofnpc(.movepos_y1, strnpcinfo(3));
+ deletearray getvariableofnpc(.movepos_x1, strnpcinfo(3));
+ deletearray getvariableofnpc(.movepos_x2, strnpcinfo(3));
+ deletearray getvariableofnpc(.movepos_y2, strnpcinfo(3));
+ .@cnt = 0;
+
+ for (.@f = 0; .@f < getargcount();)
+ {
+ set getvariableofnpc(.movegraphlabels$[.@cnt], strnpcinfo(3)), getarg(.@f++);
+ set getvariableofnpc(.movepos_x1[.@cnt], strnpcinfo(3)), getarg(.@f++);
+ set getvariableofnpc(.movepos_y1[.@cnt], strnpcinfo(3)), getarg(.@f++);
+ if (!isstr(getarg(.@f, "label")))
+ {
+ set getvariableofnpc(.movepos_x2[.@cnt], strnpcinfo(3)), getarg(.@f++);
+ set getvariableofnpc(.movepos_y2[.@cnt], strnpcinfo(3)), getarg(.@f++);
+ }
+ .@cnt ++;
+ }
+ return;
+}
+
+function script findmovegraphlabel {
+ if (!getargcount())
+ {
+ consolemes(CONSOLEMES_DEBUG, "findmovegraphlabel: no argument");
+ return -1;
+ }
+ if (!isstr(getarg(0)))
+ {
+ consolemes(CONSOLEMES_DEBUG, "findmovegraphlabel: need string argument");
+ return -1;
+ }
+
+ .@arg$ = getarg(0);
+ for (.@i = 0; .@i < getarraysize(getvariableofnpc(.movegraphlabels$, strnpcinfo(3))); .@i++)
+ {
+ if (getvariableofnpc(.movegraphlabels$[.@i], strnpcinfo(3)) == .@arg$)
+ return .@i;
+ }
+
+ npcdebug "findmovegraphlabel: label not found: " + getarg(0);
+ return -1;
+}
+
+/* setmovegraphcmd(fromPositionLabel,toPositionLabel[,moveChanceWeight[,moveFlags]],postCommand, ...);
+ * This function manipulates NPC moving graph. Before calling it, make sure
+ * `initmovegraph' was called. The function accepts 3-5 parameters (many times):
+ * fromPositionLabel, toPositionLabel -- starting and ending position of NPC move
+ * moveChanceWeight -- positive integer, represents the chance of moving in given direction. (optional)
+ * moveFlags -- if .mg_flags & moveFlags != 0, move is possible. (optional)
+ * postCommand -- either "moveon" (start moving to next location straight after arriving from
+ * fromPositionLabel to toPositionLabel) or a semicolon-separated set of commands
+ * ("wait 3", "emote 5" etc, see `execmovecmd') that will be executed after arrival.
+ * The commands don't have to end with ";moveon", it's executed in the end by default.
+ */
+function script setmovegraphcmd {
+ .@size = getarraysize(getvariableofnpc(.movepos_x1, strnpcinfo(3)));
+
+ for (.@f = 0; .@f < getargcount();)
+ {
+ .@from = findmovegraphlabel(getarg(.@f++));
+ .@to = findmovegraphlabel(getarg(.@f++));
+ .@weight = 1;
+ if (!isstr(getarg(.@f)))
+ .@weight = getarg(.@f++);
+ .@flags = 0xffff;
+ if (!isstr(getarg(.@f)))
+ .@flags = getarg(.@f++);
+ .@cmd$ = getarg(.@f++);
+ .@index = .@from * .@size + .@to; // emulation of 2d array
+ set getvariableofnpc(.movegraphcmd$[.@index], strnpcinfo(3)), .@cmd$;
+ set getvariableofnpc(.movegraphweight[.@index], strnpcinfo(3)), .@weight;
+ set getvariableofnpc(.movegraphflags[.@index], strnpcinfo(3)), .@flags;
+ }
+ return;
+}
+
+function script execmovecmd {
+
+ explode(.@cmd$, getarg(0), " ");
+
+ if (.@cmd$[0] == "moveon")
+ {
+ return 0;
+ }
+ else if (.@cmd$[0] == "dir")
+ {
+ .dir = atoi(.@cmd$[1]);
+ }
+ else if (.@cmd$[0] == "sit")
+ {
+ npcsit;
+ }
+ else if (.@cmd$[0] == "stand")
+ {
+ npcstand;
+ }
+ else if (.@cmd$[0] == "wait")
+ {
+ set getvariableofnpc(.waitticks, strnpcinfo(3)), atoi(.@cmd$[1]);
+ return 1;
+ }
+ else if (.@cmd$[0] == "emote")
+ {
+ unitemote getnpcid(), atoi(.@cmd$[1]);
+ }
+ else if (.@cmd$[0] == "class")
+ {
+ .class = atoi(.@cmd$[1]);
+ }
+ else if (.@cmd$[0] == "warp")
+ {
+ .@pos = -1;
+ .@map$ = "";
+ .@pos_idx = 1;
+ if (getarraysize(.@cmd$) == 3)
+ {
+ .@map$ = .@cmd$[1];
+ .@pos_idx = 2;
+ }
+ .@pos = findmovegraphlabel(.@cmd$[.@pos_idx]);
+ if (.@pos != -1)
+ {
+ .@x = getvariableofnpc(.movepos_x1[.@pos], strnpcinfo(3));
+ .@y = getvariableofnpc(.movepos_y1[.@pos], strnpcinfo(3));
+ if (getstrlen(.@map$) > 0)
+ unitwarp getnpcid(), .@map$, .@x, .@y;
+ else
+ movenpc strnpcinfo(3), .@x, .@y;
+ set getvariableofnpc(.movepos, strnpcinfo(3)), .@pos;
+ }
+ else
+ {
+ consolemes(CONSOLEMES_DEBUG, "execmovecmd: unknown WARP destination label: " + .@cmd$[1]);
+ }
+ }
+ else if (.@cmd$[0] == "call")
+ {
+ switch (getarraysize(.@cmd$))
+ {
+ case 1:
+ consolemes(CONSOLEMES_DEBUG, "execmovecmd: CALL command needs some parameters");
+ return 0;
+ case 2:
+ return callfunc(.@cmd$[1]);
+ break;
+ case 3:
+ return callfunc(.@cmd$[1], .@cmd$[2]);
+ case 4:
+ default:
+ return callfunc(.@cmd$[1], .@cmd$[2], .@cmd$[3]);
+ }
+ }
+ else if (.@cmd$[0] == "speed")
+ {
+ .speed = atoi(.@cmd$[1]);
+ }
+ else if (.@cmd$[0] == "say")
+ {
+ deletearray .@cmd$[0], 1;
+ npctalk implode(.@cmd$, " ");
+ }
+ else if (.@cmd$[0] == "debugmes")
+ {
+ deletearray .@cmd$[0], 1;
+ consolemes(CONSOLEMES_DEBUG, implode(.@cmd$, " "));
+ }
+ else if (.@cmd$[0] == "flags")
+ {
+ set getvariableofnpc(.mg_flags, strnpcinfo(3)), axtoi(.@cmd$[1]);
+ }
+ else if (.@cmd$[0] == "flags_0")
+ {
+ .@flags = getvariableofnpc(.mg_flags, strnpcinfo(3));
+ .@flags &= ~axtoi(.@cmd$[1]);
+ set getvariableofnpc(.mg_flags, strnpcinfo(3)), .@flags;
+ }
+ else if (.@cmd$[0] == "flags_1")
+ {
+ .@flags = getvariableofnpc(.mg_flags, strnpcinfo(3));
+ .@flags |= axtoi(.@cmd$[1]);
+ set getvariableofnpc(.mg_flags, strnpcinfo(3)), .@flags;
+ }
+ else
+ {
+ consolemes(CONSOLEMES_DEBUG, "Unknown move graph cmd: " + .@cmd$[0]);
+ }
+ return 0;
+}
+
+function script getnextmovecmd {
+ .@cmds$ = getvariableofnpc(.nextcmd$, strnpcinfo(3));
+ .@firstCmd$ = .@cmds$;
+ .@restCmd$ = "moveon";
+ .@index = strpos(.@cmds$, ";");
+ if (.@index >= 0)
+ {
+ .@firstCmd$ = substr(.@cmds$, 0, .@index - 1);
+ .@restCmd$ = substr(.@cmds$, .@index + 1, getstrlen(.@cmds$) - 1);
+ }
+ // npcdebug "firstCmd = " + .@firstCmd$ + " restCmd = " + .@restCmd$;
+ set getvariableofnpc(.nextcmd$, strnpcinfo(3)), .@restCmd$;
+ return strip(.@firstCmd$);
+}
+
+// getrandompoint(x1,y1,x2,y2)
+// -- Get a random walkable point within a map rectangle
+// x1, y1 -- top-left corner of rectangle
+// x2, y2 -- bottom-right corner of rectangle
+// Returns 0 on success and -1 on error;
+// Since we cannot return multiple values, the random
+// coordinates are stored in NPC variables .move__rand_x, .move__rand_y
+function script getrandompoint {
+ if (getargcount() < 4)
+ {
+ consolemes(CONSOLEMES_DEBUG, "error: getrandompoint(x1, y1, x2, y2) takes 4 arguments");
+ return -1;
+ }
+
+ .@max_pokes = 10;
+ .@x1 = getarg(0);
+ .@y1 = getarg(1);
+ .@x2 = getarg(2);
+ .@y2 = getarg(3);
+ .@rx = -1; .@ry = -1;
+
+ getmapxy(.@map$, .@cx, .@cy, 1); // npc location
+
+ // let's try max_pokes random cells
+ for (.@poke = 0; .@poke < .@max_pokes; .@poke++)
+ {
+ .@rx = rand(.@x1, .@x2);
+ .@ry = rand(.@y1, .@y2);
+ if (checknpccell(.@map$, .@rx, .@ry, cell_chkpass))
+ goto L_Found;
+ }
+
+ // we check each cell from random middle point to the end
+ for (;.@rx <= .@x2; .@rx++)
+ {
+ for (;.@ry <= .@y2; .@ry++)
+ if (checknpccell(.@map$, .@rx, .@ry, cell_chkpass))
+ goto L_Found;
+ .@ry = .@y1;
+ }
+
+ // we check the rectangle from beginning to end
+ for (.@rx = .@x1; .@rx <= .@x2; .@rx++)
+ for (.@ry = .@y1; .@ry <= .@y2; .@ry++)
+ if (checknpccell(.@map$, .@rx, .@ry, cell_chkpass))
+ goto L_Found;
+
+ // finally, if we don't find anything
+ consolemes(CONSOLEMES_DEBUG, "error: getrandompoint: cannot find walkable cell in rectangle [(" + .@x1 + "," + .@y1 + ") , (" + .@x2 + "," + .@y2 + ")]");
+ return -1;
+
+L_Found:
+ set getvariableofnpc(.move__rand_x, strnpcinfo(3)), .@rx;
+ set getvariableofnpc(.move__rand_y, strnpcinfo(3)), .@ry;
+ return 0;
+}
+
+// wrapper function for npcwalkto. It can accept 4 parameters.
+// If #3 and #4 params are set, the walkto location is chosen
+// from rectangle (x1,y1,x2,y2).
+// It sets the npc variables .move_target_x, .move_target_y
+// that are used to resume NPC walking
+// Returns 1 if walking is possible, 0 otherwise;
+function script mg_npcwalkto {
+ if (getargcount() < 2)
+ {
+ consolemes(CONSOLEMES_DEBUG, "usage: mg_npcwalkto(x1,y1[,x2,y2])");
+ return -1;
+ }
+
+ .@x = getarg(0);
+ .@y = getarg(1);
+ .@x2 = getarg(2);
+ .@y2 = getarg(3);
+
+ if (getargcount() >= 4 && .@x2 > 0 && .@y2 > 0)
+ if (!getrandompoint(.@x, .@y, .@x2, .@y2))
+ {
+ .@x = getvariableofnpc(.move__rand_x, strnpcinfo(3));
+ .@y = getvariableofnpc(.move__rand_y, strnpcinfo(3));
+ }
+ else
+ return 0;
+
+ if (npcwalkto(.@x, .@y))
+ {
+ set getvariableofnpc(.move_target_x, strnpcinfo(3)), .@x;
+ set getvariableofnpc(.move_target_y, strnpcinfo(3)), .@y;
+ return 1;
+ }
+ return 0;
+}
+
+function script movetonextpoint {
+ .@wait = getvariableofnpc(.waitticks, strnpcinfo(3));
+ if (.@wait > 0)
+ {
+ .@wait--;
+ set getvariableofnpc(.waitticks, strnpcinfo(3)), .@wait;
+ return;
+ }
+
+ .@nextcmd$ = "";
+ while (.@nextcmd$ != "moveon")
+ {
+ .@nextcmd$ = getnextmovecmd();
+ npcdebug " " + .@nextcmd$;
+ if (execmovecmd(.@nextcmd$))
+ return;
+ }
+
+ // choose a random path from all possible paths
+ .@size = getarraysize(getvariableofnpc(.movepos_x1, strnpcinfo(3)));
+ .@pos = getvariableofnpc(.movepos, strnpcinfo(3));
+ .@curr_flags = getvariableofnpc(.mg_flags, strnpcinfo(3));
+ .@cur = 0;
+ .@weight_sum = 0;
+ // .@dbg$ = getvariableofnpc(.movegraphlabels$[.@pos], strnpcinfo(3)) + ": ";
+
+ for (.@i = 0; .@i < .@size; .@i++)
+ {
+ .@index = .@pos * .@size + .@i;
+ .@cmd$ = getvariableofnpc(.movegraphcmd$[.@index], strnpcinfo(3));
+ .@flags = getvariableofnpc(.movegraphflags[.@index], strnpcinfo(3));
+ if (.@cmd$ != "" &&
+ .@curr_flags & .@flags)
+ {
+ .@nextpos[.@cur] = .@i;
+ .@weights[.@cur] = getvariableofnpc(.movegraphweight[.@index], strnpcinfo(3));
+ // .@dbg$ += getvariableofnpc(.movegraphlabels$[.@i], strnpcinfo(3)) + "=" + .@weights[.@cur] + " ";
+ .@weight_sum += .@weights[.@cur];
+ .@cur++;
+ }
+ }
+ // npcdebug .@dbg$;
+
+ if (!.@weight_sum)
+ {
+ npcdebug("error: cannot pick next walk point. flags=" +
+ getvariableofnpc(.mg_flags, strnpcinfo(3)));
+ return;
+ }
+
+ .@pick_tries = 0;
+L_TryPick:
+ // pick a random number based on weight_sum
+ .@rnd = rand(.@weight_sum);
+ .@k = -1; .@weight_sum = 0;
+ while (.@rnd >= .@weight_sum)
+ {
+ .@k++;
+ .@weight_sum += .@weights[.@k];
+ }
+
+ .@next_idx = .@nextpos[.@k];
+ .@next_x1 = getvariableofnpc(.movepos_x1[.@next_idx], strnpcinfo(3));
+ .@next_y1 = getvariableofnpc(.movepos_y1[.@next_idx], strnpcinfo(3));
+ .@next_x2 = getvariableofnpc(.movepos_x2[.@next_idx], strnpcinfo(3));
+ .@next_y2 = getvariableofnpc(.movepos_y2[.@next_idx], strnpcinfo(3));
+
+ if (!mg_npcwalkto(.@next_x1, .@next_y1, .@next_x2, .@next_y2))
+ {
+ if (.@pick_tries < 10)
+ {
+ .@pick_tries++;
+ goto L_TryPick;
+ }
+
+ // move to a nearby position
+ .@x1 = getvariableofnpc(.movepos_x1[.@pos], strnpcinfo(3));
+ .@y1 = getvariableofnpc(.movepos_y1[.@pos], strnpcinfo(3));
+ .@x2 = getvariableofnpc(.movepos_x2[.@pos], strnpcinfo(3));
+ .@y2 = getvariableofnpc(.movepos_y2[.@pos], strnpcinfo(3));
+ mg_npcwalkto(.@x1, .@y1, .@x2, .@y2);
+ set getvariableofnpc(.nextcmd$, strnpcinfo(3)), "wait 1";
+
+ return;
+ }
+
+ if (getvariableofnpc(.debug, strnpcinfo(3)))
+ {
+ getmapxy(.@map$, .@cx, .@cy, 1);
+ .@dist = distance(.@cx, .@cy, .@next_x1, .@next_y1);
+ npcdebug("moving to " + getvariableofnpc(.movegraphlabels$[.@next_idx], strnpcinfo(3)) +
+ " ("+ getvariableofnpc(.move_target_x, strnpcinfo(3)) +
+ "," + getvariableofnpc(.move_target_y, strnpcinfo(3)) +
+ ") [distance=" + .@dist +
+ "] flags=" + getvariableofnpc(.mg_flags, strnpcinfo(3)));
+ }
+
+ .@nextcmd$ = getvariableofnpc(.movegraphcmd$[.@pos * .@size + .@next_idx], strnpcinfo(3));
+ set getvariableofnpc(.nextcmd$, strnpcinfo(3)), .@nextcmd$;
+ set getvariableofnpc(.movepos, strnpcinfo(3)), .@next_idx;
+ return;
+}
+
+// initial actions for npc when using move graphs.
+// function can accept 2 arguments:
+// 1: action sequence, for example "speed 200; dir 4". Default is "moveon"
+// 2: start point label. Default is #0 from move graph labels
+function script firstmove {
+ .@nextcmd$ = getarg(0, "moveon");
+ .@initpos = findmovegraphlabel(getarg(1, ""));
+ if (.@initpos < 0) .@initpos = 0;
+
+ set getvariableofnpc(.movepos, strnpcinfo(3)), .@initpos;
+ movenpc strnpcinfo(3), getvariableofnpc(.movepos_x1[.@initpos], strnpcinfo(3)),
+ getvariableofnpc(.movepos_y1[.@initpos], strnpcinfo(3));
+ set getvariableofnpc(.nextcmd$, strnpcinfo(3)), .@nextcmd$;
+ set getvariableofnpc(.waitticks, strnpcinfo(3)), -1;
+ set getvariableofnpc(.mg_flags, strnpcinfo(3)), 0xffff;
+ movetonextpoint;
+ return;
+}
+
+function script npc_pausemove {
+ stopnpctimer;
+ .@move_after = 0;
+
+ if (isunitwalking())
+ {
+ .@move_after = 1;
+ npcwalkto .x, .y;
+ npcstop;
+ }
+ set getvariableofnpc(.move_after_pause, strnpcinfo(3)), .@move_after;
+
+ return 0;
+}
+
+function script npc_resumemove {
+ startnpctimer;
+
+ if (getvariableofnpc(.move_after_pause, strnpcinfo(3)))
+ {
+ .@x = getvariableofnpc(.move_target_x, strnpcinfo(3));
+ .@y = getvariableofnpc(.move_target_y, strnpcinfo(3));
+ npcwalkto .@x, .@y;
+ }
+
+ return 0;
+}
+
+// npc_turntoxy(x,y)
+// turn npc toward an object at position (x,y)
+function script npc_turntoxy {
+ .@target_x = getarg(0);
+ .@target_y = getarg(1);
+ .@dx = abs(.@target_x - .x);
+ .@dy = abs(.@target_y - .y);
+
+ if (.@dx > .@dy)
+ .dir = .@target_x >= .x ? 6 : 2;
+ else
+ .dir = .@target_y >= .y ? 0 : 4;
+
+ return 0;
+}
+
+function script dographmovestep {
+ if (!isunitwalking())
+ {
+ movetonextpoint;
+ }
+ initnpctimer;
+ end;
+}
diff --git a/npc/functions/openbook.txt b/npc/functions/openbook.txt
new file mode 100644
index 00000000..cb1a0b11
--- /dev/null
+++ b/npc/functions/openbook.txt
@@ -0,0 +1,34 @@
+// Evol functions.
+// Author:
+// Reid
+// Description:
+// Narrator dialogue to show the selected book.
+
+function script openbook {
+ .@book_name$ = getarg(0, getvariableofnpc(.book_name$, strnpcinfo(0)));
+
+ narrator(S_LAST_NEXT,
+ l("You open a book named \"%s\".", .@book_name$),
+ l("Do you want to read it?"));
+
+ return (select("Yes.", "No.") == 1);
+}
+
+function script openbookshelf {
+ .@book_name$ = getarg(0, getvariableofnpc(.book_name$, strnpcinfo(0)));
+
+ narrator(S_LAST_NEXT,
+ l("You see a dust covered book on the shelf..."),
+ l("The name of the book is \"%s\".", .@book_name$),
+ l("Do you want to read it?"));
+
+ return (select("Yes.", "No.") == 1);
+}
+
+function script openoldbook {
+ narrator S_LAST_NEXT,
+ l("You open the book, but it looks like the sea water and time damaged it severely. Some pages are not readable anymore. Some others are simply missing."),
+ l("The old book seems to tell about the legend of Aemil. Would you like to read it?");
+
+ return (select("Yes.", "No.") == 1);
+}
diff --git a/npc/functions/permissions.txt b/npc/functions/permissions.txt
new file mode 100644
index 00000000..96e69fde
--- /dev/null
+++ b/npc/functions/permissions.txt
@@ -0,0 +1,35 @@
+// Evol scripts.
+// Author:
+// gumi
+// Description:
+// checks player permissions
+// ** admins are implicitly everything
+
+// administrator
+function script is_admin {
+ return has_permission(PERM_USE_ALL_COMMANDS, getarg(0, getcharid(CHAR_ID_ACCOUNT)));
+}
+
+// any staff member
+function script is_trusted {
+ return is_admin(getarg(0, getcharid(CHAR_ID_ACCOUNT))) ||
+ has_permission("show_client_version", getarg(0, getcharid(CHAR_ID_ACCOUNT)));
+}
+
+// developer
+function script is_dev {
+ return is_admin(getarg(0, getcharid(CHAR_ID_ACCOUNT))) ||
+ has_permission(PERM_RECEIVE_REQUESTS, getarg(0, getcharid(CHAR_ID_ACCOUNT)));
+}
+
+// event coordinator
+function script is_evtc {
+ return is_admin(getarg(0, getcharid(CHAR_ID_ACCOUNT))) ||
+ can_use_command("@monster", getarg(0, getcharid(CHAR_ID_ACCOUNT)));
+}
+
+// game master
+function script is_gm {
+ return is_admin(getarg(0, getcharid(CHAR_ID_ACCOUNT))) ||
+ can_use_command("@jail", getarg(0, getcharid(CHAR_ID_ACCOUNT)));
+}
diff --git a/npc/functions/player-cache.txt b/npc/functions/player-cache.txt
new file mode 100644
index 00000000..f134f953
--- /dev/null
+++ b/npc/functions/player-cache.txt
@@ -0,0 +1,501 @@
+// Player cache system
+//
+// Holds a cache of online players in-memory even after logout. This greatly
+// reduces the need to query the SQL database for data that is frequently
+// accessed.
+//
+// NOTE: This NPC uses the new public/private function call system to avoid
+// polluting the global function namespace
+//
+// Example usage:
+// .@account_id = "playerCache"::name2account("Reid");
+//
+
+- script playerCache FAKE_NPC,{
+ /**
+ * "playerCache"::name2account("char name") => account id
+ *
+ * Searches through the player cache to convert a char name to a account ID.
+ * If the player is not found, checks the SQL table. If the player doesn't
+ * exist, returns false.
+ *
+ * @param 0 - the char name to search
+ * @return the account id of the char
+ */
+ public function name2account {
+ if (getarg(0) == "") {
+ return false;
+ }
+
+ if (.@acc = getcharid(CHAR_ID_ACCOUNT, getarg(0))) {
+ // player is currently online
+ return .@acc;
+ }
+
+ if (.@acc = htget(.name_to_account, getarg(0), 0)) {
+ // player found in the hash table
+ return .@acc;
+ }
+
+ // player still not found: now we try SQL
+ .@name$ = escape_sql(getarg(0));
+
+ if (SERVER_USES_VAULT) {
+ query_sql(sprintf("SELECT c.account_id, c.char_id, r.value "
+ "FROM `char` c "
+ "JOIN `global_acc_reg_num_db` r ON r.account_id = c.account_id "
+ "WHERE r.key = '##VAULT' AND r.index = 0 AND c.name = '%s' "
+ "LIMIT 1;", .@name$),
+ .@acc, .@char, .@vault);
+
+ if (.@vault > 0) {
+ .vault_to_account[.@vault] = .@acc;
+ .account_to_vault[.@acc] = .@vault;
+ }
+ } else {
+ query_sql(sprintf("SELECT account_id, char_id FROM `char` WHERE `name`='%s' LIMIT 1;", .@name$), .@acc, .@char);
+ }
+
+ if (.@acc > 0) {
+ // player found: add to our cache
+ htput(.name_to_account, getarg(0), .@acc);
+ .account_to_name$[.@acc] = getarg(0);
+ htput(.name_to_char, getarg(0), .@char);
+ .char_to_name$[.@char] = getarg(0);
+
+ return .@acc;
+ }
+
+ // player doesn't exist
+ return false;
+ }
+
+ /**
+ * "playerCache"::name2char("char name") => char id
+ *
+ * Searches through the player cache to convert a char name to a char ID.
+ * If the player is not found, checks the SQL table. If the player doesn't
+ * exist, returns false.
+ *
+ * @param 0 - the char name to search
+ * @return the char id of the char
+ */
+ public function name2char {
+ if ((.@acc = name2account(getarg(0))) != 0) {
+ return htget(.name_to_char, getarg(0), false);
+ }
+
+ return false;
+ }
+
+ /**
+ * "playerCache"::char2account(char id) => account id
+ *
+ * Searches through the player cache to convert a char ID to an account ID.
+ * If the player is not found, checks the SQL table. If the player doesn't
+ * exist, returns false.
+ *
+ * @param 0 - the char ID to search
+ * @return the account id of the char
+ */
+ public function char2account {
+ if (getarg(0) == 0) {
+ return false;
+ }
+
+ .@name$ = .char_to_name$[getarg(0)];
+
+ if (.@name$ != "") {
+ if (.@acc = getcharid(CHAR_ID_ACCOUNT, .@name$)) {
+ // player is currently online
+ return .@acc;
+ }
+
+ if (.@acc = htget(.name_to_account, .@name$, 0)) {
+ // player found in the hash table
+ return .@acc;
+ }
+ }
+
+ // player still not found: now we try SQL
+ if (SERVER_USES_VAULT) {
+ query_sql(sprintf("SELECT c.account_id, c.name, r.value "
+ "FROM `char` c "
+ "JOIN `global_acc_reg_num_db` r ON r.account_id = c.account_id "
+ "WHERE r.key = '##VAULT' AND r.index = 0 AND c.char_id = '%d' "
+ "LIMIT 1;", getarg(0)),
+ .@acc, .@name$, .@vault);
+
+ if (.@vault > 0) {
+ .vault_to_account[.@vault] = .@acc;
+ .account_to_vault[.@acc] = .@vault;
+ }
+ } else {
+ query_sql(sprintf("SELECT account_id, name FROM `char` WHERE `char_id`='%d' LIMIT 1;", getarg(0)), .@acc, .@name$);
+ }
+
+ if (.@acc > 0) {
+ // player found: add to our cache
+ htput(.name_to_account, .@name$, .@acc);
+ .account_to_name$[.@acc] = .@name$;
+ htput(.name_to_char, .@name$, getarg(0));
+ .char_to_name$[getarg(0)] = .@name$;
+
+ return .@acc;
+ }
+
+ // player doesn't exist
+ return false;
+ }
+
+ /**
+ * "playerCache"::account2char(account id) => char id
+ *
+ * Searches through the player cache to convert an account ID into a
+ * char ID. If the player is not found, returns false.
+ *
+ * NOTE: this is a weak reference; an account ID does not uniquely identify
+ * a character
+ *
+ * @param 0 - the account ID to search for
+ * @return the char ID of the char
+ */
+ public function account2char {
+ if (getarg(0) == 0) {
+ return false;
+ }
+
+ if ((.@name$ = strcharinfo(PC_NAME, "", getarg(0))) != "") {
+ // player is online
+ return getcharid(CHAR_ID_CHAR, .@name$);
+ } else if ((.@name$ = .account_to_name$[getarg(0)]) != "") {
+ // found in our cache
+ return htget(.name_to_char, .@name$, false);
+ }
+
+ // player still not found: now we try SQL
+ if (SERVER_USES_VAULT) {
+ query_sql(sprintf("SELECT c.char_id, c.name, r.value "
+ "FROM `char` c "
+ "JOIN `global_acc_reg_num_db` r ON r.account_id = c.account_id "
+ "WHERE r.key = '##VAULT' AND r.index = 0 AND c.account_id = '%d' "
+ "ORDER BY c.last_login DESC LIMIT 1;", getarg(0)),
+ .@char, .@name$, .@vault);
+
+ if (.@vault > 0) {
+ .vault_to_account[.@vault] = getarg(0);
+ .account_to_vault[getarg(0)] = .@vault;
+ }
+ } else {
+ query_sql(sprintf("SELECT char_id, name FROM `char` WHERE account_id='%d' ORDER BY last_login DESC LIMIT 1;", getarg(0)), .@char, .@name$);
+ }
+
+ if (.@char > 0) {
+ // player found: add to our cache
+ htput(.name_to_account, .@name$, getarg(0));
+ .account_to_name$[getarg(0)] = .@name$;
+ htput(.name_to_char, .@name$, .@char);
+ .char_to_name$[.@char] = .@name$;
+
+ return .@char;
+ }
+
+ // not found
+ return false;
+ }
+
+ /**
+ * "playerCache"::account2name(account id) => char name
+ *
+ * Searches through the player cache to convert an account ID into a
+ * char name. If the account is not found, returns an empty string.
+ *
+ * NOTE: this is a weak reference; an account ID does not uniquely identify
+ * a character
+ *
+ * @param 0 - the account ID to search for
+ * @return the name of the char
+ */
+ public function account2name {
+ if ((.@char = account2char(getarg(0))) != false) {
+ return .char_to_name$[.@char];
+ }
+
+ return "";
+ }
+
+ /**
+ * "playerCache"::char2name(char id) => char name
+ *
+ * Searches through the player cache to convert a char ID to a char name.
+ * If the player is not found, checks the SQL table. If the player doesn't
+ * exist, returns an empty string.
+ *
+ * @param 0 - the char ID to search
+ * @return the name of the char
+ */
+ public function char2name {
+ if ((.@acc = char2account(getarg(0))) != 0) {
+ return .account_to_name$[.@acc];
+ }
+
+ // player not found
+ return "";
+ }
+
+ /**
+ * "playerCache"::vault2account(vault id) => account id
+ *
+ * Searches through the player cache to convert a Vault account ID into a
+ * game account ID. If the account is not found, returns false
+ *
+ * NOTE: this is a weak reference; a Vault ID does not uniquely identify
+ * a game account
+ *
+ * @param 0 - the Vault ID to search for
+ * @return the account id of the char
+ */
+ public function vault2account {
+ if (getarg(0) == 0) {
+ return false;
+ }
+
+ if (!SERVER_USES_VAULT) {
+ return getarg(0);
+ } else if (.@acc = .vault_to_account[getarg(0)]) {
+ // found in the cache
+ return .@acc;
+ }
+
+ // player still not found: now we try SQL
+ query_sql(sprintf("SELECT c.account_id, c.char_id, c.name "
+ "FROM `char` c "
+ "JOIN `global_acc_reg_num_db` r ON r.account_id = c.account_id "
+ "WHERE r.key = '##VAULT' AND r.index = 0 AND r.value = %d "
+ "ORDER BY c.`last_login` DESC LIMIT 1;", getarg(0)),
+ .@acc, .@char, .@name$);
+
+ if (.@char > 0) {
+ // player found: add to our cache
+ htput(.name_to_account, .@name$, .@acc);
+ .account_to_name$[.@acc] = .@name$;
+ htput(.name_to_char, .@name$, .@char);
+ .char_to_name$[.@char] = .@name$;
+ .vault_to_account[getarg(0)] = .@acc;
+ .account_to_vault[.@acc] = getarg(0);
+
+ return .@acc;
+ }
+
+ return false;
+ }
+
+ /**
+ * "playerCache"::vault2char(vault id) => char id
+ *
+ * Searches through the player cache to convert a Vault account ID into a
+ * char id. If the player is not found, returns false
+ *
+ * NOTE: this is a weak reference; a Vault ID does not uniquely identify
+ * a character
+ *
+ * @param 0 - the Vault ID to search for
+ * @return the char id
+ */
+ public function vault2char {
+ if ((.@acc = vault2account(getarg(0))) != 0) {
+ return account2char(.@acc);
+ }
+
+ // player not found
+ return false;
+ }
+
+ /**
+ * "playerCache"::vault2name(vault id) => account id
+ *
+ * Searches through the player cache to convert a Vault account ID into a
+ * char name. If the player is not found, returns an empty string
+ *
+ * NOTE: this is a weak reference; a Vault ID does not uniquely identify
+ * a character
+ *
+ * @param 0 - the Vault ID to search for
+ * @return the name of the char
+ */
+ public function vault2name {
+ if ((.@acc = vault2account(getarg(0))) != 0) {
+ return account2name(.@acc);
+ }
+
+ // player not found
+ return "";
+ }
+
+ /**
+ * "playerCache"::account2vault(account id) => vault id
+ *
+ * Searches through the player cache to convert a game account ID into a
+ * Vault account ID. If the account is not found, returns false
+ *
+ * @param 0 - the account ID to search for
+ * @return the Vault ID associated with the account
+ */
+ public function account2vault {
+ if (!SERVER_USES_VAULT) {
+ return getarg(0);
+ }
+
+ account2char(getarg(0)); // will fetch vault id
+ return .account_to_vault[getarg(0)];
+ }
+
+ /**
+ * "playerCache"::name2vault(char name) => vault id
+ *
+ * Searches through the player cache to convert a character name into a
+ * Vault account ID. If the account is not found, returns false
+ *
+ * @param 0 - the char name to search for
+ * @return the Vault ID associated with the account
+ */
+ public function name2vault {
+ return account2vault(name2account(getarg(0)));
+ }
+
+ /**
+ * "playerCache"::char2vault(char id) => vault id
+ *
+ * Searches through the player cache to convert a char id into a
+ * Vault account ID. If the account is not found, returns false
+ *
+ * @param 0 - the char id to search for
+ * @return the Vault ID associated with the account
+ */
+ public function char2vault {
+ return account2vault(char2account(getarg(0)));
+ }
+
+ /**
+ * Registers a public local function that will be called when the char name
+ * associated with an account changes. The previous account ID will be
+ * passed as first argument to the provided function.
+ *
+ * @param 0 - the public local function
+ */
+ public function addNameHandler {
+ return addEventListener(.name_handlers, getarg(0));
+ }
+
+ /**
+ * Registers a public local function that will be called when the account
+ * associated with a Vault account changes. The previous account id will be
+ * passed as first argument to the provided function and the previous
+ * char name will be passed as second argument.
+ *
+ * @param 0 - the public local function
+ */
+ public function addVaultHandler {
+ return addEventListener(.vault_handlers, getarg(0));
+ }
+
+
+
+////////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * updates the char name cache and fires event handlers when the name
+ * associated with an account changes (such as when switching char)
+ */
+ private function updateName {
+ // get the cached name:
+ .@old$ = .account_to_name$[playerattached()];
+
+ // now update the cache:
+ htput(.name_to_account, strcharinfo(PC_NAME), playerattached());
+ .account_to_name$[playerattached()] = strcharinfo(PC_NAME);
+ htput(.name_to_char, strcharinfo(PC_NAME), getcharid(CHAR_ID_CHAR));
+ .char_to_name$[getcharid(CHAR_ID_CHAR)] = strcharinfo(PC_NAME);
+
+ if (.@old$ != "" && .@old$ != .account_to_name$[playerattached()]) {
+ // fire event handlers
+ dispatchEvent(.name_handlers, .@old$);
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * updates the Vault account cache and fires event handlers when the account
+ * associated with a Vault account changes (different game account on same
+ * Vault account)
+ */
+ private function updateVault {
+ // get the cached id:
+ .@old = .vault_to_account[getvaultid()];
+ .@old$ = .account_to_name$[.@old];
+
+ // now update the cache:
+ .vault_to_account[getvaultid()] = playerattached();
+ .account_to_vault[playerattached()] = getvaultid();
+
+ if (.@old > 0 && .@old != .vault_to_account[getvaultid()]) {
+ // fire event handlers
+ dispatchEvent(.vault_handlers, .@old, .@old$);
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * adds/updates the player to the player cache
+ */
+ private function updateCache {
+ updateName();
+
+ if (SERVER_USES_VAULT) {
+ updateVault();
+ }
+
+ return;
+ }
+
+ /**
+ * force-add all online players to the cache (used on init and reload)
+ */
+ private function forceCache {
+ .@count = getunits(BL_PC, .@units, false);
+
+ for (.@i = 0; .@i < .@count; ++.@i) {
+ attachrid(.@units[.@i]);
+ updateCache();
+ detachrid();
+ }
+
+ return;
+ }
+
+OnPCLoginEvent:
+ updateCache();
+ end;
+
+OnInit:
+ .name_to_account = htnew(); // Map<char name, account id>
+ .account_to_name$[0] = ""; // sparse Array<char name> indexed by account id
+ .name_to_char = htnew(); // Map<char name, char id>
+ .char_to_name$[0] = ""; // sparse Array<char name> indexed by char id
+
+ if (SERVER_USES_VAULT) {
+ .vault_to_account[0] = 0; // sparse Array<vault id> indexed by account id
+ .account_to_vault[0] = 0; // sparse Array<account id> indexed by vault id
+ }
+
+ // event handlers:
+ .vault_handlers = htnew();
+ .name_handlers = htnew();
+
+ // reloadscript handler:
+ forceCache();
+}
diff --git a/npc/functions/quest-debug/000-ShipQuests_Julia.txt b/npc/functions/quest-debug/000-ShipQuests_Julia.txt
new file mode 100644
index 00000000..e4e0ec2c
--- /dev/null
+++ b/npc/functions/quest-debug/000-ShipQuests_Julia.txt
@@ -0,0 +1,37 @@
+// Julia quest debug
+// Author:
+// gumi
+
+function script QuestDebug0 {
+ do
+ {
+ clear;
+ setnpcdialogtitle l("Quest debug");
+ mes "ShipQuests_Julia";
+ mes "---";
+ mes l("Quest state: @@", getq(ShipQuests_Julia));
+ mes "---";
+ mes l("Related quests:");
+ mes "ShipQuests_Nard: " + getq(ShipQuests_Nard);
+ mes "ShipQuests_ChefGado: " + getq(ShipQuests_ChefGado);
+ mes "General_Narrator: " + getq(General_Narrator);
+ next;
+
+ GenericQuestDebug ShipQuests_Julia,
+ menuimage("actions/manage", l("Debug Nard quest")), -1,
+ menuimage("actions/manage", l("Debug Gado quest")), -2,
+ menuimage("actions/manage", l("Debug Narrator")), -3,
+ l("Does not have the quest"), 0,
+ l("Got the quest"), 1,
+ l("Completed"), 2;
+
+ switch (@menuret)
+ {
+ case -1: callfunc "QuestDebug4"; break;
+ case -2: callfunc "QuestDebug17"; break;
+ case -3: callfunc "QuestDebug28"; break;
+ default: if (@menuret < 0) return;
+ }
+
+ } while (1);
+}
diff --git a/npc/functions/quest-debug/001-ShipQuests_Arpan.txt b/npc/functions/quest-debug/001-ShipQuests_Arpan.txt
new file mode 100644
index 00000000..26c49646
--- /dev/null
+++ b/npc/functions/quest-debug/001-ShipQuests_Arpan.txt
@@ -0,0 +1,27 @@
+// Arpan quest debug
+// Author:
+// gumi
+
+function script QuestDebug1 {
+ do
+ {
+ clear;
+ setnpcdialogtitle l("Quest debug");
+ mes "ShipQuests_Arpan";
+ mes "---";
+ mes l("Quest state: @@", getq(ShipQuests_Arpan));
+ next;
+
+ GenericQuestDebug ShipQuests_Arpan,
+ l("Arpan is waiting for you"), 0,
+ l("Arpan told you to open the chest"), 1,
+ l("You opened the chest"), 2,
+ l("Completed"), 3;
+
+ if (@menuret < 0)
+ {
+ return;
+ }
+
+ } while (1);
+}
diff --git a/npc/functions/quest-debug/002-ShipQuests_Alige.txt b/npc/functions/quest-debug/002-ShipQuests_Alige.txt
new file mode 100644
index 00000000..36d0ce23
--- /dev/null
+++ b/npc/functions/quest-debug/002-ShipQuests_Alige.txt
@@ -0,0 +1,27 @@
+// Alige quest debug
+// Author:
+// gumi
+
+function script QuestDebug2 {
+ do
+ {
+ clear;
+ setnpcdialogtitle l("Quest debug");
+ mes "ShipQuests_Alige";
+ mes "---";
+ mes l("Quest state: @@", getq(ShipQuests_Alige));
+ next;
+
+ GenericQuestDebug ShipQuests_Alige,
+ l("Does not have the quest"), 0,
+ l("First encounter"), 1,
+ l("Alige asks for food"), 2,
+ l("Completed"), 3;
+
+ if (@menuret < 0)
+ {
+ return;
+ }
+
+ } while (1);
+}
diff --git a/npc/functions/quest-debug/003-ShipQuests_Peter.txt b/npc/functions/quest-debug/003-ShipQuests_Peter.txt
new file mode 100644
index 00000000..4732d238
--- /dev/null
+++ b/npc/functions/quest-debug/003-ShipQuests_Peter.txt
@@ -0,0 +1,36 @@
+// Peter quest debug
+// Author:
+// gumi
+// jesusalva
+// Notes:
+// Using l() usually is not a good idea (translating debug text? What?)
+
+function script QuestDebug3 {
+ do
+ {
+ clear;
+ setnpcdialogtitle l("Quest debug");
+ mes "ShipQuests_Peter";
+ mes "---";
+ mes l("Quest state: @@", getq(ShipQuests_Peter));
+ mes l("Killed mob bitmask: @@", getq2(ShipQuests_Peter));
+ mes l("Instance ID: @@", getq3(ShipQuests_Peter));
+ next;
+
+ .@q=getq(ShipQuests_Peter);
+
+ GenericQuestDebug ShipQuests_Peter,
+ l("Does not have the quest"), 0,
+ l("Peter needs help"), 1,
+ l("Toggle Tortuga Bounty"), .@q^2,
+ l("Toggle Ratto Bounty"), .@q^4,
+ l("Toggle Croc Bounty"), .@q^8,
+ l("Quest complete"), 15;
+
+ if (@menuret < 0)
+ {
+ return;
+ }
+
+ } while (1);
+}
diff --git a/npc/functions/quest-debug/004-ShipQuests_Nard.txt b/npc/functions/quest-debug/004-ShipQuests_Nard.txt
new file mode 100644
index 00000000..9ec51cd8
--- /dev/null
+++ b/npc/functions/quest-debug/004-ShipQuests_Nard.txt
@@ -0,0 +1,38 @@
+// Nard quest debug
+// Author:
+// gumi
+
+function script QuestDebug4 {
+ do
+ {
+ clear;
+ setnpcdialogtitle l("Quest debug");
+ mes "ShipQuests_Nard";
+ mes "---";
+ mes l("Quest state: @@", getq(ShipQuests_Nard));
+ mes "---";
+ mes l("Subquests:");
+ mes "ShipQuests_Gugli: " + getq(ShipQuests_Gugli);
+ mes "ShipQuests_ChefGado: " + getq(ShipQuests_ChefGado);
+ next;
+
+ GenericQuestDebug ShipQuests_Nard,
+ l("Does not have the quest"), 0,
+ l("Nard asks to help crew"), 1,
+ menuimage("actions/manage", l("Debug Gugli quest")), -1,
+ l("Completed Gugli's task"), 2,
+ l("Nard asks to solve conflict"), 3,
+ menuimage("actions/manage", l("Debug Gado quest")), -2,
+ l("Solved conflict"), 4,
+ l("Official crew member"), 5,
+ l("Talked to narrator"), 6;
+
+ switch (@menuret)
+ {
+ case -1: callfunc "QuestDebug16"; break;
+ case -2: callfunc "QuestDebug17"; break;
+ default: if (@menuret < 0) return;
+ }
+
+ } while (1);
+}
diff --git a/npc/functions/quest-debug/005-ShipQuests_Knife.txt b/npc/functions/quest-debug/005-ShipQuests_Knife.txt
new file mode 100644
index 00000000..a5ec1ae8
--- /dev/null
+++ b/npc/functions/quest-debug/005-ShipQuests_Knife.txt
@@ -0,0 +1,25 @@
+// Knife quest debug
+// Author:
+// gumi
+
+function script QuestDebug5 {
+ do
+ {
+ clear;
+ setnpcdialogtitle l("Quest debug");
+ mes "ShipQuests_Knife";
+ mes "---";
+ mes l("Quest state: @@", getq(ShipQuests_Knife));
+ next;
+
+ GenericQuestDebug ShipQuests_Knife,
+ l("Does not have the quest"), 0,
+ l("Got the knife"), 1;
+
+ if (@menuret < 0)
+ {
+ return;
+ }
+
+ } while (1);
+}
diff --git a/npc/functions/quest-debug/006-ShipQuests_ArpanMoney.txt b/npc/functions/quest-debug/006-ShipQuests_ArpanMoney.txt
new file mode 100644
index 00000000..0f77c1b9
--- /dev/null
+++ b/npc/functions/quest-debug/006-ShipQuests_ArpanMoney.txt
@@ -0,0 +1,27 @@
+// Money quest debug
+// Author:
+// gumi
+
+function script QuestDebug6 {
+ do
+ {
+ clear;
+ setnpcdialogtitle l("Quest debug");
+ mes "ShipQuests_ArpanMoney";
+ mes "---";
+ mes l("Quest state: @@", getq(ShipQuests_ArpanMoney));
+ next;
+
+ GenericQuestDebug ShipQuests_ArpanMoney,
+ l("Does not have the quest"), 0,
+ l("Elmo told about money"), 1,
+ l("Arpan gave money"), 2,
+ l("Arpan gave clothes"), 3;
+
+ if (@menuret < 0)
+ {
+ return;
+ }
+
+ } while (1);
+}
diff --git a/npc/functions/quest-debug/007-ShipQuests_Door.txt b/npc/functions/quest-debug/007-ShipQuests_Door.txt
new file mode 100644
index 00000000..88aa00ab
--- /dev/null
+++ b/npc/functions/quest-debug/007-ShipQuests_Door.txt
@@ -0,0 +1,25 @@
+// Door quest debug
+// Author:
+// gumi
+
+function script QuestDebug7 {
+ do
+ {
+ clear;
+ setnpcdialogtitle l("Quest debug");
+ mes "ShipQuests_Door";
+ mes "---";
+ mes l("Quest state: @@", getq(ShipQuests_Door));
+ next;
+
+ GenericQuestDebug ShipQuests_Door,
+ l("Does not have the quest"), 0,
+ l("Heard conversation"), 1;
+
+ if (@menuret < 0)
+ {
+ return;
+ }
+
+ } while (1);
+}
diff --git a/npc/functions/quest-debug/008-ShipQuests_Couwan.txt b/npc/functions/quest-debug/008-ShipQuests_Couwan.txt
new file mode 100644
index 00000000..3df0cdbf
--- /dev/null
+++ b/npc/functions/quest-debug/008-ShipQuests_Couwan.txt
@@ -0,0 +1,26 @@
+// Couwan quest debug
+// Author:
+// gumi
+
+function script QuestDebug8 {
+ do
+ {
+ clear;
+ setnpcdialogtitle l("Quest debug");
+ mes "ShipQuests_Couwan";
+ mes "---";
+ mes l("Quest state: @@", getq(ShipQuests_Couwan));
+ next;
+
+ GenericQuestDebug ShipQuests_Couwan,
+ l("Does not have the quest"), 0,
+ l("Couwan asks to deliver box"), 1,
+ l("Delivered box, got reward"), 2;
+
+ if (@menuret < 0)
+ {
+ return;
+ }
+
+ } while (1);
+}
diff --git a/npc/functions/quest-debug/009-ShipQuests_TreasureChest.txt b/npc/functions/quest-debug/009-ShipQuests_TreasureChest.txt
new file mode 100644
index 00000000..97adef46
--- /dev/null
+++ b/npc/functions/quest-debug/009-ShipQuests_TreasureChest.txt
@@ -0,0 +1,25 @@
+// Treasure chest quest debug
+// Author:
+// gumi
+
+function script QuestDebug9 {
+ do
+ {
+ clear;
+ setnpcdialogtitle l("Quest debug");
+ mes "ShipQuests_TreasureChest";
+ mes "---";
+ mes l("Quest state: @@", getq(ShipQuests_TreasureChest));
+ next;
+
+ GenericQuestDebug ShipQuests_TreasureChest,
+ l("Does not have the quest"), 0,
+ l("Opened treasure chest"), 1;
+
+ if (@menuret < 0)
+ {
+ return;
+ }
+
+ } while (1);
+}
diff --git a/npc/functions/quest-debug/010-ShipQuests_Ale.txt b/npc/functions/quest-debug/010-ShipQuests_Ale.txt
new file mode 100644
index 00000000..e5422122
--- /dev/null
+++ b/npc/functions/quest-debug/010-ShipQuests_Ale.txt
@@ -0,0 +1,25 @@
+// Part of Gugli quest debug
+// Author:
+// gumi
+
+function script QuestDebug10 {
+ do
+ {
+ clear;
+ setnpcdialogtitle l("Quest debug");
+ mes "ShipQuests_Ale";
+ mes "---";
+ mes l("Quest state: @@", getq(ShipQuests_Ale));
+ next;
+
+ GenericQuestDebug ShipQuests_Ale,
+ l("Does not have the quest"), 0,
+ l("Got the package"), 1;
+
+ if (@menuret < 0)
+ {
+ return;
+ }
+
+ } while (1);
+}
diff --git a/npc/functions/quest-debug/011-ShipQuests_Astapolos.txt b/npc/functions/quest-debug/011-ShipQuests_Astapolos.txt
new file mode 100644
index 00000000..aca065ed
--- /dev/null
+++ b/npc/functions/quest-debug/011-ShipQuests_Astapolos.txt
@@ -0,0 +1,25 @@
+// Part of Gugli quest debug
+// Author:
+// gumi
+
+function script QuestDebug11 {
+ do
+ {
+ clear;
+ setnpcdialogtitle l("Quest debug");
+ mes "ShipQuests_Astapolos";
+ mes "---";
+ mes l("Quest state: @@", getq(ShipQuests_Astapolos));
+ next;
+
+ GenericQuestDebug ShipQuests_Astapolos,
+ l("Does not have the quest"), 0,
+ l("Got the package"), 1;
+
+ if (@menuret < 0)
+ {
+ return;
+ }
+
+ } while (1);
+}
diff --git a/npc/functions/quest-debug/012-ShipQuests_Gulukan.txt b/npc/functions/quest-debug/012-ShipQuests_Gulukan.txt
new file mode 100644
index 00000000..fac7ccde
--- /dev/null
+++ b/npc/functions/quest-debug/012-ShipQuests_Gulukan.txt
@@ -0,0 +1,25 @@
+// Part of Gugli quest debug
+// Author:
+// gumi
+
+function script QuestDebug12 {
+ do
+ {
+ clear;
+ setnpcdialogtitle l("Quest debug");
+ mes "ShipQuests_Gulukan";
+ mes "---";
+ mes l("Quest state: @@", getq(ShipQuests_Gulukan));
+ next;
+
+ GenericQuestDebug ShipQuests_Gulukan,
+ l("Does not have the quest"), 0,
+ l("Got the package"), 1;
+
+ if (@menuret < 0)
+ {
+ return;
+ }
+
+ } while (1);
+}
diff --git a/npc/functions/quest-debug/013-ShipQuests_Jalad.txt b/npc/functions/quest-debug/013-ShipQuests_Jalad.txt
new file mode 100644
index 00000000..9ae6ed2a
--- /dev/null
+++ b/npc/functions/quest-debug/013-ShipQuests_Jalad.txt
@@ -0,0 +1,25 @@
+// Part of Gugli quest debug
+// Author:
+// gumi
+
+function script QuestDebug13 {
+ do
+ {
+ clear;
+ setnpcdialogtitle l("Quest debug");
+ mes "ShipQuests_Jalad";
+ mes "---";
+ mes l("Quest state: @@", getq(ShipQuests_Jalad));
+ next;
+
+ GenericQuestDebug ShipQuests_Jalad,
+ l("Does not have the quest"), 0,
+ l("Got the package"), 1;
+
+ if (@menuret < 0)
+ {
+ return;
+ }
+
+ } while (1);
+}
diff --git a/npc/functions/quest-debug/014-ShipQuests_QMuller.txt b/npc/functions/quest-debug/014-ShipQuests_QMuller.txt
new file mode 100644
index 00000000..8a25ee65
--- /dev/null
+++ b/npc/functions/quest-debug/014-ShipQuests_QMuller.txt
@@ -0,0 +1,25 @@
+// Part of Gugli quest debug
+// Author:
+// gumi
+
+function script QuestDebug14 {
+ do
+ {
+ clear;
+ setnpcdialogtitle l("Quest debug");
+ mes "ShipQuests_QMuller";
+ mes "---";
+ mes l("Quest state: @@", getq(ShipQuests_QMuller));
+ next;
+
+ GenericQuestDebug ShipQuests_QMuller,
+ l("Does not have the quest"), 0,
+ l("Got the package"), 1;
+
+ if (@menuret < 0)
+ {
+ return;
+ }
+
+ } while (1);
+}
diff --git a/npc/functions/quest-debug/015-ShipQuests_Tibbo.txt b/npc/functions/quest-debug/015-ShipQuests_Tibbo.txt
new file mode 100644
index 00000000..84280ebf
--- /dev/null
+++ b/npc/functions/quest-debug/015-ShipQuests_Tibbo.txt
@@ -0,0 +1,25 @@
+// Part of Gugli quest debug
+// Author:
+// gumi
+
+function script QuestDebug15 {
+ do
+ {
+ clear;
+ setnpcdialogtitle l("Quest debug");
+ mes "ShipQuests_Tibbo";
+ mes "---";
+ mes l("Quest state: @@", getq(ShipQuests_Tibbo));
+ next;
+
+ GenericQuestDebug ShipQuests_Tibbo,
+ l("Does not have the quest"), 0,
+ l("Got the package"), 1;
+
+ if (@menuret < 0)
+ {
+ return;
+ }
+
+ } while (1);
+}
diff --git a/npc/functions/quest-debug/016-ShipQuests_Gugli.txt b/npc/functions/quest-debug/016-ShipQuests_Gugli.txt
new file mode 100644
index 00000000..e4868ff4
--- /dev/null
+++ b/npc/functions/quest-debug/016-ShipQuests_Gugli.txt
@@ -0,0 +1,48 @@
+// Part of Gugli quest debug
+// Author:
+// gumi
+
+function script QuestDebug16 {
+ do
+ {
+ clear;
+ setnpcdialogtitle l("Quest debug");
+ mes "ShipQuests_Gugli";
+ mes "---";
+ mes l("Quest state: @@", getq(ShipQuests_Gugli));
+ mes "---";
+ mes l("Subquests:");
+ mes "ShipQuests_Ale: " + getq(ShipQuests_Ale);
+ mes "ShipQuests_Astapolos: " + getq(ShipQuests_Astapolos);
+ mes "ShipQuests_Gulukan: " + getq(ShipQuests_Gulukan);
+ mes "ShipQuests_Jalad: " + getq(ShipQuests_Jalad);
+ mes "ShipQuests_QMuller: " + getq(ShipQuests_QMuller);
+ mes "ShipQuests_Tibbo: " + getq(ShipQuests_Tibbo);
+ next;
+
+ GenericQuestDebug ShipQuests_Gugli,
+ l("Does not have the quest"), 0,
+ l("Gugli asks to collect packages"), 1,
+ menuimage("actions/manage", l("Reset subquests")), -1,
+ menuimage("actions/manage", l("Set subquests as completed")), -2,
+ l("Got reward from gugli"), 2;
+
+ switch (@menuret)
+ {
+ case 0:
+ case 2:
+ case -1:
+ case -2:
+ .@v = (@menuret == -2 || @menuret == 2);
+ setq ShipQuests_Ale, .@v;
+ setq ShipQuests_Astapolos, .@v;
+ setq ShipQuests_Gulukan, .@v;
+ setq ShipQuests_Jalad, .@v;
+ setq ShipQuests_QMuller, .@v;
+ setq ShipQuests_Tibbo, .@v;
+ break;
+ default: if (@menuret < 0) return;
+ }
+
+ } while (1);
+}
diff --git a/npc/functions/quest-debug/017-ShipQuests_ChefGado.txt b/npc/functions/quest-debug/017-ShipQuests_ChefGado.txt
new file mode 100644
index 00000000..05e8cdb0
--- /dev/null
+++ b/npc/functions/quest-debug/017-ShipQuests_ChefGado.txt
@@ -0,0 +1,30 @@
+// Gado quest debug
+// Author:
+// gumi
+
+function script QuestDebug17 {
+ do
+ {
+ clear;
+ setnpcdialogtitle l("Quest debug");
+ mes "ShipQuests_ChefGado";
+ mes "---";
+ mes l("Quest state: @@", getq(ShipQuests_ChefGado));
+ next;
+
+ GenericQuestDebug ShipQuests_ChefGado,
+ l("Does not have the quest"), 0,
+ l("Nard asks to solve conflict"), 1,
+ l("Got poison from Gado"), 2,
+ l("Poisoned Julia"), 3,
+ l("Completed, Gado wins"), 4,
+ l("Completed, Julia wins (returned poison)"), 5,
+ l("Completed, Julia wins"), 6;
+
+ if (@menuret < 0)
+ {
+ return;
+ }
+
+ } while (1);
+}
diff --git a/npc/functions/quest-debug/018-General_Cookies.txt b/npc/functions/quest-debug/018-General_Cookies.txt
new file mode 100644
index 00000000..16c7bcd7
--- /dev/null
+++ b/npc/functions/quest-debug/018-General_Cookies.txt
@@ -0,0 +1,25 @@
+// Cookie quest debug
+// Author:
+// gumi
+
+function script QuestDebug18 {
+ do
+ {
+ clear;
+ setnpcdialogtitle l("Quest debug");
+ mes "General_Cookies";
+ mes "---";
+ mes l("Quest state: @@", getq(General_Cookies));
+ next;
+
+ GenericQuestDebug General_Cookies,
+ l("Does not have the quest"), 0,
+ l("Got a cookie"), 1;
+
+ if (@menuret < 0)
+ {
+ return;
+ }
+
+ } while (1);
+}
diff --git a/npc/functions/quest-debug/020-ArtisQuests_LazyBrother.txt b/npc/functions/quest-debug/020-ArtisQuests_LazyBrother.txt
new file mode 100644
index 00000000..11cf3a44
--- /dev/null
+++ b/npc/functions/quest-debug/020-ArtisQuests_LazyBrother.txt
@@ -0,0 +1,28 @@
+// Lazy brother quest debug
+// Author:
+// gumi
+
+function script QuestDebug20 {
+ do
+ {
+ clear;
+ setnpcdialogtitle l("Quest debug");
+ mes "ArtisQuests_LazyBrother";
+ mes "---";
+ mes l("Quest state: @@", getq(ArtisQuests_LazyBrother));
+ next;
+
+ GenericQuestDebug ArtisQuests_LazyBrother,
+ l("Does not have the quest"), 0,
+ l("Katja asked for help"), 1,
+ l("Found bobo, didn't tell Katja"), 2,
+ l("Told bobo to go home"), 3,
+ l("Katja gave reward"), 4;
+
+ if (@menuret < 0)
+ {
+ return;
+ }
+
+ } while (1);
+}
diff --git a/npc/functions/quest-debug/021-ArtisQuests_Urchin.txt b/npc/functions/quest-debug/021-ArtisQuests_Urchin.txt
new file mode 100644
index 00000000..486cae18
--- /dev/null
+++ b/npc/functions/quest-debug/021-ArtisQuests_Urchin.txt
@@ -0,0 +1,26 @@
+// Urchin quest debug
+// Author:
+// gumi
+
+function script QuestDebug21 {
+ do
+ {
+ clear;
+ setnpcdialogtitle l("Quest debug");
+ mes "ArtisQuests_Urchin";
+ mes "---";
+ mes l("Quest state: @@", getq(ArtisQuests_Urchin));
+ next;
+
+ GenericQuestDebug ArtisQuests_Urchin,
+ l("Does not have the quest"), 0,
+ l("Moon needs @@", l("Croc Claw")), 1,
+ l("Found @@, got exp", l("Croc Claw")), 2;
+
+ if (@menuret < 0)
+ {
+ return;
+ }
+
+ } while (1);
+}
diff --git a/npc/functions/quest-debug/022-ArtisQuests_CatchPiou.txt b/npc/functions/quest-debug/022-ArtisQuests_CatchPiou.txt
new file mode 100644
index 00000000..9d5afc1c
--- /dev/null
+++ b/npc/functions/quest-debug/022-ArtisQuests_CatchPiou.txt
@@ -0,0 +1,26 @@
+// Piou quest debug
+// Author:
+// gumi
+
+function script QuestDebug22 {
+ do
+ {
+ clear;
+ setnpcdialogtitle l("Quest debug");
+ mes "ArtisQuests_CatchPiou";
+ mes "---";
+ mes l("Quest state: @@", getq(ArtisQuests_CatchPiou));
+ next;
+
+ GenericQuestDebug ArtisQuests_CatchPiou,
+ l("Does not have the quest"), 0,
+ l("Salem asks to catch piou"), 1,
+ l("Returned piou to Salem"), 2;
+
+ if (@menuret < 0)
+ {
+ return;
+ }
+
+ } while (1);
+}
diff --git a/npc/functions/quest-debug/023-ArtisQuests_Fishman.txt b/npc/functions/quest-debug/023-ArtisQuests_Fishman.txt
new file mode 100644
index 00000000..e3b5ac2e
--- /dev/null
+++ b/npc/functions/quest-debug/023-ArtisQuests_Fishman.txt
@@ -0,0 +1,26 @@
+// Eugene quest debug
+// Author:
+// gumi
+
+function script QuestDebug23 {
+ do
+ {
+ clear;
+ setnpcdialogtitle l("Quest debug");
+ mes "ArtisQuests_Fishman";
+ mes "---";
+ mes l("Quest state: @@", getq(ArtisQuests_Fishman));
+ next;
+
+ GenericQuestDebug ArtisQuests_Fishman,
+ l("Does not have the quest"), 0,
+ l("Eugene needs tentacles"), 1,
+ l("Gave tentacles, got reward"), 2;
+
+ if (@menuret < 0)
+ {
+ return;
+ }
+
+ } while (1);
+}
diff --git a/npc/functions/quest-debug/024-ArtisQuests_QOnan.txt b/npc/functions/quest-debug/024-ArtisQuests_QOnan.txt
new file mode 100644
index 00000000..702ef9be
--- /dev/null
+++ b/npc/functions/quest-debug/024-ArtisQuests_QOnan.txt
@@ -0,0 +1,27 @@
+// Q'Onan quest debug
+// Author:
+// gumi
+
+function script QuestDebug24 {
+ do
+ {
+ clear;
+ setnpcdialogtitle l("Quest debug");
+ mes "ArtisQuests_QOnan";
+ mes "---";
+ mes l("Quest state: @@", getq(ArtisQuests_QOnan));
+ next;
+
+ GenericQuestDebug ArtisQuests_QOnan,
+ l("Does not have the quest"), 0,
+ l("Q'Onan asks to find chest"), 1,
+ l("Found the chest"), 2,
+ l("Gave to Q'Onan, got reward"), 3;
+
+ if (@menuret < 0)
+ {
+ return;
+ }
+
+ } while (1);
+}
diff --git a/npc/functions/quest-debug/026-General_Rumly.txt b/npc/functions/quest-debug/026-General_Rumly.txt
new file mode 100644
index 00000000..c533c27a
--- /dev/null
+++ b/npc/functions/quest-debug/026-General_Rumly.txt
@@ -0,0 +1,27 @@
+// Rumly quest debug
+// Author:
+// gumi
+
+function script QuestDebug26 {
+ do
+ {
+ clear;
+ setnpcdialogtitle l("Quest debug");
+ mes "General_Rumly";
+ mes "---";
+ mes l("Quest state: @@", getq(General_Rumly));
+ next;
+
+ GenericQuestDebug General_Rumly,
+ l("Does not have the quest"), 0,
+ l("Rumly needs your help"), 1,
+ l("Rumly wants @@", l("Plushroom")), 2,
+ l("Gave @@ to Rumly", l("Plushroom")), 3;
+
+ if (@menuret < 0)
+ {
+ return;
+ }
+
+ } while (1);
+}
diff --git a/npc/functions/quest-debug/027-ArtisQuests_Enora.txt b/npc/functions/quest-debug/027-ArtisQuests_Enora.txt
new file mode 100644
index 00000000..9121ceb3
--- /dev/null
+++ b/npc/functions/quest-debug/027-ArtisQuests_Enora.txt
@@ -0,0 +1,49 @@
+// Newbie quest debug
+// Author:
+// gumi
+
+function script QuestDebug27 {
+ do
+ {
+ clear();
+ setnpcdialogtitle(l("Quest debug"));
+ mes("ArtisQuests_Enora");
+ mes("---");
+ mes(l("Quest state: @@, @@",
+ getq(ArtisQuests_Enora),
+ getq2(ArtisQuests_Enora)));
+ next();
+
+ GenericQuestDebug(ArtisQuests_Enora,
+ l("Does not have the quest"), 0,
+ l("Enora asks to visit Chelios"), 1,
+ l("Chelios asks to visit Lloyd"), 2,
+ l("Lloyd gave package"), 3,
+ l("Chelios made sword"), 4,
+ l("Enora asks to visit Resa"), 5,
+ l("Resa gave package"), 6,
+ l("Enora asks to visit Q'Pid"), 7,
+ l("Lost the riddle"), 8,
+ l("Ivan gave package"), 9,
+ l("Enora asks to kill fluffies"), 10,
+ l("Completed, got reward"), 11);
+
+ switch (@menuret)
+ {
+ case 0:
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ case 9:
+ case 10: setq(ArtisQuests_Enora, getq(ArtisQuests_Enora), 0); break;
+ case 11: setq(ArtisQuests_Enora, getq(ArtisQuests_Enora), 10); break;
+ default: if (@menuret < 0) return;
+ }
+
+ } while (1);
+}
diff --git a/npc/functions/quest-debug/029-ArtisQuests_Fexil.txt b/npc/functions/quest-debug/029-ArtisQuests_Fexil.txt
new file mode 100644
index 00000000..9c0d7945
--- /dev/null
+++ b/npc/functions/quest-debug/029-ArtisQuests_Fexil.txt
@@ -0,0 +1,27 @@
+// Fexil quest debug
+// Author:
+// gumi
+
+function script QuestDebug29 {
+ do
+ {
+ clear;
+ setnpcdialogtitle l("Quest debug");
+ mes "ArtisQuests_Fexil";
+ mes "---";
+ mes l("Quest state: @@", getq(ArtisQuests_Fexil));
+ next;
+
+ GenericQuestDebug ArtisQuests_Fexil,
+ l("Does not have the quest"), 0,
+ l("Lloyd gave pass"), 1,
+ l("Fexil needs help"), 2,
+ l("Fexil wants to buy fur"), 3;
+
+ if (@menuret < 0)
+ {
+ return;
+ }
+
+ } while (1);
+}
diff --git a/npc/functions/quest-debug/030-ArtisQuests_Lloyd.txt b/npc/functions/quest-debug/030-ArtisQuests_Lloyd.txt
new file mode 100644
index 00000000..0afb019c
--- /dev/null
+++ b/npc/functions/quest-debug/030-ArtisQuests_Lloyd.txt
@@ -0,0 +1,25 @@
+// Lloyd quest debug
+// Author:
+// gumi
+
+function script QuestDebug30 {
+ do
+ {
+ clear;
+ setnpcdialogtitle l("Quest debug");
+ mes "ArtisQuests_Lloyd";
+ mes "---";
+ mes l("Quest state: @@", getq(ArtisQuests_Lloyd));
+ next;
+
+ GenericQuestDebug ArtisQuests_Lloyd,
+ l("Does not have the quest"), 0,
+ l("Registered"), 1;
+
+ if (@menuret < 0)
+ {
+ return;
+ }
+
+ } while (1);
+}
diff --git a/npc/functions/quest-debug/031-General_Janus.txt b/npc/functions/quest-debug/031-General_Janus.txt
new file mode 100644
index 00000000..f97b4fbe
--- /dev/null
+++ b/npc/functions/quest-debug/031-General_Janus.txt
@@ -0,0 +1,31 @@
+// Janus quest debug
+// Author:
+// gumi
+
+function script QuestDebug31 {
+ do
+ {
+ clear;
+ setnpcdialogtitle l("Quest debug");
+ mes "General_Janus";
+ mes "---";
+ mes l("Quest state: @@", getq(General_Janus));
+ next;
+
+ GenericQuestDebug General_Janus,
+ l("Does not have the quest"), 0,
+ l("Talked to Janus"), 1,
+ l("Can create party"), 2,
+ l("Can create guild"), 3;
+
+ switch (@menuret)
+ {
+ case 0:
+ case 1: skill NV_BASIC, min(6, getskilllv(NV_BASIC)), 0; break;
+ case 2:
+ case 3: skill NV_BASIC, max(7, getskilllv(NV_BASIC)), 0; break;
+ default: if (@menuret < 0) return;
+ }
+
+ } while (1);
+}
diff --git a/npc/functions/quest-debug/032-ArtisQuests_MonaDad.txt b/npc/functions/quest-debug/032-ArtisQuests_MonaDad.txt
new file mode 100644
index 00000000..bda45b90
--- /dev/null
+++ b/npc/functions/quest-debug/032-ArtisQuests_MonaDad.txt
@@ -0,0 +1,26 @@
+// Mona quest debug
+// Author:
+// gumi
+
+function script QuestDebug32 {
+ do
+ {
+ clear;
+ setnpcdialogtitle l("Quest debug");
+ mes "ArtisQuests_MonaDad";
+ mes "---";
+ mes l("Quest state: @@", getq(ArtisQuests_MonaDad));
+ next;
+
+ GenericQuestDebug ArtisQuests_MonaDad,
+ l("Does not have the quest"), 0,
+ l("Mona's dad is missing"), 1,
+ l("Mona's dad was rescued"), 3;
+
+ if (@menuret < 0)
+ {
+ return;
+ }
+
+ } while (1);
+}
diff --git a/npc/functions/quest-debug/033-Artis_Legion_Progress.txt b/npc/functions/quest-debug/033-Artis_Legion_Progress.txt
new file mode 100644
index 00000000..ff6f1d16
--- /dev/null
+++ b/npc/functions/quest-debug/033-Artis_Legion_Progress.txt
@@ -0,0 +1,31 @@
+// Mona quest debug
+// Authors:
+// gumi
+// monwarez
+
+function script QuestDebug33 {
+ do
+ {
+ clear;
+ setnpcdialogtitle l("Quest debug");
+ mes "Artis_Legion_Progress";
+ mes "---";
+ mes l("Quest state: @@", getq(Artis_Legion_Progress));
+ next;
+
+ GenericQuestDebug Artis_Legion_Progress,
+ l("Does not have the quest"), 0,
+ l("Sent to training"), 1,
+ l("Finished training"), 2,
+ l("Sent to battle"), 3,
+ l("Finished battle"), 4,
+ l("Sent to Q'Anon"), 5,
+ l("Indefinite Traning"), 6;
+
+ if (@menuret < 0)
+ {
+ return;
+ }
+
+ } while (1);
+}
diff --git a/npc/functions/quest-debug/034-ArtisQuests_TrainingLegion.txt b/npc/functions/quest-debug/034-ArtisQuests_TrainingLegion.txt
new file mode 100644
index 00000000..ba72fd07
--- /dev/null
+++ b/npc/functions/quest-debug/034-ArtisQuests_TrainingLegion.txt
@@ -0,0 +1,28 @@
+// Training Legion quest
+// Authors:
+// omatt
+
+function script QuestDebug34 {
+ do
+ {
+ clear;
+ setnpcdialogtitle l("Quest debug");
+ mes "ArtisQuests_TrainingLegion";
+ mes "---";
+ mes l("Quest state: @@", getq(ArtisQuests_TrainingLegion));
+ next;
+
+ GenericQuestDebug ArtisQuests_TrainingLegion,
+ l("Does not have the quest"), 0,
+ l("Finished sword training"), 1,
+ l("Finished bow training"), 2,
+ l("Both sword and bow training are finished"), 3,
+ l("Skill training finished, end of quest"), 4;
+
+ if (@menuret < 0)
+ {
+ return;
+ }
+
+ } while (1);
+}
diff --git a/npc/functions/quest-debug/035-ThiefQuests_Artis.txt b/npc/functions/quest-debug/035-ThiefQuests_Artis.txt
new file mode 100644
index 00000000..83f9fb9c
--- /dev/null
+++ b/npc/functions/quest-debug/035-ThiefQuests_Artis.txt
@@ -0,0 +1,38 @@
+// Artis Thieves quest debug
+// Author:
+// Jesusalva
+// Field 2: Internal use (gearwheels - odd: collected; even: used)
+// Field 3: Internal use (Archive where the file is)
+
+function script QuestDebug35 {
+ do
+ {
+ clear;
+ setnpcdialogtitle l("Quest debug");
+ mes "ThiefQuests_Artis";
+ mes "---";
+ mes l("Quest state: @@", getq(ThiefQuests_Artis));
+ .@q2=getq2(ThiefQuests_Artis);
+ mesf ("Gearwheels: %d (%s)", .@q2/2, (.@q2 % 2 ? "found" : "not found"));
+ mesf ("Henry's file: %d", getq3(ThiefQuests_Artis));
+ next;
+
+ GenericQuestDebug ThiefQuests_Artis,
+ ("Does not have the quest"), 0,
+ ("Accepted Townhall Quest"), 1,
+ ("Townhall arc complete"), 2,
+ ("Reward taken"), 3,
+ ("Accepted Legion Quest"), 4,
+ ("Legion arc comlete"), 5,
+ ("Reward taken (2)"), 6,
+ ("Nunia Quest Accepted"), 7,
+ ("Nunia arc comlete"), 8,
+ ("Reward taken (3)"), 9;
+
+ if (@menuret < 0)
+ {
+ return;
+ }
+
+ } while (1);
+}
diff --git a/npc/functions/quest-debug/050-HurnscaldQuests_Hinnak.txt b/npc/functions/quest-debug/050-HurnscaldQuests_Hinnak.txt
new file mode 100644
index 00000000..31183eb8
--- /dev/null
+++ b/npc/functions/quest-debug/050-HurnscaldQuests_Hinnak.txt
@@ -0,0 +1,32 @@
+// Hinnak quest debug
+// Author:
+// gumi
+
+function script QuestDebug50 {
+ do
+ {
+ clear;
+ setnpcdialogtitle l("Quest debug");
+ mes "HurnscaldQuests_Hinnak";
+ mes "---";
+ mes l("Quest state: @@, @@",
+ getq(HurnscaldQuests_Hinnak),
+ getq2(HurnscaldQuests_Hinnak));
+ next;
+
+ GenericQuestDebug HurnscaldQuests_Hinnak,
+ l("Cannot do the quest"), 0,
+ l("Can do the quest"), 1,
+ l("Hinnak asked for help"), 2,
+ l("Helped Hinnak"), 3;
+
+ switch (@menuret)
+ {
+ case 0:
+ case 1:
+ case 2: setq HurnscaldQuests_Hinnak, getq(HurnscaldQuests_Hinnak), 0; break;
+ default: if (@menuret < 0) return;
+ }
+
+ } while (1);
+}
diff --git a/npc/functions/quest-debug/051-HurnscaldQuests_Soup.txt b/npc/functions/quest-debug/051-HurnscaldQuests_Soup.txt
new file mode 100644
index 00000000..44694f06
--- /dev/null
+++ b/npc/functions/quest-debug/051-HurnscaldQuests_Soup.txt
@@ -0,0 +1,32 @@
+// Mikhail & Bernard quest debug
+// Author:
+// gumi
+
+function script QuestDebug51 {
+ do
+ {
+ clear;
+ setnpcdialogtitle l("Quest debug");
+ mes "HurnscaldQuests_Soup";
+ mes "---";
+ mes l("Quest state: @@",
+ getq(HurnscaldQuests_Soup));
+ next;
+
+ GenericQuestDebug HurnscaldQuests_Soup,
+ l("Cannot do the quest"), 0,
+ l("Can do the quest"), 1,
+ l("Bernard wants roasted maggot"), 2,
+ l("brought maggot"), 3,
+ l("Bernard wants maggot slime"), 4,
+ l("brought maggot slime"), 5,
+ l("Mikhail needs maggot slime"), 6,
+ l("brought maggot slime"), 7;
+
+ if (@menuret < 0)
+ {
+ return;
+ }
+
+ } while (1);
+}
diff --git a/npc/functions/quest-debug/052-HurnscaldQuests_Inspector.txt b/npc/functions/quest-debug/052-HurnscaldQuests_Inspector.txt
new file mode 100644
index 00000000..72d99242
--- /dev/null
+++ b/npc/functions/quest-debug/052-HurnscaldQuests_Inspector.txt
@@ -0,0 +1,41 @@
+// Inspector quest debug
+// Author:
+// gumi
+
+function script QuestDebug52 {
+ do
+ {
+ clear;
+ setnpcdialogtitle l("Quest debug");
+ mes "HurnscaldQuests_Inspector";
+ mes "---";
+ mes l("Quest state: @@",
+ getq(HurnscaldQuests_Inspector));
+ next;
+
+ GenericQuestDebug HurnscaldQuests_Inspector,
+ l("Cannot do the quest"), 0,
+ l("Can do the quest"), 1,
+ l("Talked to Inspector (1)"), 2,
+ l("Talked to Old Woman (1)"), 3,
+ l("Talked to Old Woman (2)"), 4,
+ l("Talked to Inspector (2)"), 5,
+ l("Talked to Troupe Leader (1)"), 6,
+ l("Talked to Inspector (3)"), 7,
+ l("Talked to Old Man"), 8,
+ l("Talked to Old Woman (3)"), 9,
+ l("Talked to Inspector (4)"), 10,
+ l("Talked to Old Woman (4)"), 11,
+ l("Talked to Malek"), 12,
+ l("Searched the bookcase"), 13,
+ l("Talked to Inspector (5)"), 14,
+ l("Talked to Troupe Leader (2)"), 15,
+ l("Talked to Inspector (6)"), 16;
+
+ if (@menuret < 0)
+ {
+ return;
+ }
+
+ } while (1);
+}
diff --git a/npc/functions/quest-debug/053-HurnscaldQuests_ForestBow.txt b/npc/functions/quest-debug/053-HurnscaldQuests_ForestBow.txt
new file mode 100644
index 00000000..2a0993e3
--- /dev/null
+++ b/npc/functions/quest-debug/053-HurnscaldQuests_ForestBow.txt
@@ -0,0 +1,42 @@
+// Jack Lumber quest debug
+// Author:
+// gumi
+
+function script QuestDebug53 {
+ do
+ {
+ clear;
+ setnpcdialogtitle l("Quest debug");
+ mes "HurnscaldQuests_ForestBow";
+ mes "---";
+ mes l("Quest state: @@", getq(HurnscaldQuests_ForestBow));
+ mes "---";
+ mes l("Related quests:");
+ mes "HurnscaldQuests_WoodenShield: " + getq(HurnscaldQuests_WoodenShield);
+ next;
+
+ GenericQuestDebug HurnscaldQuests_ForestBow,
+ menuimage("actions/manage", l("Debug Wooden Shield")), -1,
+ l("Cannot do the quest"), 0,
+ l("Can do the quest"), 1,
+ l("Alan wants to ask Jack"), 2,
+ l("Jack explained problem"), 3,
+ l("Alan asks to find wood"), 4,
+ l("Found perfect wood"), 5,
+ l("Got the bow"), 6;
+
+ switch (@menuret)
+ {
+ case -1: callfunc "QuestDebug40"; break;
+ case 0:
+ case 1:
+ case 2:
+ case 3:
+ case 4: setq(HurnscaldQuests_WoodenShield, 0); break;
+ case 5:
+ case 6: setq(HurnscaldQuests_WoodenShield, max(1, getq(HurnscaldQuests_WoodenShield))); break;
+ default: if (@menuret < 0) return;
+ }
+
+ } while (1);
+}
diff --git a/npc/functions/quest-debug/054-HurnscaldQuests_WoodenShield.txt b/npc/functions/quest-debug/054-HurnscaldQuests_WoodenShield.txt
new file mode 100644
index 00000000..0cb3ee9d
--- /dev/null
+++ b/npc/functions/quest-debug/054-HurnscaldQuests_WoodenShield.txt
@@ -0,0 +1,37 @@
+// Jack Lumber quest debug
+// Author:
+// gumi
+
+function script QuestDebug54 {
+ do
+ {
+ clear;
+ setnpcdialogtitle l("Quest debug");
+ mes "HurnscaldQuests_WoodenShield";
+ mes "---";
+ mes l("Quest state: @@", getq(HurnscaldQuests_WoodenShield));
+ mes "---";
+ mes l("Related quests:");
+ mes "HurnscaldQuests_ForestBow: " + getq(HurnscaldQuests_ForestBow);
+ next;
+
+ GenericQuestDebug HurnscaldQuests_WoodenShield,
+ menuimage("actions/manage", l("Debug Forest Bow")), -1,
+ l("Cannot do the quest"), 0,
+ l("Can do the quest"), 1,
+ l("Jack proposes shield"), 2,
+ l("Accepted the quest"), 3,
+ l("Got the shield"), 4;
+
+ switch (@menuret)
+ {
+ case -1: callfunc "QuestDebug39"; break;
+ case 1:
+ case 2:
+ case 3:
+ case 4: setq(HurnscaldQuests_ForestBow, max(5, getq(HurnscaldQuests_ForestBow))); break;
+ default: if (@menuret < 0) return;
+ }
+
+ } while (1);
+}
diff --git a/npc/functions/quest-debug/055-General_Cooking.txt b/npc/functions/quest-debug/055-General_Cooking.txt
new file mode 100644
index 00000000..ec725364
--- /dev/null
+++ b/npc/functions/quest-debug/055-General_Cooking.txt
@@ -0,0 +1,92 @@
+// Cooking quest debug
+// Author:
+// Jesusalva
+
+function script QuestDebug55 {
+ do
+ {
+ clear;
+ setnpcdialogtitle l("Quest debug");
+ mes "General_Cooking";
+ mes "---";
+ mes l("Quest state: @@", getq(General_Cooking));
+ mes l("Route choosen: %s", (getq2(General_Cooking) == VEGAN ? l("Vegan") : l("Carnivorous")));
+ mes l("Known Recipes: @@", array_entries(RECIPES));
+ next;
+
+ menuint
+ l("Return"), -1,
+ l("Reset Quest"), 0,
+ "[2] Completed Yannika's Quest", 2,
+ "[3] Accepted Yannika's Request for Seafood Plate", 3,
+ "[4] Delivered fish to Dimond's Inn", 4,
+ "[5] Accepted task to talk to Reid's Inn", 5,
+ "[6] Got request for Piou/Manana Sandwhich", 6,
+ "[7] Got sent to Candor Inn", 7,
+ "[8] Tiki asked for new idea", 8,
+ "[9] Tiki approved new idea", 9,
+ "[10] Reid's Chef gave you epic recipe", 10,
+ "[11] Sent to tulimshar bakery", 11,
+ "[12] Sent to bakery apprentice", 12,
+ "[13] Killed all rattos", 13,
+ "[14] Learned Donut recipe", 14,
+ "[15] Sent to search for Salad", 15,
+ "[16] Offered to bring stuff to Hocus", 16,
+ "[17] Received Hocus' Salad", 17,
+ "[18] Salad Delivered", 18,
+ "[19] Quest Completed", 19,
+ l("Get a Recipe Book"), -2,
+ l("Learn all recipes"), -3,
+ l("Reset all recipes"), -4;
+
+ switch (@menuret)
+ {
+ case -1:
+ break;
+ case -2:
+ getitem RecipeBook, 1;
+ break;
+ case -3:
+ RECIPES[CraftCarpSandwich]=true;
+ RECIPES[CraftMananaSandwich]=true;
+ RECIPES[CraftPioulegSandwich]=true;
+
+ RECIPES[CraftSailorStew]=true;
+ RECIPES[CraftSquirrelStew]=true;
+ RECIPES[CraftMoubooStew]=true;
+
+ RECIPES[CraftSeafoodPlate]=true;
+ RECIPES[CraftBarbecuePlate]=true;
+ RECIPES[CraftVeggiePlate]=true;
+
+ RECIPES[CraftDonut]=true;
+ RECIPES[CraftBlueberryCake]=true;
+ RECIPES[CraftCarrotCake]=true;
+ break;
+ case -4:
+ RECIPES[CraftCarpSandwich]=false;
+ RECIPES[CraftMananaSandwich]=false;
+ RECIPES[CraftPioulegSandwich]=false;
+
+ RECIPES[CraftSailorStew]=false;
+ RECIPES[CraftSquirrelStew]=false;
+ RECIPES[CraftMoubooStew]=false;
+
+ RECIPES[CraftSeafoodPlate]=false;
+ RECIPES[CraftBarbecuePlate]=false;
+ RECIPES[CraftVeggiePlate]=false;
+
+ RECIPES[CraftDonut]=false;
+ RECIPES[CraftBlueberryCake]=false;
+ RECIPES[CraftCarrotCake]=false;
+ break;
+ default:
+ setq1 General_Cooking, @menuret;
+ break;
+
+ }
+
+ return;
+
+ } while (@menu != 1);
+}
diff --git a/npc/functions/quest-debug/056-General_Brotherhood.txt b/npc/functions/quest-debug/056-General_Brotherhood.txt
new file mode 100644
index 00000000..07fe568f
--- /dev/null
+++ b/npc/functions/quest-debug/056-General_Brotherhood.txt
@@ -0,0 +1,27 @@
+// Mona quest debug
+// Authors:
+// gumi
+// monwarez
+// jesusalva
+
+function script QuestDebug56 {
+ do
+ {
+ clear;
+ setnpcdialogtitle l("Quest debug");
+ mes "General_Brotherhood";
+ mes "---";
+ mes l("Quest state: @@", getq(General_Brotherhood));
+ next;
+
+ GenericQuestDebug General_Brotherhood,
+ l("Does not have the quest"), 0,
+ l("Contacted by Sopiahalla"), 1;
+
+ if (@menuret < 0)
+ {
+ return;
+ }
+
+ } while (1);
+}
diff --git a/npc/functions/quest-debug/057-HurnscaldQuests_Kfahr.txt b/npc/functions/quest-debug/057-HurnscaldQuests_Kfahr.txt
new file mode 100644
index 00000000..4ee0b36e
--- /dev/null
+++ b/npc/functions/quest-debug/057-HurnscaldQuests_Kfahr.txt
@@ -0,0 +1,31 @@
+// Kfahr quest debug
+// Author:
+// Toams
+
+function script QuestDebug57 {
+ do
+ {
+ clear;
+ setnpcdialogtitle l("Quest debug");
+ mes "HurnscaldQuests_Kfahr";
+ mes "---";
+ mes l("Quest state: @@", getq(HurnscaldQuests_Kfahr));
+ //mes "---";
+ //mes l("Related quests:");
+ //mes "HurnscaldQuests_Setzer: " + getq(HurnscaldQuests_Setzer);
+ next;
+
+ GenericQuestDebug HurnscaldQuests_Kfahr,
+ menuimage("actions/manage", l("Debug Kfahr")), -1,
+ l("Didn't speak with Kfahr yet"), 0,
+ l("Already met Kfahr, No quests started"), 1,
+ l("Bone knife quest started"), 2,
+ l("Bone knife quest finished"), 3;
+
+ if (@menuret < 0)
+ {
+ return;
+ }
+
+ } while (1);
+}
diff --git a/npc/functions/quest-debug/058-ArgaesQuest_Galimatia.txt b/npc/functions/quest-debug/058-ArgaesQuest_Galimatia.txt
new file mode 100644
index 00000000..01a058e7
--- /dev/null
+++ b/npc/functions/quest-debug/058-ArgaesQuest_Galimatia.txt
@@ -0,0 +1,32 @@
+// Galimatia quest debug
+// Author:
+// Livio
+// Someone else I've forgot the name
+
+function script QuestDebug58 {
+ do
+ {
+ clear;
+ setnpcdialogtitle l("Quest debug");
+ mes "ArgaesQuest_Galimatia";
+ mes "---";
+ mes l("Quest state: @@", getq(ArgaesQuest_Galimatia));
+ next;
+
+ GenericQuestDebug ArgaesQuest_Galimatia,
+ l("[RESET QUEST] Does not have the quest"), 0,
+ l("PC have to pour chemicals on dirt"), 1,
+ l("Galimatia needs Fertility Potion ingredients"), 2,
+ l("PC have to pour Fertility Potion on the same spot"), 3,
+ l("Something grows or it should be"), 4,
+ l("Galimatia offers a reward"), 5,
+ l("Galimatia offers to exchange flower things"), 6;
+
+ if (@menuret < 0)
+ {
+ return;
+ }
+
+ } while (1);
+ return;
+} \ No newline at end of file
diff --git a/npc/functions/quest-debug/059-HurnscaldQuests_Rossy.txt b/npc/functions/quest-debug/059-HurnscaldQuests_Rossy.txt
new file mode 100644
index 00000000..85c2006b
--- /dev/null
+++ b/npc/functions/quest-debug/059-HurnscaldQuests_Rossy.txt
@@ -0,0 +1,43 @@
+// Rossy quest debug
+// Authors:
+// jesusalva
+
+function script QuestDebug59 {
+ do
+ {
+ clear;
+ setnpcdialogtitle l("Quest debug");
+ mes "HurnscaldQuests_Rossy";
+ mes "---";
+ mes l("Quest state: @@", getq(HurnscaldQuests_Rossy));
+ if (getq(HurnscaldQuests_Rossy) == 16)
+ mes l("Clauquer's Cave Mask: %d", getq2(HurnscaldQuests_Rossy));
+ next;
+
+ GenericQuestDebug HurnscaldQuests_Rossy,
+ l("Does not have the quest"), 0,
+ l("Requested to find Rossy"), 1,
+ l("Found Rossy"), 2,
+ l("Informed Olana about Rossy"), 3,
+ l("Requested to deliver the berries"), 4,
+ l("Berries delivered"), 5,
+ l("Requested to tell Rossy about exam"), 6,
+ l("Accepted to help Rossy"), 7,
+ l("Requested to deliver potion to David"), 8,
+ l("Delivered potion to David"), 9,
+ l("Inform Olana about success"), 10,
+ l("Bring roses to Rossy"), 11,
+ l("Talk to Olana about allergy"), 12,
+ l("Bring tulips to Rossy"), 13,
+ l("Say Olana is a good mother"), 14,
+ l("Get status on Juliet with Rossy"), 15,
+ l("Dispatched to Clauquer Cave"), 16,
+ l("Juliet has been rescued"), 17;
+
+ if (@menuret < 0)
+ {
+ return;
+ }
+
+ } while (1);
+}
diff --git a/npc/functions/quest-debug/061-General_Hal.txt b/npc/functions/quest-debug/061-General_Hal.txt
new file mode 100644
index 00000000..172108f4
--- /dev/null
+++ b/npc/functions/quest-debug/061-General_Hal.txt
@@ -0,0 +1,30 @@
+// Hal quest debug
+// Author:
+// Livio
+
+function script QuestDebug61 {
+ do
+ {
+ clear;
+ setnpcdialogtitle l("Quest debug");
+ mes "General_CptHal";
+ mes "---";
+ mes l("Quest state: @@", getq(General_CptHal));
+ next;
+
+ GenericQuestDebug General_CptHal,
+ l("[RESET QUEST] Does not have the quest"), 0,
+ l("Bothered to talk to Thamas"), 1,
+ l("Accepted to resupply Hal's Legion troop"), 2,
+ l("Looking for Sergeant Ryan"), 3,
+ l("Found Sergeant Ryan, looking for items"), 4,
+ l("Healed Sergeant Ryan"), 5;
+
+ if (@menuret < 0)
+ {
+ return;
+ }
+
+ } while (1);
+ return;
+} \ No newline at end of file
diff --git a/npc/functions/quest-debug/100-General_Narrator.txt b/npc/functions/quest-debug/100-General_Narrator.txt
new file mode 100644
index 00000000..a88f5d6a
--- /dev/null
+++ b/npc/functions/quest-debug/100-General_Narrator.txt
@@ -0,0 +1,27 @@
+// Narrator debug
+// Author:
+// gumi
+
+function script QuestDebug100 {
+ do
+ {
+ clear;
+ setnpcdialogtitle l("Quest debug");
+ mes "General_Narrator";
+ mes "---";
+ mes l("Quest state: @@", getq(General_Narrator));
+ next;
+
+ GenericQuestDebug General_Narrator,
+ l("Game introduction"), 0,
+ l("Arrived in Artis"), 1,
+ l("Arrived in Argaes"), 2,
+ l("Sent to Airlia"), 3;
+
+ if (@menuret < 0)
+ {
+ return;
+ }
+
+ } while (1);
+}
diff --git a/npc/functions/quest-debug/functions.txt b/npc/functions/quest-debug/functions.txt
new file mode 100644
index 00000000..e45cb427
--- /dev/null
+++ b/npc/functions/quest-debug/functions.txt
@@ -0,0 +1,109 @@
+// Evol functions.
+// Author:
+// gumi
+// Description:
+// generic quest debug functions
+
+
+
+// GenericQuestDebug
+// makes a generic quest debug menu for the given quest when the server
+// is in debug mode.
+
+function script GenericQuestDebug {
+ if (!debug && !is_admin())
+ {
+ select
+ menuimage("actions/back", l("Go back"));
+
+ @menuret = -0x7FFFFFFF;
+ return 1;
+ }
+
+ mes l("Choose desired quest state:");
+
+ deletearray .@vals;
+ .@menustr$ = menuimage("actions/abort", l("Do nothing")) + ":";
+ .@vals[0] = -0x7FFFFFFF;
+ .@cnt = 1;
+
+ for (.@f = 1; .@f < getargcount(); .@f += 2)
+ {
+ if (getarg(.@f) != "")
+ {
+ .@qv = getarg(.@f + 1);
+ .@s$ = menuimage("actions/" + (.@qv ? "edit" : "reset"), "[" + .@qv + "] " + getarg(.@f));
+ .@menustr$ += (.@qv < 0 ? getarg(.@f) : .@s$) + ":";
+ .@vals[.@cnt] = .@qv;
+ ++.@cnt;
+ }
+ }
+
+ if (is_admin())
+ {
+ .@menustr$ += menuimage("actions/nuke", l("Set state manually"));
+ .@vals[.@cnt] = -0x7FFFFFFE;
+ }
+
+
+ @menu = 255;
+ @menuret = -1;
+ select(.@menustr$);
+ if (@menu == 255)
+ return -1;
+
+ --@menu;
+ if (@menu < 0 || @menu >= getarraysize(.@vals))
+ return -1;
+
+ @menuret = .@vals[@menu];
+
+ switch (@menuret)
+ {
+ case -0x7FFFFFFE: input @menuret; setq getarg(0,0), @menuret;
+ case -0x7FFFFFFF: return 1;
+ }
+
+ if (@menuret >= 0)
+ {
+ setq getarg(0,0), @menuret;
+ }
+ return;
+}
+
+
+
+// selectd
+// like a normal select() but gives access to quest debug
+//
+// quest id can either be passed as first argument
+// or as the .quest_debug npc variable
+
+function script selectd {
+ .@menustr$ = rif(debug, menuaction(l("debug")));
+ .@count = getargcount();
+ .@f = 0;
+ .@questID = getvariableofnpc(.quest_debug, strnpcinfo(0));
+
+ if (.@count > 1 && !isstr(getarg(0)))
+ {
+ ++.@f;
+ .@questID = getarg(0);
+ }
+
+ for (; .@f < .@count; ++.@f)
+ {
+ .@menustr$ += ":" + getarg(.@f);
+ }
+
+ @menu = 255;
+ select .@menustr$;
+
+ switch (@menu)
+ {
+ case -1:
+ case 255: return -1;
+ default: @menu -= 1; return @menu; // FIXME: here I would have done `return --@menu;` but hercules prevents this
+ case 1: callfunc "QuestDebug" + .@questID; closeclientdialog; end;
+ }
+}
diff --git a/npc/functions/questgen.txt b/npc/functions/questgen.txt
new file mode 100644
index 00000000..285924cb
--- /dev/null
+++ b/npc/functions/questgen.txt
@@ -0,0 +1,38 @@
+// Evol functions.
+// Authors:
+// monwarez
+// Reid
+// Description:
+// Generate a random quest type and location.
+// Variables:
+// @quests : 2D array containing quest type and location availability
+// with @nb_type rows and @nb_location columns.
+// boolean value determine if the connection is possible between
+// the quest type and location selected.
+// @nb_difficulty : difficulty value from 0 to N-1.
+// Argument:
+// 0 : quest type
+// 1 : quest location
+// 2 : quest difficulty
+// Return : Tridimentional array value;
+
+function script questgen {
+
+ .@argc = getargcount();
+ @q_type = getarg(0, rand(@nb_type * 10) / 10);
+
+ do
+ {
+ @q_location = getarg(1, rand(@nb_location * 10) / 10);
+ .@cell = @q_type * @nb_location + @q_location;
+
+ if (!@quests[.@cell] && .@argc >= 2)
+ {
+ return false;
+ }
+ } while (!@quests[.@cell]);
+
+ @q_difficulty = getarg(2, rand(@nb_difficulty * 10) / 10);
+
+ return true;
+}
diff --git a/npc/functions/referral.txt b/npc/functions/referral.txt
new file mode 100644
index 00000000..824a415b
--- /dev/null
+++ b/npc/functions/referral.txt
@@ -0,0 +1,43 @@
+// The Mana World scripts.
+// Author:
+// Moubootaur Legends Team
+// Jesusalva
+// gumi
+// Description:
+// Referral System - rewards
+// Note: Does not support multi-levelup
+
+function script ReferralSendLvReward {
+ .@refVault = bitwise_get(getvaultvar(REFERRAL_PROG), 0x00FFFFFF, 0); // the unique identifier of the referrer (Vault/account ID) [24-bit integer]
+ .@status = bitwise_get(getvaultvar(REFERRAL_PROG), 0x7F000000, 24); // the last reward obtained [8-bit integer]
+
+ if (.@refVault < 1) {
+ // the player has not been referred
+ return;
+ }
+
+ .@refChar = "playerCache"::vault2char(.@refVault);
+
+ if (.@refChar < 1) {
+ // the referrer no longer exists: unassign it
+ setvaultvar(REFERRAL_PROG, false);
+ return;
+ }
+
+ setarray(.@rewardTiers[0], 25, 50, 75, 100);
+ .@tier = array_find(.@rewardTiers[0], BaseLevel);
+
+ if (.@tier < 0 || .@status > .@tier) {
+ // not a reward tier, or already claimed
+ return;
+ }
+
+ rodex_sendmail(.@refChar, "TMW Team",
+ sprintf("Referred player reached Lv %i!", BaseLevel),
+ sprintf("%s just reached level %i!\n"
+ "As they get stronger, more rewards will be sent to you!", strcharinfo(PC_NAME), BaseLevel),
+ 0, Acorn, 1);
+
+ bitwise_set(getvaultvar(REFERRAL_PROG), 0x7F000000, 24, .@tier);
+ return;
+}
diff --git a/npc/functions/refine.txt b/npc/functions/refine.txt
new file mode 100644
index 00000000..23df387c
--- /dev/null
+++ b/npc/functions/refine.txt
@@ -0,0 +1,66 @@
+// The Mana World Script
+// Author:
+// Jesusalva
+// Inspiration:
+// Pyndragon (LoF)
+// Scall (TMW-BR)
+// Saulc (ML)
+// Description:
+// Handles refinement
+
+// refineupdate( {killedrid} )
+function script refineupdate {
+ // Not armed? We do nothing
+ if (!getequipisequiped(EQI_HAND_R))
+ return;
+
+ // Set temporary variables
+ .@k=getarg(0, killedrid);
+ .@w=getequipid(EQI_HAND_R);
+ .@r=getequiprefinerycnt(EQI_HAND_R);
+
+
+ // Weapon cannot be refined
+ if (!getequipisenableref(EQI_HAND_R))
+ return;
+
+ // Register the weapon experience
+ weaponExp[.@w] = weaponExp[.@w] + getmonsterinfo(.@k, MOB_LV);
+
+ // Get exp needed to level up from global array
+ .@exp=$@REFEXP[.@r];
+
+ // Cannot level up
+ if (.@exp < 1)
+ return;
+
+ // Leveled up!
+ if (weaponExp[.@w] >= .@exp) {
+ weaponExp[.@w]-=.@exp;
+ weaponLvl[.@w] = weaponLvl[.@w] + 1;
+ successrefitem(EQI_HAND_R);
+ dispbottom l("Weapon level up!");
+ }
+ return;
+}
+
+// Resyncs weapon level
+function script refinesync {
+ // Not armed? We do nothing
+ if (!getequipisequiped(EQI_HAND_R))
+ return;
+
+ .@w=getequipid(EQI_HAND_R);
+ .@r=getequiprefinerycnt(EQI_HAND_R);
+ .@lv=weaponLvl[.@w];
+
+ if (.@r > .@lv) {
+ // Refine level overestimated
+ downrefitem(EQI_HAND_R, max(0, .@r - .@lv));
+ } else if (.@lv > .@r) {
+ // Refine level understimated
+ successrefitem(EQI_HAND_R, max(0, .@lv - .@r));
+ }
+ return;
+}
+
diff --git a/npc/functions/resetstatus.txt b/npc/functions/resetstatus.txt
new file mode 100644
index 00000000..d5d4fdda
--- /dev/null
+++ b/npc/functions/resetstatus.txt
@@ -0,0 +1,117 @@
+// Moubootaur Legends Script.
+// Authors:
+// Vasily_Makarov (original from Evol)
+// Jesusalva
+// Description:
+// Status Reset NPC utils
+
+// Reset status and return permanent bonuses
+// StatusResetReinvest( {script=True} )
+function script StatusResetReinvest {
+ /* XXX: Uncommment this for ML Permanent Boost Status Fruit System :XXX
+ // Compulsory check
+ if (getarg(0, true)) {
+ inventoryplace NPCEyes, 6;
+ } else if (!checkweight(NPCEyes, 6)) {
+ getitembound StatusResetPotion, 1, 4;
+ dispbottom l("You cannot carry the fruits.");
+ end; // Die
+ }
+
+ // Permanent boosts were now lost, return the fruits
+ if (STATUSUP_STR) {
+ getitembound StrengthFruit, STATUSUP_STR, 4;
+ STATUSUP_STR=0;
+ }
+ if (STATUSUP_AGI) {
+ getitembound AgilityFruit, STATUSUP_AGI, 4;
+ STATUSUP_AGI=0;
+ }
+ if (STATUSUP_VIT) {
+ getitembound VitalityFruit, STATUSUP_VIT, 4;
+ STATUSUP_VIT=0;
+ }
+ if (STATUSUP_INT) {
+ getitembound IntelligenceFruit, STATUSUP_INT, 4;
+ STATUSUP_INT=0;
+ }
+ if (STATUSUP_DEX) {
+ getitembound DexterityFruit, STATUSUP_DEX, 4;
+ STATUSUP_DEX=0;
+ }
+ if (STATUSUP_LUK) {
+ getitembound LuckFruit, STATUSUP_LUK, 4;
+ STATUSUP_LUK=0;
+ }
+ */
+ resetstatus();
+ return true;
+}
+
+// Return wasSP on success, 0 on failure
+// ConfirmReset( {price} )
+function script ConfirmStatusReset {
+ if (BaseLevel >= 15)
+ .@raw_price=(1000-BaseLevel*10+(BaseLevel*18));
+ else if (BaseLevel >= 10)
+ .@raw_price=(BaseLevel*210-(10*210))/(BaseLevel/10);
+ else
+ .@raw_price=1;
+
+ if (getarg(0,-1) >= 0)
+ .@raw_price=getarg(0,-1);
+
+ //mesc l("WARNING: Permanent boosts will return to their fruit form."), 1;
+ mesc l("WARNING: Status resets cannot be reverted!"), 1;
+
+ switch (select(l("Yes, I am sure. Please reset my status!"),
+ l("I need to think about it..."),
+ l("I won't need it, thank you.")))
+ {
+ case 1:
+ speech S_FIRST_BLANK_LINE | S_LAST_NEXT,
+ l("Let me just have a quick look at you. Hm... I will need %d GP to reset your stats.", .@raw_price);
+
+ select
+ rif(Zeny >= .@raw_price, l("Here, take as much as you need, I have plenty!")),
+ rif(Zeny > 0 && Zeny < .@raw_price, l("I don't have enough money...")),
+ rif(Zeny == 0, l("Oh no, I don't have any money on me right now.")),
+ l("I have to go, sorry.");
+
+ if (@menu > 1) {
+ return 0;
+ }
+
+ speech S_FIRST_BLANK_LINE | S_LAST_NEXT,
+ l("Thank you."),
+ l("Now stand still... It should not take much time...");
+
+ // Reset status have an inventorycheck, so we charge later.
+ .@wasSP = StatusPoint;
+ StatusResetReinvest();
+
+ // Nothing to do: Do not charge (eg. you just got the fruits back)
+ if (StatusPoint == .@wasSP) {
+ speech S_LAST_NEXT,
+ l("It seems that you have no status points to reset!"),
+ l("Come back when you will really need me.");
+ } else {
+ Zeny-=.@raw_price;
+ speech S_LAST_NEXT,
+ l("Let's see... @@ of your status points have just been reset!", StatusPoint - .@wasSP),
+ l("Spend it wisely this time."),
+ l("But you are welcome to reset your stats again! I need the money.");
+ }
+ return .@wasSP;
+
+ case 2:
+ return 0;
+ case 3:
+ return 0;
+ }
+ //Exception("Unknown Error: ConfirmStatusReset() failed");
+ consolemes(CONSOLEMES_ERROR, "Unknown Error: ConfirmStatusReset() failed");
+ return 0;
+
+}
+
diff --git a/npc/functions/riddle.txt b/npc/functions/riddle.txt
new file mode 100644
index 00000000..fb503e3b
--- /dev/null
+++ b/npc/functions/riddle.txt
@@ -0,0 +1,74 @@
+// Evol functions.
+// Author:
+// Reid
+// Description:
+// Riddle enigma validator
+//
+// Arguments
+// 0 PC answer
+// 1 English correct answer
+// 2 Translated correct answer
+
+// TODO: levenshtein(), similar_text(), and maybe even soundex()
+
+function script riddlevalidation {
+ .@answer$ = strtolower(getarg(0));
+ .@good$ = strtolower(getarg(1));
+ .@good_translated$ = strtolower(getarg(2));
+
+ .@size_answer = getstrlen(.@answer$);
+ .@size_good = getstrlen(.@good$);
+ .@size_good_translated = getstrlen(.@good_translated$);
+
+ .@max = max(.@size_answer, .@size_good_translated, .@size_good);
+
+ // Input is too long.
+ if (.@max > 20)
+ {
+ return false;
+ }
+
+ .@size_good *= 70;
+ .@size_good_translated *= 70;
+
+ for (.@i = 0; .@i < .@max; .@i++)
+ {
+ .@correct = 0;
+ .@correct_translated = 0;
+
+ for (.@k = .@k_translated = .@j = .@i; .@j < .@max; .@j++)
+ {
+ if (charat(.@answer$, .@j) == charat(.@good$, .@k))
+ {
+ .@correct++;
+ .@k++;
+ }
+ else
+ {
+ .@correct--;
+ }
+
+ if (charat(.@answer$, .@j) ==
+ charat(.@good_translated$, .@k_translated))
+ {
+ .@correct_translated++;
+ .@k_translated++;
+ }
+ else
+ {
+ .@correct_translated--;
+ }
+ }
+ // if 70% of the word is correct
+ .@correct *= 100;
+ .@correct_translated *= 100;
+
+ if (.@correct >= .@size_good ||
+ .@correct_translated >= .@size_good_translated)
+ {
+ return true;
+ }
+ }
+
+ return false;
+}
diff --git a/npc/functions/sailordialogue.txt b/npc/functions/sailordialogue.txt
new file mode 100644
index 00000000..281246f7
--- /dev/null
+++ b/npc/functions/sailordialogue.txt
@@ -0,0 +1,53 @@
+// Evol functions.
+// Authors:
+// Qwerty Dragon
+// Reid
+// Description:
+// Random sailor dialogues between two categories of NPCs.
+
+function script sailorfood {
+ mesn;
+
+ .@q = rand(0, 400) / 100;
+ if (.@q == 0) goto L_RandomA;
+ if (.@q == 1) goto L_RandomB;
+ if (.@q > 1) goto L_RandomC;
+
+L_RandomA:
+ mesq l("Hey.");
+ next;
+ mesq l("What did Gugli say about the box? Was it ok?");
+ next;
+
+ menu
+ l("It's ok."), L_Fine,
+ l("He needs more food."), -;
+
+ mes "";
+ mesn;
+ mesq l("Oh really? I'll put more food in the next box then.");
+
+ close;
+
+L_Fine:
+ mes "";
+ mesn;
+ mesq l("It's alright! Just one more box and it'll be ok.");
+
+ close;
+
+L_RandomB:
+ mesq l("Thanks for the help!");
+ next;
+ mesq l("These boxes are way too heavy to be lifted by only one person, all the way onto the ship.");
+
+ close;
+
+L_RandomC:
+ mesq l("I think I'll be done soon, since I almost have a box full of @@s!", getitemlink(CrocClaw));
+ next;
+ mesq l("And you? How's it going on your side?");
+ next;
+
+ return;
+}
diff --git a/npc/functions/sailortalk.txt b/npc/functions/sailortalk.txt
new file mode 100644
index 00000000..73145768
--- /dev/null
+++ b/npc/functions/sailortalk.txt
@@ -0,0 +1,37 @@
+// Evol functions.
+// Author:
+// Reid
+// Description:
+// Tell a random sentence.
+// Variables:
+// .@rand = Random number between the number of sentence choice.
+
+function script sailortalk {
+
+ .@rand = rand(8);
+ if (.@rand == 0) goodbye;
+ if (.@rand == 1)
+ {
+ speech(
+ l("These purple mushrooms are called @@s. There are plenty of 'em on this island!", getitemlink(Plushroom)),
+ l("It's a kind of mushroom that tastes like a marshmallow and looks like a plush! @@, get it?", getitemlink(Plushroom)),
+ l("These funny fungi are mushrooming all around this island. Just pick some @@s and have a try.", getitemlink(Plushroom)));
+ close;
+ }
+ if (.@rand == 2) npctalkonce(l("Good to hear from you!"));
+ if (.@rand == 3) npctalkonce(l("So finally someone has came to visit me?"));
+ if (.@rand == 4)
+ {
+ speech(
+ l("A sunny and hot day,"),
+ l("a quiet place,"),
+ l("a ground!"),
+ l("What else do you need?"));
+ close;
+ }
+ if (.@rand == 5) npctalkonce(l("A-hoy matey!"));
+ if (.@rand == 6) npctalkonce(l("We are glad captain Nard has let you join the crew!"));
+ if (.@rand == 7) npctalkonce(l("Howdy?"));
+
+ end;
+}
diff --git a/npc/functions/savepoint.txt b/npc/functions/savepoint.txt
new file mode 100644
index 00000000..46ef73e8
--- /dev/null
+++ b/npc/functions/savepoint.txt
@@ -0,0 +1,57 @@
+// Evol functions.
+// Authors:
+// gumi
+// Reid
+// Description:
+// Adds a new save point location.
+// Usage:
+// savepointparticle;
+// savepointparticle NO_INN;
+// savepointparticle map, x, y, NO_INN;
+// Description:
+// Save location with arguments:
+// getarg(0) map name,
+// getarg(1) x's value,
+// getarg(2) y's value,
+// getarg(3) INN flag.
+
+function script savepointparticle {
+
+ if (gettimetick(2) - @lastSave < 5)
+ return;
+
+ .@mapname$ = getarg(0, "");
+ .@mapx = getarg(1, -1);
+ .@mapy = getarg(2, -1);
+ .@i = 3;
+
+ if (.@mapy < 1)
+ {
+ .@npc$ = strnpcinfo(0);
+ .@mapname$ = getvariableofnpc(.map$, .@npc$);
+ .@mapx = getvariableofnpc(.x, .@npc$);
+ .@mapy = getvariableofnpc(.y, .@npc$);
+ .@i = 0;
+ }
+
+ // If this will override your current inn, a confirmation is required.
+ // Unless you already don't have a booked place, then, confirmation is never shown.
+ if (getarg(.@i, NO_INN) != INN_REGISTER && INN_REGISTER != NO_INN)
+ {
+ mesc l("Do you want to use this place as save point?");
+ mesc b(l("Warning: ")) + l("Previous Inn reservation will be lost!");
+
+ if (askyesno() != ASK_YES)
+ close;
+
+ INN_REGISTER = NO_INN;
+ }
+
+ message strcharinfo(0), l("Your position has been saved.");
+
+ savepoint .@mapname$, .@mapx, .@mapy;
+ specialeffect(4, SELF, getcharid(3));
+ @lastSave = gettimetick(2);
+
+ return;
+}
diff --git a/npc/functions/scoreboards.txt b/npc/functions/scoreboards.txt
new file mode 100644
index 00000000..616ea54a
--- /dev/null
+++ b/npc/functions/scoreboards.txt
@@ -0,0 +1,291 @@
+// Moubootaur Legends Script
+// Author:
+// Jesusalva
+// Description:
+// Leaderboards
+
+// Scoreboard functions
+function script HallOfGuild {
+ mes "";
+ mes l("##BHall Of Guild Level: TOP5##b");
+ mesf("1. %s (%d)", $@hoguild_name$[0], $@hoguild_value[0]);
+ mesf("2. %s (%d)", $@hoguild_name$[1], $@hoguild_value[1]);
+ mesf("3. %s (%d)", $@hoguild_name$[2], $@hoguild_value[2]);
+ mesf("4. %s (%d)", $@hoguild_name$[3], $@hoguild_value[3]);
+ mesf("5. %s (%d)", $@hoguild_name$[4], $@hoguild_value[4]);
+ return;
+}
+
+function script HallOfFortune {
+ mes "";
+ mes l("##BHall Of Fortune: TOP15##b");
+ mesf("1. %s (%s E)", $@hofortune_name$[0],format_number($@hofortune_value[0]));
+ mesf("2. %s (%s E)", $@hofortune_name$[1],format_number($@hofortune_value[1]));
+ mesf("3. %s (%s E)", $@hofortune_name$[2],format_number($@hofortune_value[2]));
+ mesf("4. %s (%s E)", $@hofortune_name$[3],format_number($@hofortune_value[3]));
+ mesf("5. %s (%s E)", $@hofortune_name$[4],format_number($@hofortune_value[4]));
+ mesf("6. %s (%s E)", $@hofortune_name$[5],format_number($@hofortune_value[5]));
+ mesf("7. %s (%s E)", $@hofortune_name$[6],format_number($@hofortune_value[6]));
+ mesf("8. %s (%s E)", $@hofortune_name$[7],format_number($@hofortune_value[7]));
+ mesf("9. %s (%s E)", $@hofortune_name$[8],format_number($@hofortune_value[8]));
+ mesf("10. %s (%s E)", $@hofortune_name$[9],format_number($@hofortune_value[9]));
+ mesf("11. %s (%s E)", $@hofortune_name$[10],format_number($@hofortune_value[10]));
+ mesf("12. %s (%s E)", $@hofortune_name$[11],format_number($@hofortune_value[11]));
+ mesf("13. %s (%s E)", $@hofortune_name$[12],format_number($@hofortune_value[12]));
+ mesf("14. %s (%s E)", $@hofortune_name$[13],format_number($@hofortune_value[13]));
+ mesf("15. %s (%s E)", $@hofortune_name$[14],format_number($@hofortune_value[14]));
+ return;
+}
+
+function script HallOfLevel {
+ mes "";
+ mes l("##BHall Of Level: TOP15##b");
+ mesf("1. %s (%d)", $@hoblvl_name$[0], $@hoblvl_value[0]);
+ mesf("2. %s (%d)", $@hoblvl_name$[1], $@hoblvl_value[1]);
+ mesf("3. %s (%d)", $@hoblvl_name$[2], $@hoblvl_value[2]);
+ mesf("4. %s (%d)", $@hoblvl_name$[3], $@hoblvl_value[3]);
+ mesf("5. %s (%d)", $@hoblvl_name$[4], $@hoblvl_value[4]);
+ mesf("6. %s (%d)", $@hoblvl_name$[5], $@hoblvl_value[5]);
+ mesf("7. %s (%d)", $@hoblvl_name$[6], $@hoblvl_value[6]);
+ mesf("8. %s (%d)", $@hoblvl_name$[7], $@hoblvl_value[7]);
+ mesf("9. %s (%d)", $@hoblvl_name$[8], $@hoblvl_value[8]);
+ mesf("10. %s (%d)", $@hoblvl_name$[9], $@hoblvl_value[9]);
+ mesf("11. %s (%d)", $@hoblvl_name$[10], $@hoblvl_value[10]);
+ mesf("12. %s (%d)", $@hoblvl_name$[11], $@hoblvl_value[11]);
+ mesf("13. %s (%d)", $@hoblvl_name$[12], $@hoblvl_value[12]);
+ mesf("14. %s (%d)", $@hoblvl_name$[13], $@hoblvl_value[13]);
+ mesf("15. %s (%d)", $@hoblvl_name$[14], $@hoblvl_value[14]);
+ return;
+}
+
+function script HallOfJob {
+ mes "";
+ mes l("##BHall Of Job Level: TOP15##b");
+ mesf("1. %s (%d)", $@hojlvl_name$[0], $@hojlvl_value[0]);
+ mesf("2. %s (%d)", $@hojlvl_name$[1], $@hojlvl_value[1]);
+ mesf("3. %s (%d)", $@hojlvl_name$[2], $@hojlvl_value[2]);
+ mesf("4. %s (%d)", $@hojlvl_name$[3], $@hojlvl_value[3]);
+ mesf("5. %s (%d)", $@hojlvl_name$[4], $@hojlvl_value[4]);
+ mesf("6. %s (%d)", $@hojlvl_name$[5], $@hojlvl_value[5]);
+ mesf("7. %s (%d)", $@hojlvl_name$[6], $@hojlvl_value[6]);
+ mesf("8. %s (%d)", $@hojlvl_name$[7], $@hojlvl_value[7]);
+ mesf("9. %s (%d)", $@hojlvl_name$[8], $@hojlvl_value[8]);
+ mesf("10. %s (%d)", $@hojlvl_name$[9], $@hojlvl_value[9]);
+ mesf("11. %s (%d)", $@hojlvl_name$[10], $@hojlvl_value[10]);
+ mesf("12. %s (%d)", $@hojlvl_name$[11], $@hojlvl_value[11]);
+ mesf("13. %s (%d)", $@hojlvl_name$[12], $@hojlvl_value[12]);
+ mesf("14. %s (%d)", $@hojlvl_name$[13], $@hojlvl_value[13]);
+ mesf("15. %s (%d)", $@hojlvl_name$[14], $@hojlvl_value[14]);
+ return;
+}
+
+function script HallOfAcorns {
+ mes "";
+ mes l("##BHall Of Acorns: TOP15##b");
+ mesc l("Only %s in storage will be counted.", getitemlink(Acorn));
+ mesf("1. %s (%d)", $@hoa_name$[0], $@hoa_value[0]);
+ mesf("2. %s (%d)", $@hoa_name$[1], $@hoa_value[1]);
+ mesf("3. %s (%d)", $@hoa_name$[2], $@hoa_value[2]);
+ mesf("4. %s (%d)", $@hoa_name$[3], $@hoa_value[3]);
+ mesf("5. %s (%d)", $@hoa_name$[4], $@hoa_value[4]);
+ mesf("6. %s (%d)", $@hoa_name$[5], $@hoa_value[5]);
+ mesf("7. %s (%d)", $@hoa_name$[6], $@hoa_value[6]);
+ mesf("8. %s (%d)", $@hoa_name$[7], $@hoa_value[7]);
+ mesf("9. %s (%d)", $@hoa_name$[8], $@hoa_value[8]);
+ mesf("10. %s (%d)", $@hoa_name$[9], $@hoa_value[9]);
+ mesf("11. %s (%d)", $@hoa_name$[10], $@hoa_value[10]);
+ mesf("12. %s (%d)", $@hoa_name$[11], $@hoa_value[11]);
+ mesf("13. %s (%d)", $@hoa_name$[12], $@hoa_value[12]);
+ mesf("14. %s (%d)", $@hoa_name$[13], $@hoa_value[13]);
+ mesf("15. %s (%d)", $@hoa_name$[14], $@hoa_value[14]);
+ return;
+}
+
+function script HallOfLethality {
+ mes "";
+ mes l("##BHall Of Lethality: TOP15##b");
+ mesc l("Special monsters are not counted.");
+ mesf("1. %s (%d)", $@hol_name$[0], $@hol_value[0]);
+ mesf("2. %s (%d)", $@hol_name$[1], $@hol_value[1]);
+ mesf("3. %s (%d)", $@hol_name$[2], $@hol_value[2]);
+ mesf("4. %s (%d)", $@hol_name$[3], $@hol_value[3]);
+ mesf("5. %s (%d)", $@hol_name$[4], $@hol_value[4]);
+ mesf("6. %s (%d)", $@hol_name$[5], $@hol_value[5]);
+ mesf("7. %s (%d)", $@hol_name$[6], $@hol_value[6]);
+ mesf("8. %s (%d)", $@hol_name$[7], $@hol_value[7]);
+ mesf("9. %s (%d)", $@hol_name$[8], $@hol_value[8]);
+ mesf("10. %s (%d)", $@hol_name$[9], $@hol_value[9]);
+ mesf("11. %s (%d)", $@hol_name$[10], $@hol_value[10]);
+ mesf("12. %s (%d)", $@hol_name$[11], $@hol_value[11]);
+ mesf("13. %s (%d)", $@hol_name$[12], $@hol_value[12]);
+ mesf("14. %s (%d)", $@hol_name$[13], $@hol_value[13]);
+ mesf("15. %s (%d)", $@hol_name$[14], $@hol_value[14]);
+ return;
+}
+
+function script HallOfATL {
+ mes "";
+ mes l("##BHall Of Artis Legion Training Arena: TOP10##b");
+ mesf("1. %s (%d)", $@atl_name$[0], $@atl_value[0]);
+ mesf("2. %s (%d)", $@atl_name$[1], $@atl_value[1]);
+ mesf("3. %s (%d)", $@atl_name$[2], $@atl_value[2]);
+ mesf("4. %s (%d)", $@atl_name$[3], $@atl_value[3]);
+ mesf("5. %s (%d)", $@atl_name$[4], $@atl_value[4]);
+ mesf("6. %s (%d)", $@atl_name$[5], $@atl_value[5]);
+ mesf("7. %s (%d)", $@atl_name$[6], $@atl_value[6]);
+ mesf("8. %s (%d)", $@atl_name$[7], $@atl_value[7]);
+ mesf("9. %s (%d)", $@atl_name$[8], $@atl_value[8]);
+ mesf("10. %s (%d)", $@atl_name$[9], $@atl_value[9]);
+ return;
+}
+
+// Hall of AFK
+function script HallOfAFK {
+ mes "";
+ mes l("##BHall Of AFK: TOP 10##b");
+ mesf("1. %s (%dh%02dm)", $@afk_name$[0], $@afk_value[0]/1200, $@afk_value[0]%1200/60*3);
+ mesf("2. %s (%dh%02dm)", $@afk_name$[1], $@afk_value[1]/1200, $@afk_value[1]%1200/60*3);
+ mesf("3. %s (%dh%02dm)", $@afk_name$[2], $@afk_value[2]/1200, $@afk_value[2]%1200/60*3);
+ mesf("4. %s (%dh%02dm)", $@afk_name$[3], $@afk_value[3]/1200, $@afk_value[3]%1200/60*3);
+ mesf("5. %s (%dh%02dm)", $@afk_name$[4], $@afk_value[4]/1200, $@afk_value[4]%1200/60*3);
+ mesf("6. %s (%dh%02dm)", $@afk_name$[5], $@afk_value[5]/1200, $@afk_value[5]%1200/60*3);
+ mesf("7. %s (%dh%02dm)", $@afk_name$[6], $@afk_value[6]/1200, $@afk_value[6]%1200/60*3);
+ mesf("8. %s (%dh%02dm)", $@afk_name$[7], $@afk_value[7]/1200, $@afk_value[7]%1200/60*3);
+ mesf("9. %s (%dh%02dm)", $@afk_name$[8], $@afk_value[8]/1200, $@afk_value[8]%1200/60*3);
+ mesf("10. %s (%dh%02dm)", $@afk_name$[9], $@afk_value[9]/1200, $@afk_value[9]%1200/60*3);
+ return;
+}
+
+// HallOfGame()
+function script HallOfGame {
+ if ($MOST_HEROIC$)
+ mes l("World hero: %s", $MOST_HEROIC$);
+
+ if ($TREE_PLANTED)
+ mes l("Planted Trees: %s", format_number($TREE_PLANTED)); // FIXME
+
+ mes l("Players Killed in PvP: %s", format_number($PLAYERS_KILLED));
+ mes l("Monsters Killed in PvE: %s", format_number($MONSTERS_KILLED));
+ mes "";
+ .@s$=(season_direction() == WINTER ? l("Winter") : .@s$);
+ .@s$=(season_direction() == AUTUMN ? l("Autumn") : .@s$);
+ .@s$=(season_direction() == SUMMER ? l("Summer") : .@s$);
+ .@s$=(season_direction() == SPRING ? l("Spring") : .@s$);
+ mes l("Current Season: %s", .@s$);
+ // weather ; game time ; world story ; etc.
+ mes "";
+ mes l("Notable mentions and thanks for our [@@https://www.patreon.com/themanaworld|sponsors@@] for their continued support.");
+ mes "";
+ return;
+}
+
+
+// Main script handler for scoreboards
+- script @scoreboard NPC_HIDDEN,{
+ end;
+OnHour00:
+OnHour01:
+OnHour02:
+OnHour03:
+OnHour04:
+OnHour05:
+OnHour06:
+OnHour07:
+OnHour08:
+OnHour09:
+OnHour10:
+OnHour11:
+OnHour12:
+OnHour13:
+OnHour14:
+OnHour15:
+OnHour16:
+OnHour17:
+OnHour18:
+OnHour19:
+OnHour20:
+OnHour21:
+OnHour22:
+OnHour23:
+OnInit:
+ consolemes(CONSOLEMES_DEBUG, "Reloading scoreboards...");
+ .@nb = query_sql("select name, zeny from `char` ORDER BY zeny DESC LIMIT 15", $@hofortune_name$, $@hofortune_value);
+ .@nb = query_sql("select name, base_level from `char` ORDER BY base_level DESC LIMIT 15", $@hoblvl_name$, $@hoblvl_value);
+ .@nb = query_sql("select name, job_level from `char` ORDER BY job_level DESC LIMIT 15", $@hojlvl_name$, $@hojlvl_value);
+ .@nb = query_sql("select name, guild_lv from `guild` ORDER BY guild_lv DESC LIMIT 5", $@hoguild_name$, $@hoguild_value);
+ .@nb = query_sql("SELECT c.name, i.amount FROM `storage` AS i, `char` AS c WHERE i.nameid="+Acorn+" AND i.account_id=c.account_id ORDER BY i.amount DESC LIMIT 15", $@hoa_name$, $@hoa_value);
+ .@nb = query_sql("SELECT c.name, i.value FROM `char_reg_num_db` AS i, `char` AS c WHERE i.key='ATLRANK' AND i.char_id=c.char_id ORDER BY i.value DESC LIMIT 10", $@atl_name$, $@atl_value);
+ .@nb = query_sql("SELECT c.name, i.value FROM `char_reg_num_db` AS i, `char` AS c WHERE i.key='AFKING' AND i.char_id=c.char_id ORDER BY i.value DESC LIMIT 10", $@afk_name$, $@afk_value);
+ .@nb = query_sql("SELECT c.name, i.value FROM `char_reg_num_db` AS i, `char` AS c WHERE i.key='MONSTERS_KILLED' AND i.char_id=c.char_id ORDER BY i.value DESC LIMIT 15", $@hol_name$, $@hol_value);
+ consolemes(CONSOLEMES_DEBUG, "Scoreboards reloaded");
+ if (!$@SCOREBOARD_BIND) {
+ bindatcmd "scoreboard", "@scoreboard::OnCall", 0, 100, 0;
+ bindatcmd "scoreboards", "@scoreboard::OnCall", 0, 100, 0;
+ $@SCOREBOARD_BIND=true;
+ }
+ end;
+
+OnCall:
+ do {
+ clear;
+ //HallOfSponsor(true);
+ mes l("The Mana World - rEvolt");
+ mesc l("All scoreboards are updated hourly."), 1;
+ mes "";
+ select
+ l("Hall Of Fortune"),
+ l("Hall Of Base Level"),
+ l("Hall Of Job Level"),
+ l("Hall Of Guilds"),
+ l("Hall Of Lethality"),
+ l("Hall Of Acorns"),
+ l("Hall Of Artis Legion Training Arena"),
+ l("Hall Of AFK King"),
+ l("Game Statistics"),
+ l("Quit");
+ mes "";
+ switch (@menu) {
+ case 1:
+ HallOfFortune();
+ next;
+ break;
+ case 2:
+ HallOfLevel();
+ next;
+ break;
+ case 3:
+ HallOfJob();
+ next;
+ break;
+ case 4:
+ HallOfGuild();
+ next;
+ break;
+ case 5:
+ HallOfLethality();
+ next;
+ break;
+ case 6:
+ HallOfAcorns();
+ next;
+ break;
+ case 7:
+ HallOfATL();
+ next;
+ break;
+ case 8:
+ HallOfAFK();
+ next;
+ break;
+ case 9:
+ HallOfGame();
+ next;
+ break;
+ default:
+ close;
+ }
+ } while (true);
+ end;
+}
+
+
diff --git a/npc/functions/shops.txt b/npc/functions/shops.txt
new file mode 100644
index 00000000..8962997e
--- /dev/null
+++ b/npc/functions/shops.txt
@@ -0,0 +1,18 @@
+// Evol functions.
+// Author:
+// 4144
+// Jesusalva
+// Description:
+// Shops utility functions
+
+
+
+// restoreshopitem(<item nameid>, <amount>{, <price>})
+// adds back an item to the sell list if less than the specified amount
+// returns true
+
+function script restoreshopitem {
+ if (shopcount(getarg(0)) < getarg(1))
+ sellitem(getarg(0), getarg(2, -1), getarg(1));
+ return;
+}
diff --git a/npc/functions/skills.txt b/npc/functions/skills.txt
new file mode 100644
index 00000000..5273de34
--- /dev/null
+++ b/npc/functions/skills.txt
@@ -0,0 +1,20 @@
+// The Mana World scripts.
+// Author:
+// The Mana World Team
+// Description:
+// Controls script-based skills (which are rare);
+
+function script SkillInvoked {
+ // Record to database that you used the skill
+ skillInvoke[@skillId] = skillInvoke[@skillId] + 1;
+
+ // Switch though skills for additional effects
+ switch (@skillId) {
+ default:
+ // PS. I hate scripts.conf load order
+ callfunc("GetManaExp", @skillId, 1);
+ break;
+ }
+ return;
+}
+
diff --git a/npc/functions/soul-menhir.txt b/npc/functions/soul-menhir.txt
new file mode 100644
index 00000000..3d4344c9
--- /dev/null
+++ b/npc/functions/soul-menhir.txt
@@ -0,0 +1,75 @@
+// Evol scripts.
+// Author:
+// gumi
+// Jesusalva
+// Description:
+// place of power, mana refills faster when sitting nearby
+
+- script Soul Menhir NPC_HIDDEN,{
+ if (!@menhir_meditation_message)
+ {
+ dispbottom(l("You feel a strong magic aura. You want to sit near it and meditate."));
+ @menhir_meditation_message=1;
+ }
+ end;
+
+OnRefill:
+ @menhir_lock = false;
+ getmapxy(.@map$, .@x, .@y, UNITTYPE_PC);
+
+ if (.@map$ != .map$ || distance(.x, .y, .@x, .@y) > .refill_distance ||
+ !(issit()))
+ end;
+
+ heal(0, .refill_rate);
+ end;
+
+
+OnTimer500:
+ .@count = getunits(BL_PC, .@units[0], false, .map$, (.x - .refill_distance),
+ (.y - .refill_distance), (.x + .refill_distance), (.y + .refill_distance));
+
+ for (.@i = 0; .@i < .@count; ++.@i)
+ {
+ if (.@units[.@i] < 0) continue; // pre-check, just in case
+ deltimer(.name$ + "::OnRefill", .@units[.@i]);
+ if (gettimer(TIMER_COUNT, .@units[.@i], .name$ + "::OnRefill") > 0 ||
+ getvariableofpc(@menhir_lock, .@units[.@i])) {
+ continue;
+ }
+ set(getvariableofpc(@menhir_lock, .@units[.@i]), true);
+ addtimer(rand(.refill_timer), .name$ + "::OnRefill", .@units[.@i]);
+ }
+
+ initnpctimer();
+ end;
+
+OnInit:
+ // Placeholder menhir doesn't have to run
+ if (.name$ == "Soul Menhir")
+ end;
+
+ // "Next-Generation" parsing system
+ // Syntax: RATE_DISTANCE_TIMER
+ // Soul Menhir#town_rate_dist_timer
+ // example
+ // Soul Menhir#hurns_1_7_200
+ .@n$=strnpcinfo(0, "_0_0_0");
+ explode(.@ni$, .@n$, "_");
+ .refill_rate=atoi(.@ni$[1]);
+ .refill_distance=atoi(.@ni$[2]);
+ .refill_timer=atoi(.@ni$[3]);
+
+ // number of SP to give every refill
+ if (!.refill_rate)
+ .refill_rate = 1;
+ // max distance
+ if (.refill_distance < 0)
+ .refill_distance = 7;
+ // wait rand(X) ms before refill
+ if (.refill_timer < 1)
+ .refill_timer = 200;
+
+ initnpctimer();
+ end;
+}
diff --git a/npc/functions/spotlight.txt b/npc/functions/spotlight.txt
new file mode 100644
index 00000000..219d3a31
--- /dev/null
+++ b/npc/functions/spotlight.txt
@@ -0,0 +1,92 @@
+// Evol functions.
+// Author:
+// Jesusalva
+// Micksha
+// Description:
+// Update spotlight on caves
+// Variables:
+// 2 - the darkest mask
+// 4 - the average mask
+// 8 - the lightest mask
+
+// forced_update - if set to true, will ignore if the map is known as valid
+// (required for instance maps)
+// updateSpotlight ( {forced_update} )
+function script updateSpotlight {
+ // A small delay of 80 ms in case player is changing map
+ // It will be cast twice when switching caves. This sleep prevents obscure bugs.
+ sleep2(80);
+
+ getmapxy(.@m$, .@x, .@y, 0);
+
+ // Is your map valid (or is the check skipped)
+ if (!getarg(0,false))
+ {
+ if (strpos(getmapname(), "-3-") < 0)
+ return;
+ }
+
+ // Retrieve default map masks
+ .@ms=getmapmask(.@m$);
+
+ // Which equipments provide bonuses?
+ setarray .@b_head, CandleHelmet;
+ setarray .@b_weapon, ManaTorch, TrainingWand, Torch;
+
+ // Calc your lighting score (it should NOT start on zero)
+ .@score=1;
+ if (array_find(.@b_head, getequipid(EQI_HEAD_TOP)) >= 0)
+ .@score+=1;
+ if (array_find(.@b_weapon, getequipid(EQI_HAND_R)) >= 0)
+ .@score+=1;
+ // TODO: Lighting scrolls
+
+ //debugmes "Score: %d", .@score;
+ //debugmes "Equips: %d and %d", getequipid(EQI_HEAD_TOP), getequipid(EQI_HAND_R);
+ //debugmes "Headvalue: %d", .@b_head[0];
+ //debugmes "Weappnvalue: %d, %d, %d", .@b_weapon[0], .@b_weapon[1], .@b_weapon[2];
+ // Sanitize score
+ .@score=min(3, .@score);
+
+ // Calculate and send new map mask
+ .@ms=.@ms|(2**.@score);
+ sendmapmask(.@ms);
+ return;
+}
+
+// MAIN FUNCTION - DO NOT REMOVE
+// Every NPC will be duplicating this one
+001-3-0,0,0,0 script #SpotlightMaster NPC_HIDDEN,0,0,{
+OnTouch:
+ updateSpotlight(true);
+ end;
+}
+
+
+// I'm too lazy to do this in different files and in the right spot.....
+
+// npc/001-3-0/_warps.txt
+001-3-0,196,35,0 duplicate(#SpotlightMaster) #SPOT001-3-0_196_35 NPC_HIDDEN,2,2
+001-3-0,172,41,0 duplicate(#SpotlightMaster) #SPOT001-3-0_172_41 NPC_HIDDEN,2,2
+001-3-0,162,40,0 duplicate(#SpotlightMaster) #SPOT001-3-0_162_40 NPC_HIDDEN,2,2
+001-3-0,198,60,0 duplicate(#SpotlightMaster) #SPOT001-3-0_198_60 NPC_HIDDEN,2,2
+001-3-0,152,55,0 duplicate(#SpotlightMaster) #SPOT001-3-0_152_55 NPC_HIDDEN,2,2
+001-3-0,85,130,0 duplicate(#SpotlightMaster) #SPOT001-3-0_85_130 NPC_HIDDEN,2,2
+
+// npc/001-3-1/_warps.txt
+001-3-1,24,58,0 duplicate(#SpotlightMaster) #SPOT001-3-1_24_58 NPC_HIDDEN,2,2
+001-3-1,35,59,0 duplicate(#SpotlightMaster) #SPOT001-3-1_35_59 NPC_HIDDEN,2,2
+//001-3-1,30,19,0 duplicate(#SpotlightMaster) #SPOT001-3-1_30_19 NPC_HIDDEN,2,2
+
+// npc/001-3-2/_warps.txt
+001-3-2,30,117,0 duplicate(#SpotlightMaster) #SPOT001-3-2_30_117 NPC_HIDDEN,2,2
+
+// npc/008-3-0/_warps.txt
+008-3-0,130,113,0 duplicate(#SpotlightMaster) #SPOT008-3-0_130_113 NPC_HIDDEN,2,2
+
+// npc/008-3-1/_warps.txt
+008-3-1,34,34,0 duplicate(#SpotlightMaster) #SPOT008-3-1_34_34 NPC_HIDDEN,2,2
+
+// npc/008-3-2/_warps.txt
+008-3-2,175,18,0 duplicate(#SpotlightMaster) #SPOT008-3-2_175_18 NPC_HIDDEN,2,2
+
diff --git a/npc/functions/string.txt b/npc/functions/string.txt
new file mode 100644
index 00000000..ef2e4c2a
--- /dev/null
+++ b/npc/functions/string.txt
@@ -0,0 +1,196 @@
+// safe string manipulation functions
+// ** does not require PCRE
+
+
+// str(<int>)
+// returns whatever is passed, converted to string
+
+function script str {
+ return "" + getarg(0);
+}
+
+
+
+// startswith("<string>", "<search>")
+// returns true if <string> begins with <search>
+
+function script startswith {
+ return substr(getarg(0), 0, getstrlen(getarg(1)) - 1) == getarg(1);
+}
+
+
+
+// endswith("<string>", "<search>")
+// returns true if <string> ends with <search>
+
+function script endswith {
+ .@t = getstrlen(getarg(0)); // total length
+ .@n = getstrlen(getarg(1)); // substring length
+ return substr(getarg(0), .@t - .@n, .@t - 1) == getarg(1);
+}
+
+
+
+// capitalize("<string>")
+// returns <string> with its first letter capitalized
+
+function script capitalize {
+ return setchar(getarg(0), strtoupper(charat(getarg(0), 0)), 0);
+}
+
+
+
+// titlecase("<string>" {, "<delimiter>" {, <camel>}})
+// returns <string> with the first letter of each word capitalized
+// if <camel> is true, the string is joined in a camelCase fashion
+
+function script titlecase {
+ .@delimiter$ = getarg(1, " ");
+ .@c = getarg(2, 0);
+ explode(.@words$, getarg(0), .@delimiter$);
+
+ for (.@i = (.@c ? 1 : 0); .@i < 255; ++.@i)
+ {
+ if (.@words$[.@i] == "")
+ {
+ break;
+ }
+
+ .@words$[.@i] = setchar(.@words$[.@i], strtoupper(charat(.@words$[.@i], 0)), 0);
+ }
+
+ return implode(.@words$, (.@c ? "" : .@delimiter$));
+}
+
+
+
+// camelcase("<string" {, "<delimiter>"})
+
+function script camelcase {
+ return titlecase(getarg(0), getarg(1, " "), true);
+}
+
+
+
+// zfill("<string>" {, <width> {, "<padding>"}})
+// returns <string> padded to the left with <padding> up to width
+
+function script zfill {
+ .@str$ = getarg(0);
+ .@width = getarg(1, 8);
+ .@padding$ = getarg(2, "0");
+
+ for (.@s = getstrlen(.@str$); .@s < .@width; ++.@s)
+ {
+ .@str$ = .@padding$ + .@str$;
+ }
+
+ return .@str$;
+}
+
+
+
+// format_number(<integer> {, "<separator>"})
+// formats a number properly
+
+function script format_number {
+ .@number$ = str(getarg(0));
+ .@len = getstrlen(.@number$);
+ .@separator$ = getarg(1, ",");
+
+ if (getargcount() < 2 && playerattached()) {
+ // get from user language
+ switch (Lang) {
+ case LANG_FR: .@separator$ = " "; break; // French
+ //case LANG_BR: .@separator$ = "."; break; // Portuguese
+ default: .@separator$ = ","; // English (default)
+ }
+ }
+
+ for (.@i = .@len - 3; .@i > 0; .@i -= 3) {
+ .@number$ = insertchar(.@number$, .@separator$, .@i);
+ }
+
+ return .@number$;
+}
+
+
+
+// strip("<string>")
+// removes spaces at the start and end
+
+function script strip {
+ .@s$ = getarg(0);
+ if (.@s$ == "") {
+ return "";
+ }
+ .@start = 0;
+ .@end = getstrlen(.@s$) - 1;
+ for (.@i = .@start; .@i < .@end; .@i++)
+ {
+ if (charat(.@s$, .@i) != " ") {
+ break;
+ } else {
+ .@start++;
+ }
+ }
+ for (.@i = .@end; .@i >= .@start; .@i--)
+ {
+ if (charat(.@s$, .@i) != " ") {
+ break;
+ } else {
+ .@end--;
+ }
+ }
+ return substr(.@s$, .@start, .@end);
+}
+
+
+
+// reverse("<string>")
+// returns <string> reversed
+
+function script reverse {
+ .@str$ = getarg(0);
+ .@len = getstrlen(.@str$);
+
+ for (.@i = 0; .@i < (.@len / 2); ++.@i) {
+ .@tmp$ = charat(.@str$, .@i);
+ .@str$ = setchar(.@str$, charat(.@str$, (.@len - 1 - .@i)), .@i); // a <= b
+ .@str$ = setchar(.@str$, .@tmp$, (.@len - 1 - .@i)); // b <= a
+ }
+
+ return .@str$;
+}
+
+
+
+// repeat("<string>", <multiplier>)
+// repeats <string> many times and returns it
+
+function script repeat {
+ .@mul = getarg(1);
+
+ for (.@i = 0; .@i < .@mul; ++.@i) {
+ .@str$ += getarg(0);
+ }
+
+ return .@str$;
+}
+
+
+
+// shuffle("<string>")
+// returns <string> shuffled
+
+function script shuffle {
+ .@str$ = getarg(0);
+
+ for (.@len = getstrlen(.@str$); .@len > 0; --.@len) {
+ .@rnd = rand(.@len);
+ .@out$ += charat(.@str$, .@rnd);
+ .@str$ = delchar(.@str$, .@rnd);
+ }
+
+ return .@out$;
+}
diff --git a/npc/functions/time.txt b/npc/functions/time.txt
new file mode 100644
index 00000000..8376d6a0
--- /dev/null
+++ b/npc/functions/time.txt
@@ -0,0 +1,108 @@
+function script now {
+ return gettimetick(2);
+}
+
+
+function script time_from_ms {
+ return now() + (getarg(0) / 1000);
+}
+
+function script time_from_seconds {
+ return now() + getarg(0);
+}
+
+function script time_from_minutes {
+ return now() + (getarg(0) * 60);
+}
+
+function script time_from_hours {
+ return now() + (getarg(0) * 3600);
+}
+
+function script time_from_days {
+ return now() + (getarg(0) * 86400);
+}
+
+
+// FuzzyTime(<unix timestamp>{, <options>{, <precision>}})
+// gives time in a human-readable format
+//
+// <options> is bitmasked:
+// 1 do not show "ago" when in past
+// 2 do not show "in" when in the future (default)
+// 4 show "from now" instead of "in" when in the future
+//
+// <precision> is the number of units to show,
+// do not exceed 99 (default is 2)
+
+function script FuzzyTime {
+ .@future = getarg(0, now());
+ .@options = getarg(1, 2);
+ .@precision = getarg(2, 2);
+ .@diff = (.@future - now());
+
+ // check if in the past, or in the future
+ if (.@diff < 0) {
+ .@diff *= -1;
+ .@past = true;
+ }
+
+ .@diff = max(1, .@diff);
+
+ if (.@diff >= 31536000) {
+ .@years = (.@diff / 31536000);
+ .@diff = (++.@s == .@precision ? 0 : (.@diff % 31536000));
+ .@ret$ += sprintf("%d %s", .@years, (.@years > 1 ? "years" : "year"));
+ }
+
+ if (.@diff >= 86400) {
+ .@days = (.@diff / 86400);
+ .@diff = (++.@s == .@precision ? 0 : (.@diff % 86400));
+
+ if (.@s > 1) {
+ .@ret$ += (.@diff > 0 ? ", " : " and ");
+ }
+
+ .@ret$ += sprintf("%d %s", .@days, (.@days > 1 ? "days" : "day"));
+ }
+
+ if (.@diff >= 3600) {
+ .@hours = (.@diff / 3600);
+ .@diff = (++.@s == .@precision ? 0 : (.@diff % 3600));
+
+ if (.@s > 1) {
+ .@ret$ += (.@diff > 0 ? ", " : (.@s >= 3 ? ", " : " ") + "and ");
+ }
+
+ .@ret$ += sprintf("%d %s", .@hours, (.@hours > 1 ? "hours" : "hour"));
+ }
+
+ if (.@diff >= 60) {
+ .@minutes = (.@diff / 60);
+ .@diff = (++.@s == .@precision ? 0 : (.@diff % 60));
+
+ if (.@s > 1) {
+ .@ret$ += (.@diff > 0 ? ", " : (.@s >= 3 ? ", " : " ") + "and ");
+ }
+
+ .@ret$ += sprintf("%d %s", .@minutes, (.@minutes > 1 ? "minutes" : "minute"));
+ }
+
+ if (.@diff >= 1) {
+ if (++.@s > 1) {
+ .@ret$ += (.@s >= 3 ? ", " : " ") + "and ";
+ }
+
+ .@ret$ += sprintf("%d %s", .@diff, (.@diff > 1 ? "seconds" : "second"));
+ }
+
+ if (.@past && !(.@options & 1)) {
+ .@ret$ += " ago";
+ }
+
+ if (!(.@past) && !(.@options & 2)) {
+ .@ret$ = ((.@options & 4) ? sprintf("%s from now", .@ret$) : sprintf("in %s", .@ret$));
+ }
+
+ return .@ret$;
+}
diff --git a/npc/functions/timer.txt b/npc/functions/timer.txt
new file mode 100644
index 00000000..fbfec3fd
--- /dev/null
+++ b/npc/functions/timer.txt
@@ -0,0 +1,63 @@
+// addtimer2(<tick>, "<npc>::<event>")
+function script addtimer2 {
+ deltimer(getarg(1));
+ addtimer(getarg(0), getarg(1));
+ return;
+}
+
+// areatimer("<map>", <x1>, <y1>, <x2>, <y2>, <tick>, "<npc>::<event>")
+function script areatimer {
+ .@c = getunits(BL_PC, .@players, false, getarg(0), getarg(1), getarg(2), getarg(3), getarg(4));
+ for (.@i = 0; .@i < .@c; .@i++) {
+ addtimer(getarg(5), getarg(6), .@players[.@i]);
+ }
+ return .@i;
+}
+
+// areadeltimer("<map>", <x1>, <y1>, <x2>, <y2>, "<npc>::<event>")
+function script areadeltimer {
+ .@c = getunits(BL_PC, .@players, false, getarg(0), getarg(1), getarg(2), getarg(3), getarg(4));
+ for (.@i = 0; .@i < .@c; .@i++) {
+ deltimer(getarg(5), .@players[.@i]);
+ }
+ return .@i;
+}
+
+// areatimer2("<map>", <x1>, <y1>, <x2>, <y2>, <tick>, "<npc>::<event>")
+function script areatimer2 {
+ .@c = getunits(BL_PC, .@players, false, getarg(0), getarg(1), getarg(2), getarg(3), getarg(4));
+ for (.@i = 0; .@i < .@c; .@i++) {
+ deltimer(getarg(6), .@players[.@i]);
+ addtimer(getarg(5), getarg(6), .@players[.@i]);
+ }
+ return .@i;
+}
+
+
+// maptimer("<map>", <tick>, "<npc>::<event>")
+function script maptimer {
+ .@c = getunits(BL_PC, .@players, false, getarg(0));
+ for (.@i = 0; .@i < .@c; .@i++) {
+ addtimer(getarg(1), getarg(2), .@players[.@i]);
+ }
+ return .@i;
+}
+
+// maptimer2("<map>", <tick>, "<npc>::<event>")
+function script maptimer2 {
+ .@c = getunits(BL_PC, .@players, false, getarg(0));
+ for (.@i = 0; .@i < .@c; .@i++) {
+ deltimer(getarg(2), .@players[.@i]);
+ addtimer(getarg(1), getarg(2), .@players[.@i]);
+ }
+ return .@i;
+}
+
+// mapdeltimer("<map>", "<npc>::<event>")
+function script mapdeltimer {
+ .@c = getunits(BL_PC, .@players, false, getarg(0));
+ for (.@i = 0; .@i < .@c; .@i++) {
+ deltimer(getarg(1), .@players[.@i]);
+ }
+ return .@i;
+}
diff --git a/npc/functions/treasure.txt b/npc/functions/treasure.txt
new file mode 100644
index 00000000..785dd4a0
--- /dev/null
+++ b/npc/functions/treasure.txt
@@ -0,0 +1,134 @@
+// Moubootaur Legends functions.
+// Author:
+// Jesusalva
+// Description:
+// Random Treasure Box Utils
+
+function script TreasureBox {
+ .@id=getnpcid();
+ if (RNGTREASURE_DATE[.@id] > gettimetick(2)) {
+ mesc l("The chest is unlocked and empty.");
+ close;
+ }
+
+ mesc l("Open the chest?");
+ mesc l("Cost: 1 %s", getitemlink(TreasureKey)), 1;
+ if (!countitem(TreasureKey))
+ close;
+ next;
+ if (askyesno() == ASK_NO)
+ close;
+
+ delitem TreasureKey, 1;
+ mesc l("You open the chest!");
+ RNGTREASURE_DATE[.@id]=gettimetick(2)+CHEST_WAITTIME; // Minimum 15 minutes
+
+ .@empty=getvariableofnpc(.empty, strnpcinfo(0));
+ if (!.@empty) {
+ TREASURE_OPEN=TREASURE_OPEN+1;
+ .@t=TREASURE_OPEN;
+ .@r=rand(0,10000)-(readbattleparam(getcharid(3), UDT_LUK)*2);
+
+ // Select treasure list
+ // You're warranted a rare (5%) every 25 open chests
+ // There's also uncommons (20%) and commons (75%)
+ if (.@t == 1)
+ .@loot=WoodenBow;
+ else if (.@t % 25 == 0 || .@r < 500) // Rare: 5%
+ .@loot=any(AtroposMixture, ElixirOfLife, BigHealing, BigMana, DeathPotion, MagicFeather);
+ else if (.@r < 2500) // Uncommon: 20%
+ .@loot=any(FatesPotion, ClothoLiquor, LachesisBrew, RedPlushWine, TreasureMap, MediumHealing, MediumMana);
+ else // Common: 75%
+ .@loot=any(Bread, Fungus, Cheese, Aquada, Croconut, PiberriesInfusion, Carrot, SmallHealing, SmallMana);
+
+
+ inventoryplace .@loot, 1;
+ mesc l("You find %s inside!", getitemlink(.@loot));
+ getitem .@loot, 1;
+ } else {
+ mesc l("You find %s inside!", l("nothing"));
+ }
+ return;
+}
+
+// Animation code by Evol Team
+// 4144, gumi, Hal9000, Reid
+// (Random) Treasure Chest
+// Authored by Jesusalva
+// Regenerates every 6 hours
+001-3-0,0,0,0 script #chest_001-3-0 NPC_CHEST,{
+ /*
+ // Extract the map name - Seems unused
+ explode(.@ni$, .name$, "_");
+ .@map$=.@ni$[1];
+ //if (.@map$ == "") debugmes "Error";
+ */
+
+ // Conditionals
+ if (!.busy) {
+ TreasureBox();
+
+ specialeffect(.dir == 0 ? 24 : 25, AREA, getnpcid()); // closed ? opening : closing
+ .dir = .dir == 0 ? 2 : 6; // closed ? opening : closing
+ .busy = true; // lock until available again
+ initnpctimer;
+ } else {
+ mesc l("Someone looted this treasure box already...");
+ }
+ close;
+
+OnTimer160:
+ .dir = .dir == 6 ? 0 : 4; // closing ? closed : open
+ end;
+
+OnTimer500:
+ // It's closed: Make available and stop timer
+ if (.dir == 0) {
+ .busy = false;
+ stopnpctimer;
+ }
+ end;
+
+// Autoclose
+OnTimer60000:
+ .dir = 6; // closing
+ specialeffect(25, AREA, getnpcid()); // closing
+ setnpctimer 0;
+ end;
+
+OnInit:
+ .busy = false;
+ .distance = 2;
+
+OnClock0156:
+OnClock0756:
+OnClock1356:
+OnClock1956:
+ // Try to warp randomly to a walkable spot, up to 20 attempts
+ // Otherwise, it'll stay where it already is (but will close and refill).
+ .@e=0; .@x=0; .@y=0;
+ while (!checkcell(.map$, .@x, .@y, cell_chkpass))
+ {
+ if (.@e == 20) {
+ .@x=.x;
+ .@y=.y;
+ break;
+ }
+ // Remember the +20 -20 margin adjustment
+ .@x = rand2(20, getmapinfo(MAPINFO_SIZE_X, .map$)-20);
+ .@y = rand2(20, getmapinfo(MAPINFO_SIZE_X, .map$)-20);
+ ++.@e;
+ }
+ .busy=false;
+ movenpc .name$, .@x, .@y, 0;
+ end;
+}
+
+// Lets bring some treasure to The Mana World
+008-3-4,0,0,0 duplicate(#chest_001-3-0) #chest_008-3-4 NPC_TREASURE
+008-3-5,0,0,0 duplicate(#chest_001-3-0) #chest_008-3-5 NPC_TREASURE
+008-3-6,0,0,0 duplicate(#chest_001-3-0) #chest_008-3-6 NPC_TREASURE
+
+012-3-1,0,0,0 duplicate(#chest_001-3-0) #chest_012-3-1 NPC_TREASURE
+012-3-3,0,0,0 duplicate(#chest_001-3-0) #chest_012-3-3 NPC_TREASURE
+
diff --git a/npc/functions/util.txt b/npc/functions/util.txt
new file mode 100644
index 00000000..cedd4202
--- /dev/null
+++ b/npc/functions/util.txt
@@ -0,0 +1,102 @@
+// Evol functions.
+// Authors:
+// Jesusalva
+// Reid
+// Description:
+// Util functions
+
+
+// season_direction({day, month})
+// returns the direction that represents our current season (approximation)
+// Note: You may also use WINTER/SPRING/SUMMER/AUTUMN constants for scripts
+// where the direction is not important, but the season is. (Readability)
+// DOWN: Winter, 21/12 WINTER
+// DOWNLEFT: Spring, 20/03 SPRING
+// LEFT: Summer, 21/06 SUMMER
+// UPLEFT: Autumn, 22/09 AUTUMN
+
+function script season_direction {
+ .@current_month = getarg(1, gettime(GETTIME_MONTH));
+
+ if (.@current_month % 3 == 0)
+ {
+ .@current_day = getarg(0, gettime(GETTIME_DAYOFMONTH));
+
+ switch (.@current_month)
+ {
+ case MARCH: .@season_day = 20; break;
+ case JUNE: .@season_day = 21; break;
+ case SEPTEMBER: .@season_day = 22; break;
+ case DECEMBER: .@season_day = 21; break;
+ default: break;
+ }
+
+ .@is_after_season_day = .@current_day >= .@season_day ? 0 : -1;
+ }
+
+ return (.@current_month / 3 + .@is_after_season_day) % 4;
+}
+
+// This is part of Jesusalva script toolkit to make his life easier when writing
+// quests. Many of these are actually redundant functions.
+
+// Four different flavours of setq() to quickly preserve old values
+function script setq1 {
+ // Quest, val1 , val2 , val3 , time
+ setq getarg(0), getarg(1), getq2(getarg(0)), getq3(getarg(0)), getqtime(getarg(0));
+ return;
+}
+
+function script setq2 {
+ // Quest, val1 , val2 , val3 , time
+ setq getarg(0), getq(getarg(0)), getarg(1), getq3(getarg(0)), getqtime(getarg(0));
+ return;
+}
+
+function script setq3 {
+ // Quest, val1 , val2 , val3 , time
+ setq getarg(0), getq(getarg(0)), getq2(getarg(0)), getarg(1), getqtime(getarg(0));
+ return;
+}
+
+function script setqtime {
+ // Quest, val1 , val2 , val3 , time
+ setq getarg(0), getq(getarg(0)), getq2(getarg(0)), getq3(getarg(0)), getarg(1);
+ return;
+}
+
+// gettimeparam(GETTIME_X)
+// Returns the number of seconds/minutes/hours/days/months/years since 01/01/1970
+// This is for truly daily quests, which doesn't imposes a timed wait in hours
+function script gettimeparam {
+ .@p=getarg(0, GETTIME_MINUTE);
+
+ // Seconds (same as gettimetick(2) - use that instead)
+ .@t=gettimetick(2);
+ if (.@p == GETTIME_SECOND)
+ return .@t;
+
+ // Minutes (default)
+ .@t=.@t/60;
+ if (.@p == GETTIME_MINUTE)
+ return .@t;
+
+ // Hours
+ .@t=.@t/60;
+ if (.@p == GETTIME_HOUR)
+ return .@t;
+
+ // Days
+ .@t=.@t/24;
+ if (.@p == GETTIME_DAYOFMONTH)
+ return .@t;
+
+ // Months (estimative)
+ .@t=.@t/30;
+ if (.@p == GETTIME_MONTH)
+ return .@t;
+
+ // Years (estimative, unused, fallback)
+ .@t=.@t/12;
+ return .@t;
+}
diff --git a/npc/functions/vault.txt b/npc/functions/vault.txt
new file mode 100644
index 00000000..1cfe7c99
--- /dev/null
+++ b/npc/functions/vault.txt
@@ -0,0 +1,98 @@
+// TODO: create a Vault hercules plugin for native support
+
+// NOTE: no script other than the functions in this file should EVER access
+// ##VAULT[] or the vault-bound variables
+
+/**
+ * Gets the Vault account ID of the provided or attached player.
+ * If the server does not use Vault, it returns a virtual Vault ID.
+ *
+ * Example:
+ * getvaultid("player name");
+ *
+ * @param 0? - char name / account id (defaults to attached player)
+ * @return the Vault ID
+ */
+function script getvaultid {
+ if (SERVER_USES_VAULT) {
+ // we dereference the variable to avoid accidental assignment
+ return 0+ getvariableofpc(##VAULT[0], nameid2id(getarg(0, "")));
+ } else {
+ return nameid2id(getarg(0, ""));
+ }
+}
+
+/**
+ * gets a (fake) vault account-bound variable.
+ * right now these are map-server global variables so they should be used
+ * sparingly
+ *
+ * Example:
+ * set(getvaultvar(VAR$), "foo bar");
+ *
+ * @param 0 - a variable name without prefix
+ * @param 1? - char name / account id (defaults to attached player)
+ * @return a reference to the variable
+ */
+function script getvaultvar {
+ if ((getdatatype(getarg(0)) & DATATYPE_VAR) == 0) {
+ consolemes(CONSOLEMES_ERROR, "getvaultvar: first argument should be a variable");
+ end;
+ }
+
+ .@var$ = data_to_string(getarg(0));
+
+ if (charat(.@var$, 0) == "." || charat(.@var$, 0) == "@" ||
+ charat(.@var$, 0) == "$" || charat(.@var$, 0) == "#") {
+ consolemes(CONSOLEMES_ERROR, "getvaultvar: the variable must be unprefixed");
+ end;
+ }
+
+ if (SERVER_USES_VAULT) {
+ .@vault = getvaultid(getarg(1, ""));
+ return getd(sprintf("$VAULT_%s[%i]", .@var$, .@vault));
+ } else {
+ return getvariableofpc(getd(sprintf("##%s", .@var$)), nameid2id(getarg(1, "")));
+ }
+}
+
+/**
+ * sets a (fake) vault account-bound variable.
+ * right now these are map-server global variables so they should be used
+ * sparingly
+ *
+ * Example:
+ * setvaultvar(FOO$, "bar");
+ *
+ * @param 0 - a variable name without prefix
+ * @param 1 - the value to set
+ * @param 2? - char name / account id (defaults to attached player)
+ * @return a reference to the variable
+ */
+function script setvaultvar {
+ return set(getvaultvar(getarg(0), getarg(2, "")), getarg(1));
+}
+
+/**
+ * handles Vault hooks on player login
+ */
+- script VaultHandler NPC_HIDDEN,{
+ public function OnDualLogin {
+ .@toKick$ = strcharinfo(PC_NAME, "", getarg(0, 0));
+
+ if (.@toKick$ != "") {
+ .@msg$ = sprintf("Kicking player %s (Vault dual-login)", .@toKick$);
+ consolemes(CONSOLEMES_INFO, .@msg$); // log this
+ dispbottom(.@msg$); // tell the player
+
+ return kick(.@toKick$, 2); // reason 2 is dual-login
+ }
+
+ return false;
+ }
+
+OnInit:
+ if (SERVER_USES_VAULT) {
+ "playerCache"::addVaultHandler("OnDualLogin");
+ }
+}
diff --git a/npc/functions/villagertalk.txt b/npc/functions/villagertalk.txt
new file mode 100644
index 00000000..371a9f20
--- /dev/null
+++ b/npc/functions/villagertalk.txt
@@ -0,0 +1,53 @@
+// Evol functions.
+// Authors:
+// Akko Teru
+// Qwerty Dragon
+// Reid
+// Description:
+// Tell a random sentence. || There ought to be a law!
+
+function script villagertalk {
+
+ function darn_or_smile
+ {
+ .@darn = rand(42);
+
+ if (.@darn < 26)
+ {
+ emotion E_JOY;
+ hello;
+ }
+ else if (.@darn > 26)
+ {
+ emotion E_LOOKAWAY;
+ goodbye;
+ }
+ else
+ {
+ npctalkonce(l("Stop it!"));
+ }
+
+ return;
+ }
+
+ switch (rand(4))
+ {
+ case 0:
+ darn_or_smile();
+ break;
+ case 1:
+ npctalkonce(l("It is a sunny day, don't you think?"));
+ break;
+ case 2:
+ npctalkonce(l("Go fly a kite."));
+ break;
+ case 3:
+ npctalkonce(l("I just want to live my life in peace."));
+ break;
+ default:
+ emotion E_HAPPY;
+ break;
+ }
+
+ return;
+}
diff --git a/npc/functions/warp.txt b/npc/functions/warp.txt
new file mode 100644
index 00000000..46c390ad
--- /dev/null
+++ b/npc/functions/warp.txt
@@ -0,0 +1,58 @@
+// Evol functions.
+// Authors:
+// gumi
+
+
+
+// map_exists
+// self-explanatory
+
+function script map_exists {
+ return getmapinfo(MAPINFO_ID, getarg(0)) >= 0;
+}
+
+
+
+// slide_or_warp
+// Slides the player instead of warping, when possible.
+// usage:
+// slide_or_warp({<aid>});
+// slide_or_warp(<x>, <y>{, <aid>});
+// slide_or_warp("<map>", <x>, <y>{, <aid>});
+
+function script slide_or_warp {
+ if (getargcount() <= 1) {
+ .@aid = getarg(1, 0);
+ } else {
+ if (getargcount() >= 3 && getdatatype(getarg(0)) & DATATYPE_STR) {
+ .@map$ = getarg(0);
+ .@x = getarg(1);
+ .@y = getarg(2);
+ .@aid = getarg(3, 0);
+ } else {
+ .@x = getarg(0);
+ .@y = getarg(1);
+ .@aid = getarg(2, 0);
+ }
+ }
+
+ if (!isloggedin(.@aid)) {
+ if ((.@aid = playerattached()) == 0) {
+ consolemes(CONSOLEMES_DEBUG, "slide_or_warp: no player attached!");
+ return false;
+ }
+ }
+
+ getmapxy(.@pc_map$, .@pc_x, .@pc_y, UNITTYPE_PC, .@aid); // get char location
+ .@cid = getcharid(CHAR_ID_CHAR, strcharinfo(PC_NAME, .@aid)); // FIXME: [Hercules] make it so you can pass account id directly to getcharid
+
+ if (getargcount() < 1) {
+ warpchar(.@pc_map$, .@pc_x, .@pc_y, .@cid); // no arguments, just refresh
+ } else if (.@map$ == .@pc_map$ && (.@pc_x != .@x || .@pc_y != .@y)) {
+ slide(.@x, .@y); // same map, slide instead of full warp
+ // FIXME: make slide take GID as optional arg
+ } else {
+ warpchar(.@map$, .@x, .@y, .@cid); // different map, warp to given location
+ }
+ return true;
+}