Some updates I made to it when I used it on iClassic:
this.can_stop - boolean - set to true if you want to 'stop' the npc mid-movement.
npc_stop(); - stops the npc.
npc_continue(); - starts the npc back up again.
"emote:image:time" - new action to make the npc do an emote for a specified time (3 if not specified)
"levelvar:var:value" - sets a level variable to value
PHP Code:
//#CLIENTSIDE
function onCreated() {
if (!this.ani_idle) this.ani_idle = "idle";
if (!this.ani_walk) this.ani_walk = "walk";
if (!this.speed) this.speed = 6; //Tiles Per Second.
if (this.can_stop) {
// Breaks up movement actions to allow NPC to
// stop properly.
this.actions = parseMovement(this.actions);
temp.subactions = getstringkeys("this.action_");
for (temp.subaction: temp.subactions) {
this.("action_" @ temp.subaction) = parseMovement(this.("action_" @ temp.subaction));
}
}
this.initxy = {
this.x, this.y
};
this.actionpos = 0;
doAction(0);
}
function parseMovement(old_actions) {
temp.new_actions = {};
for (temp.action: old_actions) {
temp.data = temp.action.tokenize(":");
switch (data[0]) {
case "move":
temp.new_actions.addarray(calcMoves(data[1], data[2]));
break;
case "moveto":
temp.movex = data[1] - (this.x % 64);
temp.movey = data[2] - (this.y % 64);
temp.new_actions.addarray(calcMoves(movex, movey));
break;
default:
temp.new_actions.add(temp.action);
break;
}
}
return temp.new_actions;
}
function calcMoves(dx, dy) {
temp.distance = GetDistance(this.x, this.y, this.x + dx, this.y + dy);
temp.time = distance / this.speed;
temp.frames = temp.time / 0.05;
temp.ndx = dx * (1 / temp.frames);
temp.ndy = dy * (1 / temp.frames);
temp.nmove = format("move:%s:%s", temp.ndx, temp.ndy);
for (temp.i = 0; temp.i < temp.frames; temp.i++) {
temp.moves.add(temp.nmove);
}
return temp.moves;
}
function doAction(ind) {
if (ind == this.actions.size()) return;
if (this.is_stopped || this.waiting) return;
temp.action = this.actions[ind];
temp.data = action.tokenize(":");
if (data[0].starts("!")) {
this.actions.delete(ind);
this.actionpos--;
data[0] = data[0].substring(1);
}
switch (data[0]) {
case "move":
doMove(data[1], data[2]);
break;
case "moveto":
temp.movex = data[1] - (this.x % 64);
temp.movey = data[2] - (this.y % 64);
doMove(movex, movey);
break;
case "reversemove":
this.reversing = true;
this.reversepos = this.moves.size();
onMovementFinished();
break;
case "action":
temp.act = this.("action_" @ data[1]);
for (temp.action: act) {
this.actions.insert(this.actionpos+1+temp.i, "!" @ action);
temp.i++;
}
onNextStep();
break;
case "randomaction":
if (random(0,1) <= data[1]) {
temp.act = this.("action_" @ data[2]);
for (temp.action: act) {
this.actions.insert(this.actionpos+1+temp.i, "!" @ action);
temp.i++;
}
}
onNextStep();
break;
case "setdir":
this.dir = data[1];
onNextStep();
break;
case "setani":
setcharani(data[1], data[2]);
onNextStep();
break;
case "emote":
temp.emote = data[1];
temp.time = (!data[2] ? 3 : data[2]);
if (temp.emote == "emoticon_Zzz_stay.mng") {
showimg(200, temp.emote, this.x + 2.5, this.y - (92/16) + 1);
} else {
showani(200, this.x, this.y, 0, "emoticon", temp.emote);
}
this.scheduleevent(time, "ClearEmote", "");
onNextStep();
break;
case "levelvar":
level.(@data[1]) = data[2];
onNextStep();
break;
case "randomani":
if (random(0,1) <= data[1]) {
setcharani(data[2], data[3]);
}
onNextStep();
break;
case "chat":
this.chat = data[1];
temp.time = (!data[2] ? 3 : data[2]);
this.scheduleevent(time, "ClearChat", "");
onNextStep();
break;
case "randomchat":
if (random(0,1) <= data[1]) {
this.chat = data[2];
temp.time = (!data[3] ? 3 : data[3]);
this.scheduleevent(time, "ClearChat", "");
}
onNextStep();
break;
case "showimg":
showimg(data[1], data[2], data[3], data[4]);
break;
case "hideimg":
hideimg(data[1]);
break;
case "setvar":
this.(@data[1]) = data[2];
break;
case "wait":
this.waiting = true;
this.scheduleevent(data[1], "NextStep", true);
break;
case "repeat":
this.actionpos = 0;
doAction(this.actionpos);
break;
}
}
// Movement Functions
function npc_stop() {
this.is_stopped = true;
this.oani = this.ani;
this.odir = this.dir;
setcharani(this.ani_idle, "");
}
function npc_continue() {
this.is_stopped = false;
this.dir = this.odir;
this.ani = this.oani;
onMovementFinished();
}
function GetDistance(temp.x1, temp.y1, temp.x2, temp.y2) {
temp.toreturn = (temp.x2 - temp.x1) ^ 2 + (temp.y2 - temp.y1) ^ 2;
return (temp.toreturn) ^ .5;
}
function doMove(dx, dy) {
temp.distance = GetDistance(this.x, this.y, this.x + dx, this.y + dy);
temp.time = distance / this.speed;
this.dir = getRealDir(dx, dy);
move(dx, dy, temp.time, 8);
if (this.ani != this.ani_walk) setcharani(this.ani_walk, "");
if (!this.reversing) this.moves.add({dx, dy});
}
function getRealDir(dx, dy) {
if (dy < 0 && dx > 0) return 3;
if (dy > 0 && dx > 0) return 3;
if (dy < 0 && dx < 0) return 1;
if (dy > 0 && dx < 0) return 2;
return getdir(dx, dy);
}
function onMovementFinished() {
if (this.is_stopped || this.waiting) return;
temp.next_action = this.actions[this.actionpos+1];
if (temp.next_action.pos("move") == -1 && !this.reversing) {
setcharani(this.ani_idle, "");
}
if (this.reversing) {
this.reversepos--;
if (this.reversepos <= -1) {
this.reversing = false;
this.moves = "";
setcharani(this.ani_idle, "");
return onNextStep();
}
temp.move = this.moves[this.reversepos];
return doMove(-temp.move[0], -temp.move[1]);
}
else onNextStep();
}
// Action Related Functions
function onClearChat() {
this.chat = "";
}
function onClearEmote() {
hideimg(200);
}
function onNextStep(done_waiting) {
if (this.waiting && done_waiting) {
this.waiting = false;
}
if (this.is_stopped) return;
this.actionpos++;
doAction(this.actionpos);
}
Stop example:
PHP Code:
//#CLIENTSIDE
function onCreated() {
// your actions...
this.can_stop = true;
join("movement");
}
function onPlayerTouchsMe() {
npc_stop();
this.dir = (player.dir + 2) % 4;
waitfor(this, "RandomEvent", 3); // I would use a dialog script that uses waitfor here...
npc_continue();
}