Jump to content
  • 0

unitwalk - Unit has already been forced to walk and not reached it's destination yet


noblesse

Question


  • Group:  Members
  • Topic Count:  6
  • Topics Per Day:  0.00
  • Content Count:  17
  • Reputation:   0
  • Joined:  05/15/16
  • Last Seen:  

Hello,

I am using unitwalk and it is indeed walking, but the problem is I am always getting this warning

[Warning]: buildin_unitwalk: Unit has already been forced to walk and not reached it's destination yet.
[Warning]: Script command 'unitwalk' returned failure.
 

I can't even make the event label work "unitwalk <GID>,<x>,<y>{,"<event label>"};"

Does anyone have a successful unitwalk with an event label?

Link to comment
Share on other sites

8 answers to this question

Recommended Posts

  • 0

  • Group:  Members
  • Topic Count:  1
  • Topics Per Day:  0.00
  • Content Count:  245
  • Reputation:   93
  • Joined:  06/30/18
  • Last Seen:  

*unitwalk <GID>,<x>,<y>{,"<event label>"};

Use the event label to issue the next unitwalk after the last has finished.

    OnDestinationReached:
        if(!unitexists(.gid)) end;

        if(.move_count == .curr_pointer)
            .curr_pointer = 0;
        else
            .curr_pointer += 2;

        unitwalk(.gid, .path[.curr_pointer], .path[.curr_pointer + 1], "MVP_EVENT::OnDestinationReached");
        end;

 

Edited by Winterfox
Link to comment
Share on other sites

  • 0

  • Group:  Members
  • Topic Count:  16
  • Topics Per Day:  0.00
  • Content Count:  696
  • Reputation:   721
  • Joined:  11/12/12
  • Last Seen:  

Heya,

Personally, I always disliked that command because it's awkward to use. The answer from Winterfox probably works, but if you want to have more "freedom" when using the command, you can instead my custom command "unitwalk_wait" (you'll have to apply the diff file yourself, of course).

/// Makes the unit walk to target position.
/// Returns if it was successful.
///
/// unitwalk_wait(<unit_id>,<x>,<y>{,<timeout>}) -> <bool>

Basically, the script will pause until the unit has reached its destination (or until it times out). Here is a script example:

prontera,150,186,4	script	test_walk	77,{
	mes "Move the NPC.";
L_Again:
	getmapxy(.@m$, .@npc_x, .@npc_y, BL_NPC, getnpcid(0));
	
	switch(select("Left:Right:Scripted path")) {
		case 1:
			unitwalk_wait getnpcid(0), .@npc_x - 3, .@npc_y;
			break;
		case 2:
			unitwalk_wait getnpcid(0), .@npc_x + 3, .@npc_y;
			break;
		case 3:
			clear;
			mes "Give me a moment...!";
			close2;
			donpcevent strnpcinfo(0) + "::OnWalk";
			end;
	}
	
	goto L_Again;
	end;
OnWalk:
	.@npcid = getnpcid(0);
	unitwalk_wait .@npcid, 150, 187;
	misceffect 150;
	npctalk "Here I go!";
	sleep 1500;
	unitwalk_wait .@npcid, 155, 187;
	unitwalk_wait .@npcid, 160, 187;
	emotion E_GASP;
	npctalk "What a rough corner...";
	sleep 1500;
	unitwalk_wait .@npcid, 160, 180;
	npctalk "I have walked plenty, no more sir.";
	end;
}

Source changes:

diff --git a/src/map/script.cpp b/src/map/script.cpp
index 8fdc6fabc..16a54d81b 100644
--- a/src/map/script.cpp
+++ b/src/map/script.cpp
@@ -4256,6 +4256,24 @@ TIMER_FUNC(run_script_timer){
 	return 0;
 }
 
+int script_sleep_resume(struct script_state* st) {
+	struct linkdb_node* node = (struct linkdb_node*)sleep_db;
+
+	while (node && st->sleep.timer != INVALID_TIMER) {
+		if ((int)__64BPRTSIZE(node->key) == st->oid && ((struct script_state*)node->data)->sleep.timer == st->sleep.timer) {
+			script_erase_sleepdb(node);
+			delete_timer(st->sleep.timer, run_script_timer);
+			st->sleep.timer = INVALID_TIMER;
+			break;
+		}
+		node = node->next;
+	}
+	if (st->state != RERUNLINE)
+		st->sleep.tick = 0;
+	run_script_main(st);
+	return 0;
+}
+
 /**
  * Remove sleep timers from the NPC
  * @param id: NPC ID
@@ -19782,6 +19800,66 @@ BUILDIN_FUNC(unitwalk)
 	return SCRIPT_CMD_SUCCESS;
 }
 
+/// Makes the unit walk to target position.
+/// Returns if it was successful.
+///
+/// unitwalk_wait(<unit_id>,<x>,<y>{,<timeout>}) -> <bool>
+BUILDIN_FUNC(unitwalk_wait)
+{
+	struct block_list* bl;
+	struct unit_data* ud = NULL;
+	const char* cmd = script_getfuncname(st);
+
+	if (!script_rid2bl(2, bl))
+	{
+		script_pushint(st, 0);
+		return SCRIPT_CMD_FAILURE;
+	}
+
+	ud = unit_bl2ud(bl);
+
+	// Timeout reached, the unit didn't walk to the destination... just ignore and move to the next script command.
+	if (st->sleep.tick != 0) {
+		st->state = RUN;
+		st->sleep.tick = 0;
+		return SCRIPT_CMD_SUCCESS;
+	}
+
+	// Unit was already forced to walk.
+	if (ud != nullptr && ud->state.force_walk) {
+		script_pushint(st, 0);
+		ShowWarning("buildin_%s: Unit has already been forced to walk and not reached it's destination yet.\n", cmd);
+		return SCRIPT_CMD_FAILURE;
+	}
+
+	if (bl->type == BL_NPC) {
+		if (!((TBL_NPC*)bl)->status.hp)
+			status_calc_npc(((TBL_NPC*)bl), SCO_FIRST);
+		else
+			status_calc_npc(((TBL_NPC*)bl), SCO_NONE);
+	}
+
+	int x = script_getnum(st, 3);
+	int y = script_getnum(st, 4);
+	int timeout = script_hasdata(st, 5) ? script_getnum(st, 5) : 5000;
+
+	if (script_pushint(st, unit_can_reach_pos(bl, x, y, 0))) {
+		if (ud != nullptr)
+			ud->state.force_walk = true;
+		add_timer(gettick() + 50, unit_delay_walktoxy_timer, bl->id, (x << 16) | (y & 0xFFFF)); // Need timer to avoid mismatches
+	}
+
+	struct npc_data* nd = map_id2nd(st->oid);
+
+	if (ud && nd) {
+		st->state = RERUNLINE;
+		st->sleep.tick = timeout;
+		ud->walk_done_script = st->id;
+	}
+
+	return SCRIPT_CMD_SUCCESS;
+}
+
 /// Kills the unit.
 ///
 /// unitkill <unit_id>;
@@ -27693,6 +27771,7 @@ struct script_function buildin_func[] = {
 	BUILDIN_DEF(setunitdata,"iii"),
 	BUILDIN_DEF(unitwalk,"iii?"),
 	BUILDIN_DEF2(unitwalk,"unitwalkto","ii?"),
+	BUILDIN_DEF(unitwalk_wait,"iii?"),
 	BUILDIN_DEF(unitkill,"i"),
 	BUILDIN_DEF(unitwarp,"isii"),
 	BUILDIN_DEF(unitattack,"iv?"),
diff --git a/src/map/script.hpp b/src/map/script.hpp
index b7beb8483..4c499359a 100644
--- a/src/map/script.hpp
+++ b/src/map/script.hpp
@@ -2231,6 +2231,7 @@ int conv_num(struct script_state *st, struct script_data *data);
 const char* conv_str(struct script_state *st,struct script_data *data);
 void pop_stack(struct script_state* st, int start, int end);
 TIMER_FUNC(run_script_timer);
+int script_sleep_resume(struct script_state* st);
 void script_stop_sleeptimers(int id);
 struct linkdb_node *script_erase_sleepdb(struct linkdb_node *n);
 void script_attach_state(struct script_state* st);
diff --git a/src/map/unit.cpp b/src/map/unit.cpp
index a643967a0..339a025a1 100644
--- a/src/map/unit.cpp
+++ b/src/map/unit.cpp
@@ -488,6 +488,15 @@ static TIMER_FUNC(unit_walktoxy_timer)
 
 		ud->state.force_walk = false;
 
+		if (ud->walk_done_script) {
+			ud->state.walk_script = false;
+			struct script_state*st = (struct script_state* )idb_get(st_db, ud->walk_done_script);
+
+			if (st) {
+				script_sleep_resume(st);
+			}
+		}
+
 		if (ud->walk_done_event[0]){
 			char walk_done_event[EVENT_NAME_LENGTH];
 
diff --git a/src/map/unit.hpp b/src/map/unit.hpp
index cfd932615..76ccc0c8f 100644
--- a/src/map/unit.hpp
+++ b/src/map/unit.hpp
@@ -61,6 +61,7 @@ struct unit_data {
 		bool force_walk; ///< Used with script commands unitwalk/unitwalkto. Disables monster idle and random walk.
 	} state;
 	char walk_done_event[EVENT_NAME_LENGTH];
+	int walk_done_script; ///Script OID to run after a walking event is over
 	char title[NAME_LENGTH];
 	int32 group_id;
 

I find this command much easier to use. You can also mix it up with the original "unitwalk" when multiple NPCs walk at the same time, as long as unitwalk_wait is the last one. Anyhow, you'll figure it out if you do end up using this command (or whoever else does).

unitwalk_wait.diff

Link to comment
Share on other sites

  • 0

  • Group:  Members
  • Topic Count:  2
  • Topics Per Day:  0.00
  • Content Count:  12
  • Reputation:   2
  • Joined:  07/26/16
  • Last Seen:  

@Tokei Your while loop is raising eyebrows because the server will be in a blocking loop until the timer exits. 

Link to comment
Share on other sites

  • 0

  • Group:  Members
  • Topic Count:  16
  • Topics Per Day:  0.00
  • Content Count:  696
  • Reputation:   721
  • Joined:  11/12/12
  • Last Seen:  

8 hours ago, Smokexyz said:

@Tokei Your while loop is raising eyebrows because the server will be in a blocking loop until the timer exits. 

You mean the loop in script_sleep_resume? It will leave the loop once the timer is found in sleep_db. Then it's erased/deleted and set to INVALID_TIMER. There is nothing blocking as far as I can tell. This code is copied from the run_script_timer timer function, which resumes a script after sleep/sleep2 is executed. Or are you talking about another loop or am I missing something here?

Edited by Tokei
Link to comment
Share on other sites

  • 0

  • Group:  Members
  • Topic Count:  2
  • Topics Per Day:  0.00
  • Content Count:  12
  • Reputation:   2
  • Joined:  07/26/16
  • Last Seen:  

Unless you can guarantee that the while loop doesn't always result in a true condition.

while (node && st->sleep.timer != INVALID_TIMER) {

For example, if the timer runs indefinitely, the server would be in an infinite loop here. Indefinitely waiting for the timer to arrive as an INVALID_TIMER. Blocking all other processes. Hell even while the timer (for a shorter duration) is being processed, it would wait here blocking all other processes on the server. I don't think the code is safe for the server.

Such is coding on single threaded applications >:D

Link to comment
Share on other sites

  • 0

  • Group:  Members
  • Topic Count:  16
  • Topics Per Day:  0.00
  • Content Count:  696
  • Reputation:   721
  • Joined:  11/12/12
  • Last Seen:  

4 hours ago, Sephus said:

Unless you can guarantee that the while loop doesn't always result in a true condition.

while (node && st->sleep.timer != INVALID_TIMER) {

For example, if the timer runs indefinitely, the server would be in an infinite loop here. Indefinitely waiting for the timer to arrive as an INVALID_TIMER. Blocking all other processes. Hell even while the timer (for a shorter duration) is being processed, it would wait here blocking all other processes on the server. I don't think the code is safe for the server.

Such is coding on single threaded applications >:D

It took me some time to figure out what the concern was, but I think I got it. This comes from a confusion on what the while-loop condition means and the misconception of how timers work in the emulator.

First thing first, sleep_db is a linked list, each node is connected to the next node, and the final node is connected to a null pointer. So eventually, node->next will return a null pointer, and it will break out of the while-loop. This is your typical way of going through a linked list and this is what we're doing here. We're just going through a linked list, nothing more.

As for the "while (node && st->sleep.timer != INVALID_TIMER)" condition, think of it more like this:

if (st->sleep.timer != INVALID_TIMER) {
	while (node) {
		if (something) {
			...
			break;
		}
		node = node->next;
	}
}

It simply combines the condition for simplicity. If the timer is already marked as INVALID_TIMER, then it doesn't need to remove the linked node nor does it need to delete the timer. This has nothing to do with "waiting for the timer to finish", it doesn't do that at all. It's a condition check for whether or not it should bother deleting the timer.

As you've said before, rAthena is indeed a single threaded application, so waiting for a timer to finish directly doesn't make sense and I want to reiterate that this isn't what this code does.

Also, I said this previously, but this code is copied from run_script_timer. The code you're worried about is already currently running on your server, so if there was a problem with it, you'd have known by now (it's being executed dozens of time every second as well).

__

Quick note on timers: after using add_timer, no threads are created. Your timer tick is added to a static list and then checked constantly through the core loop of the emulator in core.cpp (via do_timer):

#ifndef MINICORE
		if( !this->m_run_once ){
			// Main runtime cycle
			while( this->get_status() == e_core_status::RUNNING ){
				t_tick next = do_timer( gettick_nocache() );

				this->handle_main( next );
			}
		}
#endif

Once the your timer tick is expired, the code will be executed and the timer will be removed. It's rather simple in the end

Though on that note... That's also why you'll notice that using small sleep timers (like "sleep 100;" in a script) is often not recommend if you rely on these small amounts. The timers are always delayed by 0~20 ms because the core loop for timers only check at a minimum interval to not overload the emulator. So in such a scenario, you'll notice that doing "sleep 100;" 50 times in a row will take roughly 6 seconds rather than 5 seconds. It's quite noticeable after a while, and that's why you should rely more on OnTimer labels instead (those are still delayed by that 0~20 ms, but the delays don't stack up, so they end up being more accurate). Anyhow, hopefully this helps some people deal with timers both in the source and in scripts.

(So yes, this while-loop is safe!)

Edited by Tokei
Link to comment
Share on other sites

  • 0

  • Group:  Members
  • Topic Count:  6
  • Topics Per Day:  0.00
  • Content Count:  17
  • Reputation:   0
  • Joined:  05/15/16
  • Last Seen:  

On 9/8/2024 at 2:49 AM, Winterfox said:
*unitwalk <GID>,<x>,<y>{,"<event label>"};

Use the event label to issue the next unitwalk after the last has finished.

    OnDestinationReached:
        if(!unitexists(.gid)) end;

        if(.move_count == .curr_pointer)
            .curr_pointer = 0;
        else
            .curr_pointer += 2;

        unitwalk(.gid, .path[.curr_pointer], .path[.curr_pointer + 1], "MVP_EVENT::OnDestinationReached");
        end;

 

@Winterfox Is it not possible to have the event trigger my script like mob search in the area after reaching the destination?

 

getmapxy .@m$,.@x,.@y,BL_PC;
getfreecell .@m$,.@to_x,.@to_y,.@x,.@y,10,10;

unitwalk getcharid(3),.@to_x,.@to_y,"Walk::OnDest";

dispbottom "continue script";

OnDest:
dispbottom "event label triggered";

This is my sample code, the problem is it didn't skip the "continue script" part. It just run continuously. 

Link to comment
Share on other sites

  • 0

  • Group:  Members
  • Topic Count:  1
  • Topics Per Day:  0.00
  • Content Count:  245
  • Reputation:   93
  • Joined:  06/30/18
  • Last Seen:  

8 hours ago, noblesse said:

@Winterfox Is it not possible to have the event trigger my script like mob search in the area after reaching the destination?

 

getmapxy .@m$,.@x,.@y,BL_PC;
getfreecell .@m$,.@to_x,.@to_y,.@x,.@y,10,10;

unitwalk getcharid(3),.@to_x,.@to_y,"Walk::OnDest";

dispbottom "continue script";

OnDest:
dispbottom "event label triggered";

This is my sample code, the problem is it didn't skip the "continue script" part. It just run continuously. 

Why do you let the script fall trough to the label? 
If you want to continue the script after the destination is reached, you can simply let the script end after unitwalk and it will automatically pick up at the label after it reached the finale destination.

Edited by Winterfox
Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Answer this question...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...