diff options
Diffstat (limited to 'world/map/npc/functions')
-rw-r--r-- | world/map/npc/functions/doomsday.txt | 4 | ||||
-rw-r--r-- | world/map/npc/functions/global_event_handler.txt | 21 | ||||
-rw-r--r-- | world/map/npc/functions/spawns_on_mobkill.txt | 81 | ||||
-rw-r--r-- | world/map/npc/functions/teleport_manager.txt | 553 | ||||
-rw-r--r-- | world/map/npc/functions/treasure_hunt.txt | 469 |
5 files changed, 1123 insertions, 5 deletions
diff --git a/world/map/npc/functions/doomsday.txt b/world/map/npc/functions/doomsday.txt index bba08e75..2be517c3 100644 --- a/world/map/npc/functions/doomsday.txt +++ b/world/map/npc/functions/doomsday.txt @@ -979,7 +979,3 @@ OnDeathAct4: end; } - - - - diff --git a/world/map/npc/functions/global_event_handler.txt b/world/map/npc/functions/global_event_handler.txt index d169f3c7..ed95b442 100644 --- a/world/map/npc/functions/global_event_handler.txt +++ b/world/map/npc/functions/global_event_handler.txt @@ -19,11 +19,14 @@ OnPCLoginEvent: callfunc "ConvertChristmas21"; // Christmas 2021 callfunc "VaultLogin"; // Mirror Lake Protocol biddings callfunc "VaultLogout"; // Clean some stuff, to be safe + if (WEIGHT_LIMIT) set MaxWeightOverride, WEIGHT_LIMIT; // If this char var set, override max weight (tradebots, etc) + if (PC_BOSS_REWARD) set @PC_BOSS_REWARD, PC_BOSS_REWARD; // Load tempvar from per-acct var (needed by maybe_reward_boss_kill) // add more here set @login_event, 2; end; OnPCKillEvent: + callfunc "maybe_reward_boss_kill"; callfunc "elanore_decrease_exp"; // decrease heal exp for doing bad things callfunc "SweetTooth"; end; @@ -32,6 +35,7 @@ OnMobKillEvent: callfunc "MobPoints"; callfunc "MobKillHandler"; callfunc "SweetTooth"; + callfunc "spawns_on_mobkill"; end; OnPCDieEvent: @@ -45,7 +49,7 @@ OnPCDieEvent: end; OnPCLogoutEvent: -// if (getnpcid("#Upmarmu#" + getcharid(3)) >= 1) addnpctimer 0, "#Upmarmu#" + getcharid(3) + "::OnDestroy"; // @upmarmu_spell_npc_name$ is already lost at this point so it must be done like this + if (getnpcid("#Upmarmu#" + getcharid(3)) >= 1) addnpctimer 0, "#Upmarmu#" + getcharid(3) + "::OnDestroy"; // @upmarmu_spell_npc_name$ is already lost at this point so it must be done like this callfunc "VaultLogout"; end; @@ -58,3 +62,18 @@ OnDay0411: callfunc "MaybeAnnounceBirthday"; end; } + +// Inputs: nothing, but assumes RID attached and @victimrid set on PK kill +// Return: nothing, but adjusts BOSS_POINTS +// TODO: move this func somewhere else maybe? +function|script|maybe_reward_boss_kill +{ // not here or lacks reward for head -> skip rewarding. + if ((@victimrid < 1) || !(isloggedin(@victimrid)) || (get(@PC_BOSS_REWARD, @victimrid)) < 1) goto L_SkipReward; + set BOSS_POINTS, (BOSS_POINTS + get(@PC_BOSS_REWARD, @victimrid)); + message strcharinfo(0), "[boss reward] : you killed eventer-controlled boss and gained " + get(@PC_BOSS_REWARD, @victimrid) + + " boss points, for total of " + BOSS_POINTS + " boss points"; + return; + +L_SkipReward: + return; +} diff --git a/world/map/npc/functions/spawns_on_mobkill.txt b/world/map/npc/functions/spawns_on_mobkill.txt new file mode 100644 index 00000000..fadb6cd1 --- /dev/null +++ b/world/map/npc/functions/spawns_on_mobkill.txt @@ -0,0 +1,81 @@ +// Mob splitting/spawn logic By Hello=) + +// Spawns some mobs on death of other mob. E.g. split BIG slime -> few smaller ones +// This function meant to be called with player RID attached (usually in OnMobKillEvent) +// Inputs: nothing, but expects @mobID, @mobX and @mobY set (usually by server) +// Return: nothing, but spawns few things. +function|script|spawns_on_mobkill +{ + if (@mobID == SeaSlimeMother) goto L_SplitSea; + if ((@mobID == GreenSlimeMother) || (@mobID == GreenSuperSlime)) goto L_SplitGreen; + if (@mobID == YellowSuperSlime) goto L_SplitYellow; + if (@mobID == RedSuperSlime) goto L_SplitRed; + if (@mobID == BlueSuperSlime) goto L_SplitBlue; + if (@mobID == Tormenta) goto L_TorWitchDead; + if ((@mobID == Luvia) && (getmap() != "052-2")) goto L_LuvWitchDead; // Skip spawns if its Illia + if (@mobID == Blanc1) goto L_Blanc1Dead; + return; // Not mob of interest -> do nothing + +L_SplitSea: + void call("spawn_mobs_around", getmap(), @mobX, @mobY, AngrySeaSlime, rand(8, 16)); + return; + +L_SplitGreen: + void call("spawn_mobs_around", getmap(), @mobX, @mobY, AngryGreenSlime, rand(8, 16)); + return; + +L_SplitYellow: + void call("spawn_mobs_around", getmap(), @mobX, @mobY, YellowSlime, rand(6, 16)); + return; + +L_SplitRed: + void call("spawn_mobs_around", getmap(), @mobX, @mobY, RedSlime, rand(6, 16)); + return; + +L_SplitBlue: + void call("spawn_mobs_around", getmap(), @mobX, @mobY, BlueSlime, rand(3, 6)); + return; + +L_TorWitchDead: + void call("spawn_mobs_around", getmap(), @mobX, @mobY, VoidBat, rand(7, 12)); + void call("spawn_mobs_around", getmap(), @mobX, @mobY, DemonicSpirit, rand(5, 10)); + void call("spawn_mobs_around", getmap(), @mobX, @mobY, UndeadWitch, 1); + return; + +L_LuvWitchDead: + void call("spawn_mobs_around", getmap(), @mobX, @mobY, VoidBat, rand(7, 12)); + void call("spawn_mobs_around", getmap(), @mobX, @mobY, DemonicSpirit, rand(5, 8)); + void call("spawn_mobs_around", getmap(), @mobX, @mobY, UndeadWitch, 1); + return; + +L_Blanc1Dead: + void call("spawn_mobs_around", getmap(), @mobX, @mobY, Blanc2, 1); + return; +} + +// Spawns mobs around spot, if it can - or stacks mobs on spot if no room for 3x3 area +// This function can be called from any context. +// Inputs: arg0: map (string), arg1: X, arg2: Y, arg3: mob ID, arg4: amount +// Return: nothing, but spawns few things. +function|script|spawn_mobs_around +{ + set .@map$, getarg(0, ""); // map where to spawn + set .@mobX, getarg(1, -1); // X coord + set .@mobY, getarg(2, -1); // Y coord + set .@mobID, getarg(3, -1); // Mob ID to spawn. + set .@mobQTY, getarg(4, -1); // Amount. + if ((.@map$ == "") || (.@mobX < 1) || (.@mobY < 1) || (.@mobID < 1002) || + (.@mobX > getmapmaxx(.@map$)) || (.@mobY > getmapmaxy(.@map$)) || + (.@mobQTY < 1)) // Invalid parameters given? + goto L_Abort; // Yell and return + if ((.@mobX > 1) && (.@mobY > 1) && (.@mobX < getmapmaxx(.@map$)) && + (.@mobY < getmapmaxy(.@map$)) && (.@mobQTY > 1)) //Enough room for 3x3 && "mass" spawn + areamonster .@map$, (.@mobX-1), (.@mobY-1), (.@mobX+1), (.@mobY+1), "", .@mobID, .@mobQTY; + else + monster .@map$, .@mobX, .@mobY, "", .@mobID, .@mobQTY; // 3x3 wouldnt fit or just 1 mob. + return; + +L_Abort: + debugmes "spawn_mob_around: invalid args! Map=" + .@map$ + " x=" + .@mobX + " y=" + .@mobY + " mobID=" + .@mobID + " mobQTY=" + .@mobQTY; + return; +} diff --git a/world/map/npc/functions/teleport_manager.txt b/world/map/npc/functions/teleport_manager.txt new file mode 100644 index 00000000..529bd5e0 --- /dev/null +++ b/world/map/npc/functions/teleport_manager.txt @@ -0,0 +1,553 @@ +// GateBuilders toolkit aka "arbitrary teleports". +// Author: Hello=). Credits go to Freeyorp and HoraK for some insights. + +// This lacks limits of previously existing teleport systems. +// * New teleports can be instatiated as needed, running side by side. +// * Teleports can have finite lifetime, disappearing themself. +// * Teleports can be removed, leaving no side effects. +// * No leftovers or side effects after teleport removed. +// * Up to about 100 teleports can coexist. +// * Flexible condition check based on cookie. If some gate got cookie set +// then it only lets players with matching variable to pass. +// * Aspects like timeout, FX and their timing, etc can be customized. +// * Teleports meant to be registered in TeleportManager. However, its +// possible to create "unmanaged" teleports (e.g. for spells). +// * In future it can allow custom hooks/checks on teleporters (planned) + +// Technically, TeleportManager NPC keeps teleports as array of integers. +// with structure .teleports[.index] = Teleport's NPC ID. +// Up to 100 teleports allowed (.index = 1..100, .index = 0 is unused). +// if .teleports[.index] = 0 -> empty/reusable slot. +// All teleport specific data stored in puppet NPC itself. +// Teleport NPCs are puppets of TeleportManager and inherit its code. + +// Attempt will be made to make it reasonably robust. That is, +// * Try to handle usage bugs/bogus data/etc. +// * No actions causing big server side fallouts. +// * No out of bounds, nonexistent NPC references, etc. + +// Teleports have the following variables (some are set on teleport_create) +// .srcmap$ - source map of teleport's npc +// .src_x - source map of teleport's npc, X coord +// .src_y - source map of teleport's npc, Y coord +// .dstmap$ - destination map of teleport's npc +// .dst_x - destination map of teleport's npc, X coord +// .dst_y - destination map of teleport's npc, Y coord +// .lifetime - teleport's lifetime, in ms. 0 = permanent (until server restart) +// .cookie - if set, only teleport players with teleport_cookie == NPC's .cookie +// .fx, - FX effect to play on teleport. +// .fx_time, - Time FX allowed to play before teleport actually happens. +// .IS_MANAGER - Indicates TeleportManager NPC. Puppets wouldnst have that. +// .managed - Indicates its teleport managed by TeleportManager. Its possible to +// create unmanaged teleports, e.g. transient teleports by spells. +// .teleport - Set to 1 to be able to distinguish teleport NPCs from other NPCs +// .info$ - Description, shown to user when clicking NPC +// .inactive - set to disable OnTouch reaction. Useful for staged shutdown or +// e.g. temporarily deactivating teleport for whatever reason. + +// Key functcions: teleport_add and teleport_del. Rest are "helpers". + +// Main NPC code, runs on both TeleportManager and teleport puppets. +// Do not rename NPC: functions below rely on NPC name. +-|script|TeleportManager|32767 +{ + message strcharinfo(0), .info$; // Display info about teleport, var allows message change. + end; + +// Manager code below - puppets dont use OnCmd* events +// Invoked when someone adds teleport via @teleportadd +OnCmdAdd: + if (call("teleport_access_check")) end; // Not allowed to use this. + set .@idx, call("teleport_find_slot", 0); // Try to find empty slot (containing 0) + if (.@idx <= 0) goto L_AddNoRoom; // No empty slots? NB: teleports[0] not used. + if (call ("teleportadd_parseargs") <= 0) goto L_AddArgsFail; // @VARs like @POS_X, @NAME$, etc set by teleportadd_parseargs() +// Create actual teleport NPC ------- src map - src x src y --- name - NPC ---- Sz Sz dst map - dst x - dst y -- time - cook managed? + set .@tp, call("teleport_create", getmap(), @POS_X, @POS_Y, @NAME$, @NPCSPRITE, 0, 0, @DSTMAP$, @DST_X, @DST_Y, @TIMEOUT, 0, 1); + if (.@tp <= 0) goto L_AddPuppetFail; // Has puppet() failed? + set .teleports[.@idx], .@tp; // Store NPC ID -> .teleports[.@idx] slot. Access safe: teleport_find_slot() == 1..100 + gmlog strcharinfo(0) + " accessed TeleportManager: @teleportadd " + @args$; + wgm strcharinfo(0) + " accessed TeleportManager: @teleportadd " + @args$; + message strcharinfo(0), "[TeleportManager] : Added: [" + .@idx + "] " + + (get(.srcmap$, .@tp)) + " " + (get(.src_x, .@tp)) + "," + (get(.src_y, .@tp)) + + " -> " + (get(.dstmap$, .@tp)) + " " + (get(.dst_x, .@tp)) + "," + (get(.dst_y, .@tp)) + + " lifetime:" + ((get(.lifetime, .@tp)) / 1000) + "s, Name:" + strnpcinfo(0, .@tp) + " (" + .@tp + ")"; + end; + +L_AddNoRoom: + message strcharinfo(0), "[TeleportManager] : Too many teleports open, max 100 tracked teleports"; + message strcharinfo(0), "[TeleportManager] : use @teleportlist to list and @teleportdel to remove some."; + end; + +L_AddArgsFail: + if (@DSTMAP$ != "help") message strcharinfo(0), "[TeleportManager] : try @teleportadd help or @teleporthelp"; + end; + +L_AddPuppetFail: + message strcharinfo(0), "[TeleportManager] : failed to add teleport, check params. Duplicate NPC name maybe?"; + end; + +// Invoked when someone removes teleport via @teleportdel +OnCmdDel: + if (call("teleport_access_check")) end; // Not allowed to use this. + callfunc "argv_splitter"; + if (@argv[0] <= 0) goto L_DelFail; + if (call("teleport_delete", @argv[0]) <= 0) goto L_DelNotFound; + message strcharinfo(0), "[TeleportManager] : requested teleport ID [" + @argv[0] + "] to terminate"; + gmlog strcharinfo(0) + " accessed TeleportManager: @teleportdel " + @args$; + wgm strcharinfo(0) + " accessed TeleportManager: @teleportdel " + @args$; + end; + +L_DelNotFound: + message strcharinfo(0), "[TeleportManager] : teleport ID [" + @argv[0] + "] not found."; + end; + +L_DelFail: + message strcharinfo(0), "[TeleportManager] : Bad parameters."; + callfunc("teleport_help_del"); + end; + +// Invoked when someone lists teleports via @teleportlist +OnCmdList: + if (call("teleport_access_check")) end; // Not allowed to use this. + callfunc("teleports_list"); + gmlog strcharinfo(0) + " accessed TeleportManager: @teleporlist" + @args$; + end; + +// Invoked when someone requests help via @teleporthelp +// (doesn't needs access checks - does nothing dangerous) +OnCmdHelp: + message strcharinfo(0), "[TeleportManager] : Commands: @teleportadd @teleportdel @teleportlist @teleporthelp"; + message strcharinfo(0), "[TeleportManager] : @teleportlist - display list of active teleports"; + callfunc("teleport_help_del"); + callfunc("teleport_help_add"); + end; + +// Puppets (teleportation pads) logic below. +// Invoked when player steps on npc. Manager NPC ignores this. +OnTouch: + if (.IS_MANAGER) end; // Manager NPC isnt teleport -> no interaction. + if (.inactive) end; // If gate deactivated -> no interaction. + if ((.cookie) && (teleport_cookie != .cookie)) goto L_CantPass; + set teleport_cookie, 0; // Clear teleport cookie of player on teleport use. + sc_start SC_SLOWMOVE, .fx_time+200, 100000; // Slow player temporarily to avoid movement VS warp DCs + addtimer .fx_time, strnpcinfo(0)+"::OnTeleport"; // time before teleporting away + misceffect .fx; // Default .fx set in teleport_add, other code can override it. + end; +L_CantPass: + message strcharinfo(0), .cantpass$; // NPC var allows to change message. + end; // "keyed" teleport and player didnt had proper cookie. + +// Teleportation timer event (attached to player). Queued by OnTouch. +OnTeleport: + warp .dstmap$, .dst_x, .dst_y; + addtimer 3000, strnpcinfo(0)+"::OnWarpDone"; // Mostly to clean up SC icon + end; + +// Mostly used to remove slow icon that sticks in some cases. +OnWarpDone: + sc_end SC_SLOWMOVE; + end; + +// Invoked on timed teleport's NPC timer expired. +OnTeleportExpired: + set .inactive, 1; // Flag shutdown so OnTouch ignores incoming players. + addnpctimer (.fx_time + 1000), strnpcinfo(0)+"::OnTeleportShutdown"; // Give time to in-flight players to teleport. + end; + +// Does actual teleport shutdown. +OnTeleportShutdown: + void call("teleport_mgr_clean"); // Cleanups adequately both "managed" and "unmanaged" teleports. + if !(.IS_MANAGER) destroy; // Dont ever try to destroy manager npc. + end; + +OnInit: + set .IS_MANAGER, 1; // Only Manager NPC would have this, puppets wouldnt. + set .info$, "TeleportManager : This thing is unlike anything you've seen before"; // Manager's NPC message on click. + registercmd "@teleportadd", "TeleportManager::OnCmdAdd"; + registercmd "@teleportdel", "TeleportManager::OnCmdDel"; + registercmd "@teleportlist", "TeleportManager::OnCmdList"; + registercmd "@teleporthelp", "TeleportManager::OnCmdHelp"; + registercmd "@teleport", "TeleportManager::OnCmdHelp"; + end; +} + +// PUBLIC API. Creates requested teleport NPC. Arg 0..6 are like puppet(). +// This function designed to run in ANY context, whether RID attached or not. +// It creates puppet + setups all relevant data in created NPC. +// arg(0): Teleport's map. +// arg(1): Teleport's X +// arg(2): Teleport's Y +// arg(3): Teleport's NPC label +// arg(4): Teleport's NPC sprite ID +// arg(5): Teleport's X size +// arg(6): Teleport's Y size +// arg(7): Destination map name. +// arg(8): Destination X +// arg(9): Destination Y +// arg(10):Teleport lifetime ( > 0 or -1 = indefinite) +// arg(11):Teleport magic cookie, 0 = everyone allowed, otherwise checks match. +// arg(12):Whether teleport managed, 0 = unmanaged, != 0 means it is. +// Return: On success: NPC ID of teleport created. +// On failure: 0 if puppet failed, -1 if arg check failed. +function|script|teleport_create +{ + set .@map$, getarg(0, ""); // Get args (+failsafe defailts) + set .@x, getarg(1, -10); // Get args (+failsafe defailts) + set .@y, getarg(2, -10); // Get args (+failsafe defailts) + set .@name$, getarg(3, ""); // Get args (+failsafe defailts) + set .@sprite, getarg(4, -10); // Get args (+failsafe defailts) + set .@xsz, getarg(5, -10); // Get args (+failsafe defailts) + set .@ysz, getarg(6, -10); // Get args (+failsafe defailts) + set .@dstmap$, getarg(7, -10); // Get args (+failsafe defailts) + set .@dst_x, getarg(8, -10); // Get args (+failsafe defailts) + set .@dst_y, getarg(9, -10); // Get args (+failsafe defailts) + set .@lifetime, getarg(10, -10); // Get args (+failsafe defailts) + set .@cookie, getarg(11, -10); // Get args (+failsafe defailts) + set .@managed, getarg(12, -10); // Get args (+failsafe defailts) + set .@res, -1; // Validate what caller gave + if !(call("teleport_map_valid", .@map$ )) goto L_Fail; // Validate src map$ + if !(call("teleport_map_valid", .@dstmap$)) goto L_Fail; // Validate dst map$ + if ((.@x < 0) || (.@y < 0)) goto L_Fail; // SRC X/Y cant be < 0 + if ((.@dst_x < 0) || (.@dst_y < 0)) goto L_Fail; // DST X/Y cant be < 0 + if ((.@xsz < 0) || (.@ysz < 0)) goto L_Fail; // NPC X/Y size cant be <= 0 + if (.@name$ == "") goto L_Fail; // NPC name cant be empty + if (.@sprite <= 0) goto L_Fail; // NPC sprite cant be <= 0 + if (.@lifetime < -1 || (.@lifetime) == 0) goto L_Fail; // Lifetime either > 0 or -1 = infinite + if ((.@cookie < 0) || (.@managed < 0)) goto L_Fail; // Cookie and managed cant be < 0 + set .@res, puppet(.@map$, .@x, .@y, .@name$, .@sprite, .@xsz, .@ysz); // instatiate teleport NPC + if (.@res <= 0) goto L_Fail; + //NPC's var value NPC ID -- teleport defaults + set .srcmap$, .@map$, .@res; // set .srcmap$ of NPC + set .src_x, .@x, .@res; // set .src_x of NPC + set .src_y, .@y, .@res; // set .src_y of NPC + set .dstmap$, .@dstmap$, .@res; // set .dstmap$ of NPC + set .dst_x, .@dst_x, .@res; // set .dst_x of NPC + set .dst_y, .@dst_y, .@res; // set .dst_y of NPC + set .lifetime, .@lifetime, .@res; // set .lifetime of NPC + set .cookie, .@cookie, .@res; // set .cookie of NPC + set .fx, 41, .@res; // set default teleport FX + set .fx_time, 350, .@res; // set default FX time (warp delay) + set .managed, .@managed, .@res; // If > 0, npc managed by TeleportManager (unmanaged temp TPs can be e.g. spells) + set .teleport, 1, .@res; // All teleports created by teleport_create have this. + set .info$, "Teleport : strange structure of unknown origins", .@res; // Default on-click message. + set .cantpass$, "Teleport : structure seems to ignore you", .@res; // Default "can't pass" message. + // If timeout requested, set up teardown timer. + if (.@lifetime > 0) addnpctimer .@lifetime, .@name$+"::OnTeleportExpired"; + return .@res; + +L_Fail: + return .@res; +} + +// PUBLIC API. This function deletes teleport. +// This function designed to run in ANY context, whether RID attached or not. +// Teleport removed by NPC destroy + setting its .teleports[.index] = 0. +// Inputs: arg[0] is either slot index (1..100) or NPC ID to remove. +// Return: 1 on success, <= 0 on failure. +function|script|teleport_delete +{ + set .@npctodel, getarg(0, -1); // This is either slot # or NPC ID + if (.@npctodel <= 0) goto L_Error; + if (.@npctodel > 100) goto L_GotNpcId; // If > 100 assume its NPC ID, not slot. + set .@npctodel, call("teleport_get_slot_val", .@npctodel); // Get NPC id from teleport manager slot. + if (.@npctodel <= 0) goto L_Error; // Failed to get NPC ID? + goto L_GotNpcId; + +L_GotNpcId: + if !(get(.teleport, .@npctodel)) goto L_Error; // Sanity check its teleport NPC indeed + donpcevent strnpcinfo(0, .@npctodel)+"::OnTeleportShutdown"; // Request teleport NPC to perform shutdown. + return 1; + +L_Error: + return 0; +} + +// This function prints teleports known to TeleportManager. +// This function MUST run in player context with RID attached to send messages. +// Inputs: nothing, all data taken from TeleportManager NPC. +// Return: nothing, just prints info as it iterates manager's slots. +function|script|teleports_list +{ + set .@idx, 0; + freeloop 1; // Loops via 100 slots -> can time out. + message strcharinfo(0), "[TeleportManager] : ---- Active teleports ----"; + goto L_NextSlot; + +L_NextSlot: + set .@idx, (.@idx+1); + if (.@idx > 100) goto L_Done; // Iterated whole array. + set .@npc, call("teleport_get_slot_val", .@idx); + if (.@npc < 0) goto L_Error; // Abort iteration on error and report it. + if (.@npc > 0) goto L_PrintSlot; // Print slot data. + goto L_NextSlot; // .@npc == 0 // just iterate to next slot + +L_PrintSlot: + if !(get(.managed, .@npc)) goto L_Error; // Sanity check its really "managed teleport" NPC + // Display info about teleport, data taken from NPC. + message strcharinfo(0), "[TeleportManager] : [" + .@idx + "] " + + (get(.srcmap$, .@npc)) + " " + (get(.src_x, .@npc)) + "," + (get(.src_y, .@npc)) + + " -> " + (get(.dstmap$, .@npc)) + " " + (get(.dst_x, .@npc)) + "," + (get(.dst_y, .@npc)) + + " lifetime:" + ((get(.lifetime, .@npc)) / 1000) + "s, Name:" + strnpcinfo(0, .@npc) + " (" + .@npc + ")"; + goto L_NextSlot; + +L_Done: + freeloop 0; + message strcharinfo(0), "[TeleportManager] : ---- End ----"; + return; + +L_Error: + freeloop 0; + message strcharinfo(0), "[TeleportManager] : Error iterating TeleportManager slots (bug?!)"; + return; +} + +// This function finds slot in TeleportManager with given NPC ID. +// This function designed to run in ANY context, whether RID attached or not. +// Inputs: arg[0]: NPC ID to find, 0 means "find free slot", +// Return: slot index in 1..100 range if slot found, <= 0 on fail. +function|script|teleport_find_slot +{ + if (getarg(0) < 0) goto L_Fail; // Caller gave some crap? + set .@wanted, getarg(0); // NPC ID to find (or 0 to find free slot) + set .@manager, getnpcid("TeleportManager"); + if (.@manager <= 0) goto L_Fail; // Manager NPC not found? + set .@i, 1; // 0 slot not used. + freeloop 1; + goto L_TrySlot; // Start iterating via teleport slots on TeleportManager. + +L_TrySlot: + set .@npcid, get(.teleports[.@i], .@manager); + if (.@npcid == .@wanted) goto L_Found; //.teleports[.@i] == desired ID? + if (.@i > 100) goto L_Fail; // No free slots in teleports[1..100] + set .@i, (.@i + 1); goto L_TrySlot; // Try next slot + +L_Found: + freeloop 0; + return .@i; + +L_Fail: + freeloop 0; + return 0; +} + +// This function returns value in TeleportManager's slot with given indes. +// This function designed to run in ANY context, whether RID attached or not. +// Inputs: arg[0]: slot ID, must be 1 .. 100; +// Return: -1 on error, 0 on empty slot, or NPC ID of teleport in given sslot. +function|script|teleport_get_slot_val +{ + if ((getarg(0) < 1) || (getarg (0) > 100)) goto L_Fail; // Bogus index? + set .@index, getarg(0); + set .@manager, getnpcid("TeleportManager"); + if (.@manager <= 0) goto L_Fail; // Manager NPC not found? + set .@ret, get(.teleports[.@index], .@manager); + return .@ret; + +L_Fail: + return -1; +} + +// This function sets slot in TeleportManager with given value. +// This function designed to run in ANY context, whether RID attached or not. +// Inputs: arg[0]: slot ID, must be 1 .. 100; +// arg[1]: value to store to slot; +// Return: 1 on success, <= 0 on failure. +function|script|teleport_set_slot_val +{ + if ((getarg(0) < 1) || (getarg (0) > 100)) goto L_Fail; // Bogus index? + if (getarg(1) < 0) goto L_Fail; // TP slots are NPC ID or 0 for empty + set .@index, getarg(0); + set .@val, getarg(1); + set .@manager, getnpcid("TeleportManager"); + if (.@manager <= 0) goto L_Fail; // Manager NPC not found? + set .teleports[.@i], .@val, .@manager; + return 1; + +L_Fail: + return 0; +} + +// PRIVATE: This function cleans up slot in TeleportManager on teleport shutdown. +// This function ONLY meant to be invoked by teleport NPC puppet on shutdown! +// Inputs: nothing. Gets data from its calling NPC. +// Return: 1 on success, <= 0 on failure. +function|script|teleport_mgr_clean +{ + if !(.managed) goto L_RetOk; // Teleport not managed by TeleportManager -> no cleanup + set .@my_id, getnpcid(); + if (.@my_id <= 0) goto L_RetFail; // Give up on cleanup, slot will leak + // Call chaining OK: teleport_set_slot_val() checks slot # sanity, so teleport_find_slot() fail handled. + set .@res, call("teleport_set_slot_val", call("teleport_find_slot", .@my_id), 0); + if (.@res != 1) goto L_RetFail; + goto L_RetOk; +L_RetOk: + return 1; + +L_RetFail: + debugmes "teleport_mgr_clean: TeleportManager cleanup failure -> slot leak. Likely bug! .@my_id=" + .@my_id + ".@res=" + .@res; + return 0; +} + +// PRIVATE: This function validates @teleportadd args and prepares for teleport_add() call. +// This function MUST be invoked with player RID attached, by TeleportManager NPC +// Inputs: nothing, but assumes args$ set. +// Return: <= 0 on failure, 1 on success. +// Return: sets @POS_X, @POS_X, @DSTMAP$, @DST_X, @DST_Y, +function|script|teleportadd_parseargs +{ + callfunc "argv_splitter"; + set @DSTMAP$, @argv$[0]; // Destination map + set @DST_X, @argv[1]; // Dst warp coordinates + set @DST_Y, @argv[2]; // Dst warp coordinates + set @NAME$, @argv$[3]; // Teleport's label + set @TIMEOUT, @argv[4]; // Teleport's lifetime + set @NPCSPRITE, @argv[5]; // Teleport's NPC sprite (optional) + // Check DST map is okay + if ((@DSTMAP$ == "help") || (@DSTMAP$ == "")) goto L_DisplayHelp; // @teleportadd help or @teleportadd + if !(call("teleport_map_valid", @DSTMAP$)) goto L_FailBadmap; // DST: invalid map? + // Check DST X,Y sane + if ((@DST_X <= 0) || (@DST_Y <= 0)) goto L_FailBadDstXY1; // DST: invalids coords <= 0? + if ((getmapmaxx(@DSTMAP$) < @DST_X) || (getmapmaxy(@DSTMAP$) < @DST_Y)) goto L_FailBadDstXY2; // Outside of map? + // Check if DST is collision + if (iscollision(@DSTMAP$, @DST_X, @DST_Y)) goto L_FailDstCollide; + // Try adaptive NPC placement. Above caller if there's room or on caller if not. + set @POS_X, POS_X; + if ((POS_Y > 2) && !(iscollision(getmap(), POS_X, (POS_Y-2)))) set @POS_Y, (POS_Y - 2); + else set @POS_Y, POS_Y; // Overhead placement failed, use caller's tile + // NPC name checks + if (@NAME$ == "") goto L_FailNPCName; + // TIMEOUT checks and setup + if ((@TIMEOUT < -1) || (@TIMEOUT == 0) || (@TIMEOUT > 2000000)) goto L_FailTimeout; + if (@TIMEOUT > 0) set @TIMEOUT, (@TIMEOUT * 1000); // translate seconds -> ms to make more wieldy numbers + // NPC SPRITE configuration + if ((@NPCSPRITE != 424) && (@NPCSPRITE != 369) && (@NPCSPRITE != 368) + && (@NPCSPRITE != 325) && (@NPCSPRITE != 324)) set @NPCSPRITE, 424; + // All checks complete + return 1; // Everything OK + +L_FailBadmap: + message strcharinfo(0), "[TeleportManager] : @teleportadd: unknown destination map:" + @DSTMAP$; + return -1; + +L_FailBadDstXY1: + message strcharinfo(0), "[TeleportManager] : @teleportadd: destination X,Y must be > 0! Given X=" + @DST_X + " Y=" + @DST_Y; + return -2; + +L_FailBadDstXY2: + message strcharinfo(0), "[TeleportManager] : @teleportadd: destination X,Y outside of map! Given X=" + @DST_X+ " Y=" + @DST_Y; + return -3; + +L_FailDstCollide: + message strcharinfo(0), "[TeleportManager] : @teleportadd: destination MAP=" + @DSTMAP$ + " X=" + @DST_X + " Y=" + @DST_Y + " is a collision (impassable)"; + return -4; + +L_FailNPCName: + message strcharinfo(0), "[TeleportManager] : @teleportadd: NPCNAME can't be empty!"; + return -5; + +L_FailTimeout: + message strcharinfo(0), "[TeleportManager] : @teleportadd: timeout must be either -1, or > 0 and < 2000000 (seconds)"; + return -6; + +L_DisplayHelp: + void call("teleport_help_add"); + return -7; +} + +// PUBLIC API: This function checks if map name known and OK to use. +// This function designed to run in any context. +// Inputs: arg$[0] is map name to check for sanity. +// Return: 1 for known maps, 0 for unknown +function|script|teleport_map_valid +{ + set .@inputmap$, getarg(0, ""); + set .@i, 0; + setarray .@maps1$, "001-1", "001-2", "001-3", "002-1", "002-2", "002-3", + "002-4", "002-5", "003-1", "003-4", "004-1", "004-3", + "004-4", "004-5", "005-3", "006-1", "006-2", "006-3", + "007-1", "007-2", "008-1", "009-1", "009-2", "009-3", + "009-4", "009-5", "009-6", "009-7", "009-8", "010-1", + "010-2", "011-1", "011-3", "011-4", "011-6", "012-1", + "012-3", "012-4", "013-1", "013-2", "013-3", "014-1", + "014-3", "015-1", "015-3", "016-1", "016-2", "017-1", + "017-2", "017-3", "017-4", "017-9", "018-1", "018-2", + "018-3", "019-1", "019-3", "019-4", "020-1", "020-2", + "020-3", "021-3", "023-1", "023-2", "023-3", "025-1", + "025-3", "025-4", "026-1", "026-2", "027-1", "027-2", + "027-3", "027-4", "027-5", "027-6", "027-7", "027-8", + "028-1", "028-3", "029-1", "029-2", "029-3", "029-4", + "030-1", "030-2", "030-3", "030-4", "031-1", "031-2", + "031-3", "031-4", "032-3", "033-1", "034-1", "034-2", + "035-2", "036-2", "041-1", "042-1", "043-1", "043-3", + "043-4", "045-1", "046-1", "046-3", "047-1", "047-3"; +// Had to split to 2 arrays as its too big for array initializer + setarray .@maps2$, "048-2", "051-1", "051-3", "052-1", "052-2", "055-1", + "055-3", "056-2", "057-1", "058-1", "058-2", "069-2", + "070-1", "070-3", "099-1", "099-2", "099-3", "099-4", + "099-5", "099-6", "099-7", "099-8", "botcheck"; + set .@arr_sz1, getarraysize(.@maps1$[0]); + set .@arr_sz2, getarraysize(.@maps2$[0]); + freeloop 1; // Needed to iterate over array of about 150 maps + goto L_NextMap; // Start iterating over array of maps. + +L_NextMap: + if (.@inputmap$ == .@maps1$[.@i]) goto L_Found; // Found map in arr 1? + if (.@inputmap$ == .@maps2$[.@i]) goto L_Found; // Found map in arr 2? + set .@i, (.@i + 1); // increment .@maps$[] index + if ((.@i >= .@arr_sz1) && (.@i >= .@arr_sz2)) goto L_NotFound; // Abort if whole arrays scanned + goto L_NextMap; // Try next map in .@maps$[] + +L_NotFound: + freeloop 0; + return 0; + +L_Found: + freeloop 0; + return 1; +} + +// PRIVATE: This function displays usage help for TeleportManager - @teleportadd +// This function should be invoked by TeleportManager with RID attached. +// Inputs: nothing. +// Return: nothing, just shows usage -> caller. +function|script|teleport_help_add +{ + message strcharinfo(0), "[TeleportManager] : @teleportadd <MAP> <X> <Y> <NPCNAME> <TIMEOUT> [sprite]"; + message strcharinfo(0), "[TeleportManager] : <MAP> <X> <Y>: teleport's destination map and coordinates"; + message strcharinfo(0), "[TeleportManager] : <NPCNAME>: name of NPC, unique and nonempty. If it stats with # its hidden"; + message strcharinfo(0), "[TeleportManager] : <TIMEOUT>: teleport lifetime (sec), < 2 000 000 sec, -1 = persistent"; + message strcharinfo(0), "[TeleportManager] : [sprite]: optional, NPC sprite (424,369,368,325 and 324 accepted)"; + message strcharinfo(0), "[TeleportManager] : Example: @teleportadd 009-1 52 39 Hurns -1 adds permanent teleport to Hurns menhir"; + message strcharinfo(0), "[TeleportManager] : Example: @teleportadd 009-1 52 39 Hurns 600 324 - same but 10 min, and red circle"; + return; +} + +// PRIVATE: This function displays usage help for TeleportManager - @teleportdel +// This function should be invoked by TeleportManager with RID attached. +// Inputs: nothing. +// Return: nothing, just shows usage -> caller. +function|script|teleport_help_del +{ + message strcharinfo(0), "[TeleportManager] : @teleportdel <ID>"; + message strcharinfo(0), "[TeleportManager] : ID either slot# (1..100) or teleport NPC ID"; + return; +} + +// Access checks for TeleportManager. Based on cut-down BossPowers checks. +function|script|teleport_access_check +{ + if ($BP_DISABLE) goto L_Killswitch; // If things go wrong, TeleportManager can be disabled. + if (#BP_DISABLE) goto L_Killswitch; // If someone abuses feature, there's _per-account_ DENY flag. + if (GM >= 40) goto L_Allowed; // GM >= 40 can use boss actions. + if (IS_EVENTER == 42) goto L_Allowed; // Trusted player(s) could be allowed to access Eventer "magic" +// if (debug) goto L_Allowed; // Allow on debug. + message strcharinfo(0), "[TeleportManager] : You can't use this feature at this time. Sorry. [1]"; + return 1; // Not allowed by default. + +L_Allowed: + return 0; // Whoever gets here allowed to invoke BossPowers spells + +L_Killswitch: + message strcharinfo(0), "[TeleportManager] : You can't use this feature at this time. Sorry. [2]"; + return 2; +} diff --git a/world/map/npc/functions/treasure_hunt.txt b/world/map/npc/functions/treasure_hunt.txt new file mode 100644 index 00000000..0e97adce --- /dev/null +++ b/world/map/npc/functions/treasure_hunt.txt @@ -0,0 +1,469 @@ +// Pirate Treasures Hunt quest (c) 2025 Hello=) + +// This NPC handles treasure placement +-|script|TreasureHunt|32767 +{ + end; + +OnTreasurePlace: + if (GM < 60) end; + gmlog strcharinfo(0) + " shuffled treasure placement"; + wgm strcharinfo(0) + " shuffled treasure placement"; + set $TREASURE_X, 0; // To force reinit + void call("treasure_place"); + end; + +OnInit: + registercmd "@treasure_place", "TreasureHunt::OnTreasurePlace"; + void call("treasure_place"); + end; +} + +// Applies defaults to global vars if unconfigured +function|script|treasure_place +{ + if ($TREASURE_MAP$ == "") || !($TREASURE_X) || !($TREASURE_Y) // Not looks configured? + goto L_Setup; + return; + +L_Setup: + // Apply defaults + set $TREASURE_REWARD, Eyepatch; // main reward + set $TREASURE_BOOTY, 200; // max amount of booty (worst case anti-milking cap) + set $TREASURE_DIFFICULTY, 100; // common rounds difficulty + set $TREASURE_DIFFICULTY2, 100; // extra rounds difficulty (if player found treasure) + set $TREASURE_MAP$, "003-4"; // Map to use. Ships are hardwired so far, beware. + set $TREASURE_X1, 97; // Rectangle + set $TREASURE_X2, 104; + set $TREASURE_Y1, 61; + set $TREASURE_Y2, 100; + // $TREASURE_DEBUG // Debug mode, a lot of battle flow/trace spam. + // $TREASURE_TURBO // Play battle real fast. You'll need @hide and GM Hat to survive. + goto L_TryPlacement; + +L_TryPlacement: + // Try placement + set $TREASURE_X, rand($TREASURE_X1, $TREASURE_X2); // Choose exact spot + set $TREASURE_Y, rand($TREASURE_Y1, $TREASURE_Y2); + if (iscollision($TREASURE_MAP$, $TREASURE_X, $TREASURE_Y)) + goto L_TryPlacement; // do not place trasure on collisions + debugmes "treasure_place: treasure placed and configured"; + return; +} + +// Handler to be invoked from item usage attempt of shovels. +// This function supposed to be called with player RID attached (its item use by player) +// Input: Arg0: shovel name from item use handler function. +// Return: 1 = item use handled (swallowed) by quest, 0 = not handled. +function|script|treasure_try_shovel +{ + set .@shovel$, getarg(0, ""); + set .@handled, 0; + if ($TREASURE_DEBUG) debugmes "treasure_try_shovel, shovel=" + .@shovel$; + if (.@shovel$ == "LegendaryShovel") set .@handled, 1; // TH handles everything about Legendary Shovel + if (!(.@handled) && (getmap() == $TREASURE_MAP$)) // Hint player they need different shovel on treasure map + message strcharinfo(0), "There're rocks in these sands. You'll need different shovel here..."; + if ($TREASURE_DEBUG) debugmes "treasure_try_shovel, .@handled=" + .@handled; + if (.@handled) addtimer 0, "TreasureDig::OnDig", BL_ID; // Request NPC to do its thing just like #treasuredig. + return .@handled; +} + +// Handler to be invoked from item usage attempt of maps. +// This function supposed to be called with player RID attached (its item use by player) +// Input: Arg0: map name from item use handler function. +// Return: 1 = item use handled (swallowed) by quest, 0 = not handled. +function|script|treasure_try_map +{ + set .@mapname$, getarg(0, ""); + if ($TREASURE_DEBUG) debugmes "treasure_try_map, map=" + .@mapname$; + set .@handled, 0; + if (.@mapname$ == "LegendaryTreasureMap") set .@handled, 1; // TH handles everything about Legendary Treasure Map + if ($TREASURE_DEBUG) debugmes "treasure_try_map, .@handled=" + .@handled; + if ((getmap() != $TREASURE_MAP$) && (.@handled)) + message strcharinfo(0), "Treasure Map : This place doesn't looks like one on map."; + if ((getmap() == $TREASURE_MAP$) && (.@handled) && + !(isin($TREASURE_MAP$, $TREASURE_X1, $TREASURE_Y1, $TREASURE_X2, $TREASURE_Y2))) + message strcharinfo(0), "Treasure Map : This place looks right! Yet [X] mark isn't where you stand."; + if ((getmap() == $TREASURE_MAP$) && (.@handled) && + (isin($TREASURE_MAP$, $TREASURE_X1, $TREASURE_Y1, $TREASURE_X2, $TREASURE_Y2))) + message strcharinfo(0), "Treasure Map : Aha! Right spot! Unfortunately X mark is big and crude. Promising area is " + + "(" + $TREASURE_X1 +"," + $TREASURE_Y1 + ")<->(" + $TREASURE_X2 + "," + $TREASURE_Y2 + ")"; + return .@handled; +} + +// This NPC handles traeasure dig. +-|script|TreasureDig|32767 +{ + end; + +OnDig: + if ($TREASURE_DISABLE) message strcharinfo(0), "Tough luck, seems there's no booty at this time!"; + if ($TREASURE_DISABLE) end; + set @treasureMobs, + mobcount($TREASURE_MAP$, "TreasureDigAux::OnMobKill") + + mobcount($TREASURE_MAP$, "TreasureDigAux::OnPirateKill"); + if ($TREASURE_DEBUG) debugmes "OnDig: mob count = " + @treasureMobs; + if (@treasureMobs > 300) message strcharinfo(0), "Its too crowded here to dig! Perhaps kill some mob first?"; + if (@treasureMobs > 300) end; // Do not let users spam server by mobs indefinitely + set .@puppet$, "#"+strnpcinfo(0)+"#"+BL_ID; + set .@puppet, puppet(getmap(), POS_X, POS_Y, .@puppet$, 127); + if (.@puppet < 1) end; // It also denies player launching N instances at once. + // Set up instance of treasure hunt attempt + set .digger, BL_ID, .@puppet; + set .diggernm$, strcharinfo(0), .@puppet; + set .delay, 1000, .@puppet; // Initial round delay + set .tiles, TILES_WALKED, .@puppet; + if ($TREASURE_DEBUG) debugmes "OnDig: starting"; + npctalk .@puppet$, "Digging : " + strcharinfo(0) + " starts digging in hope to find something.."; + if ($TREASURE_MAP$ == getmap()) + addnpctimer 4000, .@puppet$+"::OnDiggingRound"; // Start digging rounds. + else + addnpctimer 12000, .@puppet$+"::OnDiggingRound"; // Start digging rounds - fake - slow 'em a bit. + addnpctimer 700000, .@puppet$+"::OnDestroy"; // Failsafe quest shutdown on timeout + end; + +OnDiggingRound: + set .dig_round, .dig_round + 1; + if ($TREASURE_DEBUG) debugmes "OnDiggingRound: my name=" + strnpcinfo(0) + " digger=" + .digger; + if ($TREASURE_DEBUG) debugmes "OnDiggingRound: PLAYER: POSX=" + get(POS_X, .digger) + " POSY=" + get(POS_Y, .digger) + " MAP=" + getmap(.digger) + " TILES=" + get(TILES_WALKED, .digger); + if ($TREASURE_DEBUG) debugmes "OnDiggingRound: NPC: POSX=" + getnpcx() + " POSY=" + getnpcy() + " MAP=" + strnpcinfo(3) + " TILES=" + .tiles; + if (!(isloggedin(.digger))) set .failed, 2; // Treasure digger disappeared? + if (get(Hp, .digger) < 1) set .failed, 3; // Digger digger died? + if ((getmap(.digger) != strnpcinfo(3)) || // Digger left map? + (get(POS_X, .digger) != getnpcx()) || + (get(POS_Y, .digger) != getnpcy()) || + (get(TILES_WALKED, .digger) != .tiles)) + set .failed, 4; // Has digger moved? + if ($TREASURE_MAP$ != strnpcinfo(3)) set .failed, 9; // Trying to dig on wrong map? Side effects avoidance. + if ($TREASURE_DEBUG) debugmes "OnDiggingRound: failed1=" + .failed; + if (.failed) goto L_DiggFail; + // Basic checks ok -> next round starts + misceffect FX_CHANNELLING_RAISE_RED, strcharinfo(0, .digger); + set .failed, call("treasure_dig_round", .dig_round); // core function of digging + if ($TREASURE_DEBUG) debugmes "OnDiggingRound: failed2=" + .failed; + if !($TREASURE_TURBO) set .delay, .delay + 1000; // Turbo == "skip AI moves" for test/debug: FAST battle, @hide+gm map to observe + if ((.failed == 9) && call("treasure_is_here") && ($TREASURE_REWARD != 0)) + goto L_DiggFound; // MaxRound && Found treasure?! Its WIN, not .fail :) + if ((.failed == 9) && call("treasure_is_here") && ($TREASURE_REWARD == 0)) + set .failed, 10; // Found it - but someone got there first? How unfortunate. + if (.failed) goto L_DiggFail; // catches e.g. (running to max round && no treasure) + else addnpctimer .delay, strnpcinfo(0)+"::OnDiggingRound"; + end; + +// Invoked when digging failed for any reason, by both OnDiggingRound and OnBootyRound. +L_DiggFail: + if ($TREASURE_DEBUG) debugmes "TreasureDig: digging done, .failed=" + .failed; + if (.failed == 2) npctalk strnpcinfo(0), "Digging : ##3##BDigger " + .diggernm$ + " disappeared... strange..."; + elif (.failed == 3) npctalk strnpcinfo(0), "Digging : ##3##BDigger " + .diggernm$ + " haven't made it, digging site collapsed"; + elif (.failed == 4) npctalk strnpcinfo(0), "Digging : ##3##BDigger " + .diggernm$ + " has lost focus and digging site collapsed"; + elif (.failed == 9) npctalk strnpcinfo(0), "Digging : ##3##BDoh! Seems there's no treasure on this spot!"; + elif (.failed == 10) npctalk strnpcinfo(0), "Digging : ##3##BDoh! Someone already digged treasure out!"; + else /*error, etc*/ npctalk strnpcinfo(0), "Digging : ##3##BDigger " + .diggernm$ + " something went wrong, digging site collapsed"; + destroy; + +L_DiggFound: + if !($TREASURE_TURBO) set .delay, 16000; // Configure booty rounds + else set .delay, 3000; + set .failed, 0; + set .round, 1; + monster $TREASURE_MAP$, 94, 74, "Pirates!", MontBlanc, 1, "TreasureDigAux::OnPirateKill"; // FIXME hardwired thing + monster $TREASURE_MAP$, 95, 66, "Pirates!", MontBlanc, 1, "TreasureDigAux::OnPirateKill"; + monster $TREASURE_MAP$, 97, 91, "Pirates!", MontBlanc, 1, "TreasureDigAux::OnPirateKill"; + mapannounce strnpcinfo(3), "Emo : WHAT DO WE HAVE HERE?! Contenders for OUR booty?! Pirates! Kill them all! YARRR!!", 0; + addnpctimer .delay, strnpcinfo(0)+"::OnBootyRound"; // Hand over -> OnBootyRound but do not destroy puppet. + end; + +// Invoked when digger survived initial digging && found treasure. Called when OnDiggRound rounds end. +OnBootyRound: + if ($TREASURE_DEBUG) debugmes "OnBootyRound ->"; + if (!(isloggedin(.digger))) set .failed, 2; // Treasure digger disappeared? + if (get(Hp, .digger) < 1) set .failed, 3; // Digger digger died? + if ((getmap(.digger) != strnpcinfo(3)) || // Digger left map? + (get(POS_X, .digger) != getnpcx()) || + (get(POS_Y, .digger) != getnpcy()) || + (get(TILES_WALKED, .digger) != .tiles)) + set .failed, 4; // Has digger moved? + if (.failed) goto L_DiggFail; // Reuse digg failure handling above. + if (.round > 8) goto L_Finalize; + npctalk strnpcinfo(0), "Digging : ##3##B [TREASURE] [" + .round + "] "+ .diggernm$ + " found treasure, keep digger alive to collect collateral!"; + if ($TREASURE_BOOTY > 0) + addtimer 0, "TreasureDigAux::OnCollateral10", .digger; // Collateral for supporters + set $TREASURE_BOOTY, $TREASURE_BOOTY - 10; // anti-milking cap, total ~20 rounds with booty top (configurable) + set .@round_power, call("treasure_estimate_team"); // get team level + if ($TREASURE_DEBUG) debugmes "treasure_dig_round: round power B0=" + .@round_power; + set .@round_power, (.@round_power * $TREASURE_DIFFICULTY2) / 170 + (.round / 7) + rand(3); // Adjust & randomize + if ($TREASURE_DEBUG) debugmes "treasure_dig_round: round power B1=" + .@round_power; + if (.round == 1) set .@mob, Emo; // Guy who yelled + else set .@mob, Ratto; // His ship ratto, also treasure contender + void call("spawn_in_radius", strnpcinfo(3), getnpcx(), getnpcy(), 2, .@mob, 1, "TreasureDigAux::OnPirateKill"); + void call("spawn_in_radius", strnpcinfo(3), getnpcx(), getnpcy(), 4, Swashbuckler, .@round_power, "TreasureDigAux::OnPirateKill"); + void call("spawn_in_radius", strnpcinfo(3), getnpcx(), getnpcy(), 4, Grenadier, .@round_power, "TreasureDigAux::OnPirateKill"); + void call("spawn_in_radius", strnpcinfo(3), getnpcx(), getnpcy(), 4, Thug, .@round_power, "TreasureDigAux::OnPirateKill"); + void call("spawn_in_radius", strnpcinfo(3), getnpcx(), getnpcy(), 4, Ratto, 5, "TreasureDigAux::OnPirateKill"); + set .round, .round + 1; + addnpctimer .delay, strnpcinfo(0)+"::OnBootyRound"; // Hand over -> OnBootyRound but do not destroy puppet. + end; + +L_Finalize: + addtimer 0, "TreasureDigAux::OnMainBooty", .digger; // Hand over to final reward handler. + mapannounce strnpcinfo(3), "Treasure : Digger " + .diggernm$ + " found treasure!", 0; + npctalk strnpcinfo(0), "Digging : ##3##B [TREASURE] Victory! Treasure hunter " + .diggernm$ + " got booty!"; + destroy; + +OnDestroy: + debugmes "TreasureDig: GLOBAL TIME OUT, this shouldn't happen"; // Failsafe logic + npctalk strnpcinfo(0), "Digging : ##3##BSomething went wrong, your digging site has collapsed"; + destroy; + +OnInit: + end; +} + +// This function only meant to be called by TreasureDig's puppet. +// Main digging round handling logic happens here, spawns + dig fallouts +// Input: Arg0: round # +// Arg1: players str +// Returns: 0 if all ok, 1 = failure, 9 = max round reached. +function|script|treasure_dig_round +{ + set .@round, getarg(0, -1); + set .@res, 0; // > 0 halts next rounds, 1 = failure, 9 = "failed to find treasure" + set .@rounds0, 2; // # of rounds configuration. + set .@rounds1, 6; + set .@rounds2, 12; + set .@rounds3, 18; + set .@rounds4, 22; + if ($TREASURE_DEBUG) debugmes "treasure_digg_round: round # " +.@round + " r4=" + .@rounds4; + if (.@round < 1) goto L_Failed; + if ((.@round >= .@rounds4)) goto L_Done; + setarray .@mobs_0[0], CaveMaggot, HouseMaggot, VoidMaggot, Ratto; // Mobs for round spawns + setarray .@mobs_1[0], AngryScorpion, Hyvern, Snake, Spider, Archant, RedSlime, AngrySeaSlime, AngryGreenSlime; + setarray .@mobs_2[0], VoidSnake, MountainSnake, GrassSnake, SoulSnake, BlackScorpion, CrotcherScorpion, VoidBat, HuntsmanSpider; + setarray .@mobs_3[0], Skeleton, LadySkeleton, Wight, RedBone, SoulEater; + setarray .@mobs_4[0], Thug, Swashbuckler, Grenadier; + if (.@round <= .@rounds0) set .@mobID, .@mobs_0[rand(getarraysize(.@mobs_0))]; // Pick random mob for i-th round + elif (.@round <= .@rounds1) set .@mobID, .@mobs_1[rand(getarraysize(.@mobs_1))]; // Make mobs progressiveky harder + elif (.@round <= .@rounds2) set .@mobID, .@mobs_2[rand(getarraysize(.@mobs_2))]; + elif (.@round <= .@rounds3) set .@mobID, .@mobs_3[rand(getarraysize(.@mobs_3))]; + elif (.@round <= .@rounds4) set .@mobID, .@mobs_4[rand(getarraysize(.@mobs_4))]; + else set .@mobID, MobMoubootaur; // Failsafe :) + set .@rand, rand(1, 100); // Does digging site blows up, releases poison or stays stable? + if (.@rand < 22) set .@dig_action$, "treasure_dig_poison"; + elif (.@rand > 78) set .@dig_action$, "treasure_dig_blowup"; + else /* 30..70 */ set .@dig_action$, "treasure_dig_stable"; + if ($TREASURE_DEBUG) debugmes "treasure_digg_round: round # " + .dig_round + " .@rand=" + .@rand + " .@dig_action$=" + .@dig_action$; + void call(.@dig_action$, .dig_round); // Avoids goto spaghetti but chosen func MUST exist, or server WILL crash! + set .@round_power, call("treasure_estimate_team"); // get team level + if ($TREASURE_DEBUG) debugmes "treasure_dig_round: round power0=" + .@round_power; + set .@round_power, (.@round_power * $TREASURE_DIFFICULTY) / 130 + (.@round / 7) + rand(3); // Adjust & randomize + if ($TREASURE_DEBUG) debugmes "treasure_dig_round: round power1=" + .@round_power; + addtimer 0, "TreasureDigAux::OnCollateral1", .digger; + void call("spawn_in_radius", strnpcinfo(3), getnpcx(), getnpcy(), 4, .@mobID, .@round_power, "TreasureDigAux::OnMobKill"); + // Show some fancy messages + if (.@round <= .@rounds2) npctalk strnpcinfo(0), "Digging : [" +.@round + "] " + .diggernm$ + " hit monster nest!"; + elif (.@round <= .@rounds3) npctalk strnpcinfo(0), "Digging : [" +.@round + "] " + .diggernm$ + " Yuck! Skeleton I found moves!"; + elif (.@round <= .@rounds4) npctalk strnpcinfo(0), "Digging : [" +.@round + "] " + .diggernm$ + " pirates got curious what this noise all about"; + else npctalk strnpcinfo(0), "Digging : [" +.@round + "] " + .diggernm$ + " ?!?!?!"; + return 0; + +L_Done: + if ($TREASURE_DEBUG) debugmes "treasure_digg_round: rounds done"; + return 9; // Inform quest max rounds reached and no treasure been found. + +L_Failed: + debugmes "treasure_digg_round: call failed, arg0=" + getarg(0) + "arg1=" + getarg(1); + return 1; +} + +// This spawns mobs in a given radius. Fallbacks to spot it it not fits map. +// This function can be called in any context. +// Input: Arg0: map where to spawn +// Arg1: X +// Arg2: Y +// Arg3: Radius +// Arg4: Mob id +// Arg5: # of mobs +// Arg6: Event for mob death (must be given, even as "") +function|script|spawn_in_radius +{ + set .@map$, getarg(0, ""); + set .@x, getarg(1, -1); + set .@y, getarg(2, -1); + set .@r, getarg(3, -1); + set .@mob, getarg(4, -1); + set .@qty, getarg(5, -1); + set .@evt$, getarg(6, ""); + // Args check. + if ((.@map$ == "") || (.@evt$ == "6") || (.@mob < 1) || + (.@x < 1) || (.@x > getmapmaxx(.@map$)) || (.@r < 1) || (.@qty < 1) || + (.@y < 1) || (.@y > getmapmaxy(.@map$))) + goto L_Fail; + // Does (x-r, y-r, x+r, y+r) rectangle fits map? + if ((.@x > .@r) && (.@y > .@r) && + ((.@x + .@r) < getmapmaxx(.@map$)) && + ((.@y + .@r) < getmapmaxy(.@map$))) //Enough room? Use area. + areamonster .@map$, (.@x-.@r), (.@y-.@r), (.@x+.@r), .@y+.@r, "", .@mob, .@qty, .@evt$; + else // Rectangle does not fits, fallback + monster .@map$, .@x, .@y, "", .@mob, .@qty, .evt$; + return; + +L_Fail: + debugmes "spawn_in_radius: call failed, arg0=" + getarg(0) + " arg1=" + getarg(1) + + " arg2=" + getarg(2) + " arg3=" + getarg(3) + " arg4=" + getarg(4) + + " arg5=" + getarg(5)+ " arg6=" + getarg(6); + return; +} + +// This function meant to be run in context of TreasureDig's *puppet* only +function|script|treasure_dig_poison +{ + if ($TREASURE_DEBUG) debugmes "TreasureDigg: dig_poison"; + npctalk strnpcinfo(0), "Digging : ["+getarg(0)+"] attempt to dig released poisonous gas!"; // Next throws events in digger (player) context + foreach 0, strnpcinfo(3), (getnpcx() - 7), (getnpcy() - 7), (getnpcx() + 7), (getnpcy() + 7), "TreasureDigAux::OnPoisonousGasPlayer", .digger; + foreach 2, strnpcinfo(3), (getnpcx() - 4), (getnpcy() - 4), (getnpcx() + 4), (getnpcy() + 4), "TreasureDigAux::OnPoisonousGasMob", .digger; + return; +} + +// This function meant to be run in context of TreasureDig's *puppet* only +function|script|treasure_dig_blowup +{ + if ($TREASURE_DEBUG) debugmes "TreasureDigg: dig_blowup"; + npctalk strnpcinfo(0), "Digging : ["+getarg(0)+"] underground gas bubble blows up!"; // Next throws events in digger (player) context + foreach 0, strnpcinfo(3), (getnpcx() - 7), (getnpcy() - 7), (getnpcx() + 7), (getnpcy() + 7), "TreasureDigAux::OnBlowUpPlayer", .digger; + foreach 2, strnpcinfo(3), (getnpcx() - 4), (getnpcy() - 4), (getnpcx() + 4), (getnpcy() + 4), "TreasureDigAux::OnBlowUpMob", .digger; + return; +} + +// This function meant to be run in context of TreasureDig's *puppet* only +function|script|treasure_dig_stable +{ + if ($TREASURE_DEBUG) debugmes "TreasureDigg: dig_stable"; + return; +} + +// This function meant to be run in context of TreasureDig's *puppet* only +function|script|treasure_is_here +{ + if ($TREASURE_DEBUG) debugmes "treasure_is_here ->"; + set .@res, 0; + if (($TREASURE_MAP$ == strnpcinfo(3)) && + ($TREASURE_X == getnpcx()) && + ($TREASURE_Y == getnpcy())) + set .@res, 1; + if ($TREASURE_DEBUG) debugmes "treasure_is_here <- .@res=" + .@res; + return .@res; +} + +// This function meant to be run in context of TreasureDig's *puppet* only +function|script|treasure_estimate_team +{ + if ($TREASURE_DEBUG) debugmes "treasure_estimate_team: ->"; + set @treasure_estimate_team, 1, .digger; // Prepare digger's context + if ($TREASURE_DEBUG) debugmes "team_est0 = " + get(@treasure_estimate_team, .digger); // This throws events in digger's context + foreach 0, strnpcinfo(3), (getnpcx() - 7), (getnpcy() - 7), (getnpcx() + 7), (getnpcy() + 7), "TreasureDigAux::OnPlayerEstimate", .digger; + if ($TREASURE_DEBUG) debugmes "team_est1 = " + get(@treasure_estimate_team, .digger); // This throws events in digger's context + return get(@treasure_estimate_team, .digger); +} + +// This NPC handles aux things like poisoning, blow up, statuses cleanup, items placement, ... +-|script|TreasureDigAux|32767 +{ + end; + +// This cleans players statues like leftovers of poison or blowup stunning. +// Invoked by timer set by site blowup/poison gas handlers +OnPlayerStatusCleanup: + if ($TREASURE_DEBUG) debugmes "TreasureDigAux: status cleanup, BL ID=" + BL_ID; + if (sc_check(SC_POISON)) sc_end SC_POISON; + if (sc_check(SC_SLOWMOVE)) sc_end SC_SLOWMOVE; + if (sc_check(SC_HALT_REGENERATE)) sc_end SC_HALT_REGENERATE; + end; + +// Dig site poisonous gas - invoked per player (hurts players) +OnPoisonousGasPlayer: +// debugmes "TreasureDigAux: poison/player, target ID=" + @target_id; + misceffect FX_EMOTE_DISGUST, strcharinfo(0, @target_id); + sc_start SC_POISON, 1, 60, @target_id; // Poison player + sc_start SC_HALT_REGENERATE, 5000, 0; // Even if fails, at least halt regen. + addtimer 5000, "TreasureDigAux::OnPlayerStatusCleanup"; // clean PC statuses + end; + +// Dig site poisonous gas - invoked per mob (aggravates mobs) +OnPoisonousGasMob: +// debugmes "TreasureDigAux: poison/mob, target ID=" + @target_id; + set .@type, get(Class, @target_id); // Dont poison undead + if ((.@type != Skeleton) && (.@type != LadySkeleton) && (.@type != Wight) && + (.@type != RedBone) && (.@type != SoulEater)) + sc_start SC_POISON, 1, 10, @target_id; // Poison MOBS, too. + aggravate @target_id; // This aggravates mobs. + end; + +// Dig site blow up - invoked per player (hurts players) +OnBlowUpPlayer: +// debugmes "TreasureDigAux: blowup/player, target ID=" + @target_id; + misceffect FX_MEDIUM_SMOKE, strcharinfo(0, @target_id); // Show slow effect + sc_start SC_SLOWMOVE, 3000, 300, @target_id; // Slow player temporarily + sc_start SC_HALT_REGENERATE, 5000, 0; // Stop regen temporarily + set Hp, (get(Hp, @target_id) * 2 / 3), @target_id; // Yes explosion hurts. + addtimer 5000, "TreasureDigAux::OnPlayerStatusCleanup"; // clean statuses + end; + +// Dig site blow up - invoked per mob (aggravates mobs) +OnBlowUpMob: +// debugmes "TreasureDigAux: blowup/mob, target ID=" + @target_id; + injure BL_ID, @target_id, get(Hp, @target_id) / 3; // Yes, explosion can hurt mobs, too! + aggravate @target_id; // This aggravates them! + end; + +// Event thrown when quest mobs die. +OnMobKill: + if ($TREASURE_DEBUG) debugmes "TreasureDigAux: mob killed, @mobID=" + @mobID; + end; + +OnPirateKill: + if ($TREASURE_DEBUG) debugmes "TreasureDigAux: pirate killed, @mobID=" + @mobID; + end; + +// Event invoked by team estimation function. +OnPlayerEstimate: + if (get(Hp, @target_id) > 0) set @treasure_estimate_team, @treasure_estimate_team + (get(BaseLevel, @target_id) / 10); + if ($TREASURE_DEBUG) debugmes "TreasureDigAux: player_estimate: @treasure_estimate_team=" + @treasure_estimate_team; + end; + +// Emit few collateral items spawner +OnCollateral1: + if ($TREASURE_DEBUG) debugmes "OnCollateral1"; + setarray .@items1[0], SulphurPowder, IronPowder, BlackScorpionStinger, TreasureKey, Bone, Skull; + set .@itemID, .@items1[rand(getarraysize(.@items1))]; // random collateral + if ($TREASURE_DEBUG) debugmes "OnCollateral1 .@itemID=" + .@itemID; + makeitem .@itemID, 1, getmap(), rand(POS_X-2,POS_X+2), rand(POS_Y-2,POS_Y+2); + end; + +// Emit plenty of collateral items spawner (treasure reward mode) +OnCollateral10: + if ($TREASURE_DEBUG) debugmes "OnCollateral10"; + setarray .@items10[0], Pearl, Sapphire, Amethyst, GoldenTooth, GoldOre, CoinBag; + set .@count, 0; + goto L_RandomItems; + +L_RandomItems: + set .@itemID, .@items10[rand(getarraysize(.@items10))]; // random collateral + makeitem .@itemID, 4+rand(16), getmap(), rand(POS_X-3,POS_X+3), rand(POS_Y-3,POS_Y+3); + set .@count, .@count + 1; + if (.@count < 25) goto L_RandomItems; + end; + +// Hand over rewards + extra "collateral" +// This handler invoked on digger who initiated digging session and won. +OnMainBooty: + addtimer 0, "TreasureDigAux::OnCollateral10"; + gmlog strcharinfo(0) + " found treasure!"; + wgm strcharinfo(0) + " found treasure!"; + getitem $TREASURE_REWARD, 1; + message strcharinfo(0), "Treasure Hunt : Success! You found [" + getitemlink($TREASURE_REWARD) + "]"; + set $TREASURE_REWARD, 0; // Deny re-runs, treasure acquired + end; + +OnInit: + end; +} |