// 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()) { debugmes "findmovegraphlabel: no argument"; return -1; } if (!isstr(getarg(0))) { debugmes "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 { debugmes "execmovecmd: unknown WARP destination label: " + .@cmd$[1]; } } else if (.@cmd$[0] == "call") { switch (getarraysize(.@cmd$)) { case 1: debugmes "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; .@msg$=implode(.@cmd$, " "); if (.@msg$ != "" && .@msg$ != " ") npctalk .@msg$; else debugmes "Invalid message passed to execmovecmd/npctalk"; } else if (.@cmd$[0] == "debugmes") { deletearray .@cmd$[0], 1; debugmes 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 { debugmes "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) { debugmes "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 debugmes "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) { debugmes "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; }