summaryrefslogtreecommitdiff
path: root/world/map
diff options
context:
space:
mode:
authorHello=) <hello@themanaworld.org>2024-10-26 01:30:56 +0300
committerLed Mitz <smoothshifter@tuta.io>2024-10-30 16:49:48 +0000
commita2460e6f19972b8815186e50b5e1eacad99a6772 (patch)
treee6796b04c0cf18fa8d510c50084a1e118470abf3 /world/map
parentab666be35fa773e3ebddb4fff1033104592ce856 (diff)
downloadserverdata-a2460e6f19972b8815186e50b5e1eacad99a6772.tar.gz
serverdata-a2460e6f19972b8815186e50b5e1eacad99a6772.tar.bz2
serverdata-a2460e6f19972b8815186e50b5e1eacad99a6772.tar.xz
serverdata-a2460e6f19972b8815186e50b5e1eacad99a6772.zip
Next-Gen teleports system and Gate Building Toolkit for TMW
Its attempt to get best of @addwarp, GM Island teleport and Illia teleports - making visually OKish teleports with desirable properties. On user visible side this allows to open nice looking teleports into arbitrary destinations, that do FX effect upon player stepping on them. But its more. * 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. * There's way to "globally" track all "tracked" teleports. * Up to 100 "tracked" teleports can coexist - and this list can be displayed or managed. * 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. (for safety reasons @teleportadd allows only few NPC sprites and few params) * 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) * Attempts to reasonably validate input and refuses to do apparently invalid things like landing on collisions or bad map/coordinates. * Builtin help. Overall it meant to * Play events with better FX effects on arbitrary maps, including e.g. chaining inaccessible ones. * Keep track of all open "managed" gateways - being able to manage all established wormholes. * Reusable component (function) doing teleport setup - and overall quite configurable thing via NPC vars, reusable from e.g. spells or various quests. So quest or spell can open e.g. transient reasonably looking self-destroying teleport easily, etc (these are not tracked and would not appear in @teleportlist but can in principle be force-closed via @teleportdel if caller knows NPC ID) To get started: type @teleport or @teleporthelp. @teleportadd will also do.
Diffstat (limited to 'world/map')
-rw-r--r--world/map/npc/functions/teleport_manager.txt589
-rw-r--r--world/map/npc/scripts.conf1
2 files changed, 590 insertions, 0 deletions
diff --git a/world/map/npc/functions/teleport_manager.txt b/world/map/npc/functions/teleport_manager.txt
new file mode 100644
index 00000000..6ef24d5b
--- /dev/null
+++ b/world/map/npc/functions/teleport_manager.txt
@@ -0,0 +1,589 @@
+// GateBuilders toolkit aka "arbitrary teleports".
+// 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:
+ debugmes "OnAdd called";
+ 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 + ")";
+ debugmes "OnAdd: npc="+.@tp+" idx="+.@idx;
+ 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:
+ debugmes "OnDel called";
+ 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:
+ debugmes "OnList called";
+ 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:
+ debugmes "OnHelp called";
+ 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:
+ debugmes "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+100, 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:
+ debugmes "OnTeleport -> " + .dstmap$ + " X=" + .dst_x + " Y=" + .dst_y;
+ warp .dstmap$, .dst_x, .dst_y;
+ end;
+
+// Invoked on timed teleport's NPC timer expired.
+OnTeleportExpired:
+ debugmes "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:
+ debugmes "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
+{
+ debugmes "teleport_create -> Enter";
+ 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";
+ debugmes "teleport_create <- Puppet: Map="+.@map$+" X="+.@x+" Y="+.@y+" Nm="+.@name$+" SpID="+.@sprite+" xsz="+.@xsz+" ysz="+.@ysz+" .@res="+.@res;
+ return .@res;
+
+L_Fail:
+ debugmes "arg0:"+getarg(0)+" arg1:"+getarg(1)+" arg2:"+getarg(2)+" arg3:"+getarg(3)+" arg4:"+getarg(4)+" arg5:"+getarg(5)+" arg6:"+getarg(6)+" arg7:"+getarg(7)+" arg8:"+getarg(8)+" arg9:"+getarg(9)+" arg10:"+getarg(10)+" arg11:"+getarg(11)+" arg12:"+getarg(12);
+ debugmes "teleport_create <- Leave, call failed ["+.@res+"]";
+ 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
+{
+ debugmes "teleports_delete -> enter";
+ 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:
+ debugmes "teleports_delete <- leave, failed";
+ 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
+{
+ debugmes "teleports_list -> enter";
+ 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);
+ debugmes "teleport_list idx=" + .@idx + " val=" + .@npc;
+ 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
+ debugmes "teleport_list: after managed check";
+ // 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 ----";
+ debugmes "teleports_list <- Leave, ok";
+ return;
+
+L_Error:
+ freeloop 0;
+ debugmes "teleports_list <- Leave, error";
+ 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
+{
+ debugmes "teleport_find_slot -> Enter";
+ 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);
+ debugmes "teleport_find_slot: @i="+.@i+" npcid="+.@npcid;
+ 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:
+ debugmes "teleport_find_slot <- Leave, found slot .@i="+.@i;
+ freeloop 0;
+ return .@i;
+
+L_Fail:
+ debugmes "teleport_find_slot <- Leave, call failed";
+ 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
+{
+ debugmes "teleport_get_slot_val -> Enter";
+ 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);
+ debugmes "teleport_get_slot_val <- Leave, ok";
+ return .@ret;
+
+L_Fail:
+ debugmes "teleport_get_slot_val <- Leave, failed";
+ 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
+{
+ debugmes "teleport_set_slot_val -> Enter";
+ 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;
+ debugmes "teleport_set_slot_val <- Leave, ok";
+ return 1;
+
+L_Fail:
+ debugmes "teleport_set_slot_val <- Leave, failed";
+ 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 "OnTeleportShutdown: TeleportManaer cleanup failure -> will leak slot. Likely bug!";
+ 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
+{
+ debugmes "teleportadd_parseargs -> Enter";
+ // FIXME check arg presence$ if ()
+ callfunc "argv_splitter";
+ debugmes "teleportadd_parseargs @argv$[0]"+@argv$[0]+" @argv[1]"+@argv[1]+" @argv[2]"+@argv[2]+" @argv$[3]"+@argv$[3]+" @argv[4]"+@argv[4]+" @argv[5]"+@argv[5];
+ 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)
+ debugmes "teleportadd_parseargs 1 DSTMAP$="+@DSTMAP$+" @DST_X="+@DST_X+" @DST_Y="+@DST_Y+" @NAME$="+@NAME$+" @TIMEOUT="+@TIMEOUT+" @NPCSPRITE="+@NPCSPRITE;
+ // 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
+ debugmes "teleportadd_parseargs 2 DSTMAP$="+@DSTMAP$+" @DST_X="+@DST_X+" @DST_Y="+@DST_Y+" @NAME$="+@NAME$+" @TIMEOUT="+@TIMEOUT+" @NPCSPRITE="+@NPCSPRITE;
+ return 1; // Everything OK
+
+L_FailBadmap:
+ message strcharinfo(0), "[TeleportManager] : @teleportadd: unknown destination map:" + @DSTMAP$;
+ debugmes "teleportadd_parseargs <- Leave, fail Badmap";
+ return -1;
+
+L_FailBadDstXY1:
+ message strcharinfo(0), "[TeleportManager] : @teleportadd: destination X,Y must be > 0! Given X=" + @DST_X + " Y=" + @DST_Y;
+ debugmes "teleportadd_parseargs <- Leave, fail BadDstXY1";
+ return -2;
+
+L_FailBadDstXY2:
+ message strcharinfo(0), "[TeleportManager] : @teleportadd: destination X,Y outside of map! Given X=" + @DST_X+ " Y=" + @DST_Y;
+ debugmes "teleportadd_parseargs <- Leave, fail BadDstXY2";
+ return -3;
+
+L_FailDstCollide:
+ message strcharinfo(0), "[TeleportManager] : @teleportadd: destination MAP=" + @DSTMAP$ + " X=" + @DST_X + " Y=" + @DST_Y + " is a collision (impassable)";
+ debugmes "teleportadd_parseargs <- Leave, fail DstCollide";
+ return -4;
+
+L_FailNPCName:
+ message strcharinfo(0), "[TeleportManager] : @teleportadd: NPCNAME can't be empty!";
+ debugmes "teleportadd_parseargs <- Leave, fail NPCName";
+ return -5;
+
+L_FailTimeout:
+ message strcharinfo(0), "[TeleportManager] : @teleportadd: timeout must be either -1, or > 0 and < 2000000 (seconds)";
+ debugmes "teleportadd_parseargs <- Leave, fail NPCName";
+ 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/scripts.conf b/world/map/npc/scripts.conf
index 448e0947..7fbe10b4 100644
--- a/world/map/npc/scripts.conf
+++ b/world/map/npc/scripts.conf
@@ -36,6 +36,7 @@ npc: npc/functions/motdconfig.txt
npc: npc/functions/ghost.txt
npc: npc/functions/vault.txt
npc: npc/functions/global_event_handler.txt
+npc: npc/functions/teleport_manager.txt
// Item Functions
npc: npc/items/purification_potion.txt