diff options
author | Jesusaves <cpntb1@ymail.com> | 2021-04-09 11:00:49 -0300 |
---|---|---|
committer | Jesusaves <cpntb1@ymail.com> | 2021-04-09 11:00:49 -0300 |
commit | 8a4bf716002a017de77fe7df301ef8e4aaf00a2e (patch) | |
tree | 4947d0b015baad639fa07133369e9a09c8a468bc /npc/functions | |
download | serverdata-8a4bf716002a017de77fe7df301ef8e4aaf00a2e.tar.gz serverdata-8a4bf716002a017de77fe7df301ef8e4aaf00a2e.tar.bz2 serverdata-8a4bf716002a017de77fe7df301ef8e4aaf00a2e.tar.xz serverdata-8a4bf716002a017de77fe7df301ef8e4aaf00a2e.zip |
Initial commit
Diffstat (limited to 'npc/functions')
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; +} |