// Evol functions.
// Authors:
// 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(0), 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 > 0)
{
.@x = getvariableofnpc(.movepos_x1[.@pos], strnpcinfo(3));
.@y = getvariableofnpc(.movepos_y1[.@pos], strnpcinfo(3));
if (getstrlen(.@map$) > 0)
unitwarp getnpcid(0), .@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;
npctalk 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;
}