PDA

View Full Version : NPC checking for player


i8bit
01-04-2014, 02:08 PM
As you all know, the onPlayerTouchsMe() only works if the player runs into the NPC.

How would I script the NPC to check if the player is in the x, y of the object.
In this case, block.png

function onCreated(){
setImg("block.png");
setTimer(1);
}

function onTimeOut(){
destroy();
}

function onPlayerTouchsMe(){
player.chat = "HIT";
}

Starfire2001
01-04-2014, 04:34 PM
You need continuously to check the players position in a loop to see if they are within the bounds of the block. If you are looking to do it clientside...

//#CLIENTSIDE
function onCreated() {
setImg("block.png");
onTimeout(); // probably better than setting a timer here as you might not want to wait a second before checking
}

function onTimeout() {
// assuming that your block's dimensions are 2x2
if (player.x in |this.x, this.x + 2| && player.y in |this.y, this.y + 2|) {
//destroy or hide the block or whatever you want to do
} else {
setTimer(.05); // or higher if it doesn't need to be so accurate
}
}

If you want to do it serverside you'll have to loop through the players array in your timeout and check if any of them are within the bounds of the block.

cbk1994
01-04-2014, 10:27 PM
// assuming that your block's dimensions are 2x2
if (player.x in |this.x, this.x + 2| && player.y in |this.y, this.y + 2|) {

Just a note that you can use this.width and this.height to get the width/height of an NPC with a defined shape (like an image).

callimuc
01-05-2014, 01:51 AM
Just a note that you can use this.width and this.height to get the width/height of an NPC with a defined shape (like an image).

changing the image though (like a 1x1 pixel one) will also alter this.width/height clientside though, that way players would be able to cheat through blocks

cbk1994
01-05-2014, 02:13 AM
changing the image though (like a 1x1 pixel one) will also alter this.width/height clientside though, that way players would be able to cheat through blocks

If you're worried about cheating players, then you can't trust anything that happens clientside. You're right that that would be one of the easier ways to cheat, though.

Torankusu
01-05-2014, 03:12 AM
don't want to seem like i'm giving advice, because I am not, and I also don't know what the OP. wants to do with this, since it's so vague and there might be a better approach, but here's something i came up with on a serverside approach to at least give an array of players in that object's coordinates.


TServerLevel.findareaplayers(float, float, float, float) - returns object - returns an array of all players in the specified rectangle (x,y,width,height), uses the blocking rectangle of players (0.5,1,2,2) instead of (0,0)



// Created by Torankusu

function onCreated()
{
setshape(1,32,32);
this.image = "block.png";
drawunderplayer();
dontblock();
setTimer(0.1);
}

function onTimeout()
{
temp.pls = this.level.findareaplayers(this.x,this.y,2,2);
this.chat = temp.pls;
setTimer(0.1);
}


the object's chat in this example will display the array of players that are in the x,y of that object.

i8bit
01-05-2014, 03:15 PM
don't want to seem like i'm giving advice, because I am not, and I also don't know what the OP. wants to do with this, since it's so vague and there might be a better approach, but here's something i came up with on a serverside approach to at least give an array of players in that object's coordinates.

I am trying to make "damage blocks" so when the player collides with them, player.clientr.hp is deducted. That's why I want to go serverside. I can see where I was unclear by making the player chat in the end result. I should have been more specific.

Emera
01-05-2014, 04:26 PM
I am trying to make "damage blocks" so when the player collides with them, player.clientr.hp is deducted. That's why I want to go serverside. I can see where I was unclear by making the player chat in the end result. I should have been more specific.

Just to make sure, you're not using onPlayerTouchsMe() because I'm guessing that both the damage block and the players will be moving?

i8bit
01-05-2014, 06:44 PM
Just to make sure, you're not using onPlayerTouchsMe() because I'm guessing that both the damage block and the players will be moving?


Well if the player was at constant motion, the onPlayerTouchsMe() would not be a problem. But it's the players that are on idle that it won't work.

Jakov_the_Jakovasaur
01-05-2014, 07:08 PM
you could always make a clientside system which triggers to serverside upon damage to reduce hp or handle deaths, youd just need to make sure that it works securely (such as preventing people who modify packets invoking negative damage aka increased health) and that theres a clientside health variable to give the illusion of real time damage

if you do this then it would be easy to have a single weapon system for checking all box intersects for every enemy, rather than scripting the box intersect checks in a timeout inside all of their scripts

i8bit
01-05-2014, 11:21 PM
you could always make a clientside system which triggers to serverside upon damage to reduce hp or handle deaths, youd just need to make sure that it works securely (such as preventing people who modify packets invoking negative damage aka increased health) and that theres a clientside health variable to give the illusion of real time damage

Think you could give me an example/scenario?

cbk1994
01-05-2014, 11:32 PM
Think you could give me an example/scenario?

The simple solution is just take Starfire's code and modify it to send a trigger...


//#CLIENTSIDE
function onCreated() {
setImg("block.png");
onTimeout(); // probably better than setting a timer here as you might not want to wait a second before checking
}

function onTimeout() {
// assuming that your block's dimensions are 2x2
if (player.x in |this.x, this.x + 2| && player.y in |this.y, this.y + 2|) {
triggerServer("npc", "HealthSystem", "hitByBlock");
return setTimer(1); // to avoid sending 20 triggers per second
}

setTimer(.05); // or higher if it doesn't need to be so accurate
}


and then in a DB NPC HealthSystem...


function onActionServerSide(temp.cmd) {
if (temp.cmd == "hitByBlock") {
echo(player.niceName() @ " was hit by a block.");
player.hit(1); // or whatever
}
}


You could also send a point trigger (but those can be unreliable) or do this a bunch of other ways. There are some neat tricks you can use to do this more efficiently but I'd need to know more about what you're trying to do to know if they'd work here.

Note that the above makes it possible for cheating players to not be hit by the block, so if you care about that, you should do everything serverside.

i8bit
01-06-2014, 12:47 PM
So I took what you said, but modified it a bit

//#CLIENTSIDE
function onCreated() {
setImg("block.png");
onTimeout();
scheduleEvent("onDestroy", 1);
}

function onTimeout() {
if (player.x in |this.x, this.x + 2| && player.y in |this.y, this.y + 2|) {
triggerServer("weapon", "HealthSystem", "hitByBlock");
}

setTimer(.05);
}

function onDestroy(){
destroy()
}

Then in the weapon- "HealthSystem"
function onActionServerSide(){
if (params[0] == "hitByBlock"){
player.clientr.hp -= //whatever amount
}
}

I haven't had a chance to test this but I don't see why it wouldn't work

SO my next question is...
How would I check local NPCs (Baddies) to trigger their "Hurt" function with that block too?

I want my "damage block" to not only effect players but NPCs also.

cbk1994
01-06-2014, 05:30 PM
Looks fine to me except you probably don't want to send 20 triggers per second.

Hurting NPCs should probably be done serverside:


function hurtNPCs() {
for (temp.npc : findAreaNPCs(this.x, this.y, 2, 2)) {
temp.npc.trigger("hurt");
}
}


then you can add an onHurt function in any NPCs which can be hurt.

i8bit
01-06-2014, 07:25 PM
Looks fine to me except you probably don't want to send 20 triggers per second.

Oh yeah, good observation. What if I don't make it loop and it just checks for players/NPCs in that 0.05 time range, triggers the actions, then destroys?

cbk1994
01-06-2014, 09:38 PM
Oh yeah, good observation. What if I don't make it loop and it just checks for players/NPCs in that 0.05 time range, triggers the actions, then destroys?

Should work if you only want it to send the trigger once. Just call this.destroy(); after sending the trigger.

i8bit
01-06-2014, 11:25 PM
Should work if you only want it to send the trigger once. Just call this.destroy(); after sending the trigger.

So I ran into a little bit of issues. I discovered I need to destroy the block serverside. How would I trigger a serverside function in clientside?

I tried this in the clientside

triggerserver("npc", this.name, "destroy")

and this in the serverside..

function onActionServerSide(){
if (params[0] == "destroy"){
destroy();
}
}

it doesn't work so can you tell me what I'm doing wrong please?

Torankusu
01-07-2014, 11:31 AM
Is this in a class or level npc?

Also, are you going to be using this in a situation where the blocks appearance and location needs to be:
A.) The same for all players in the level
Or
B.) Can be different for all players in the level.

Some good suggestions previously listed here, but there are potentially a lot of ways to cheat this on the clientside as well.

i8bit
01-07-2014, 12:00 PM
Is this in a class or level npc?

Also, are you going to be using this in a situation where the blocks appearance and location needs to be:
A.) The same for all players in the level
Or
B.) Can be different for all players in the level.

Some good suggestions previously listed here, but there are potentially a lot of ways to cheat this on the clientside as well.

It is a class.

callimuc
01-07-2014, 06:47 PM
triggerserver() does not work inside level NPCs, you would need to use triggerAction() for this case

cbk1994
01-08-2014, 01:25 AM
I still don't really know what you're doing so it's hard to give advice, but if your NPCs are created via putnpc2, they do have a name, and you can trigger them via triggerServer (but you have to get the name clientside; normally it isn't available). That might be the best approach.

callimuc
01-08-2014, 03:36 PM
triggerserver() does not work inside level NPCs, you would need to use triggerAction() for this case

Have to fix this statement, you can use triggerServer() inside level NPCs to trigger DB NPCs or weapons (and classes being joined to those). I figured this would be the best solution if you also want to receive the onActionServerSide() inside level NPCs (or NPCs created via putNPC2()) and classes being joined to those


You can also do


function onCreated() {
this.level.tapThis = this;
}

function onPow() {
this.chat = "ouch!";
}

//#CLIENTSIDE
function onPlayerChats() {
triggerServer("npc", "TriggerControl", "Trigger", this.level.name, "tapThis", "Pow");
}


and then in an NPC TriggerControl


function onActionTrigger(levelName, npcName, command) {
findLevel(levelName).(@ npcName).trigger(command);
}


Though you'd want to add some security features. Usually something like this is preferable since you never know if a trigger will be received when using x/y.

i8bit
01-08-2014, 04:13 PM
Allow me to clarify exactly what I'm doing.

When the player presses 'a', the player creates an NPC that I'm using as a damage block. The NPC is functional through joining a class.

Perhaps it'd be easier to explain if I did step by step what I'm doing, so I can get some better, more specific advice.

1. Player presses 'A' and creates the "damage block" NPC.
This is the code from the Weapon
function onActionServerSide(){
if (params[0] == "dmgblock"){
temp.npc = this.level.putNPC2(player.x, player.y, "");
temp.npc.join("damageblock");
}
}

//#CLIENTSIDE
function onKeyPressed(code, key){
if (key == "c"){
player.chat = "Placed!";
triggerserver("weapon", this.name, "dmgblock");
}
}
I realize the placement with the x and y is off and the player will collide instantly within placing. Ignore that.

2. The NPC preforms it's function, then deletes
This is where I'm Having issues. I need the block to destroy serverside.
This is the script from the CLASS it joined

function onActionServerside(){
if (params[0] == "destroy"){
destroy();
}
}

//#CLIENTSIDE
function onCreated(){
dontblock();
setImg("block.png");
onTimeOut();
scheduleEvent(1, "onDestroy");
}


function onTimeOut(){
if (player.x in |this.x, this.x + 2| && player.y in |this.y, this.y + 2|) {
player.chat = "HIT";}
//Trigger Actions in the player to make him lose HP and Whatever else
setTimer(0.05);
}

function onDestroy(){
triggerAction("npc", this.name, "destroy");
}

BlueMelon
01-08-2014, 04:58 PM
Consider this example:

function onCreated() {
setshape(1,32,32);
}
function onActionTest() {
this.chat = random(0,100);
}

//#CLIENTSIDE

function onCreated() {
setTimer(0.05);
}
function onTimeout() {
triggeraction(this.x,this.y,"test");
setTimer(1);
}

callimuc
01-08-2014, 05:04 PM
Weapon script:
changed your this.level.putNPC2(...) to player.level.putNPC2(...), this. would refer to the weapon while player. refers to the player)


function onActionServerSide(){
if (params[0] == "dmgblock"){
temp.npc = player.level.putNPC2(player.x, player.y, ""); //should be player.level instead of this.level
temp.npc.join("damageblock");
}
}

//#CLIENTSIDE
function onKeyPressed(code, key){
if (key == "c"){
player.chat = "Placed!";
triggerserver("weapon", this.name, "dmgblock");
}
}


Class script:
removed your dontBlock() as it needs to be a blocking NPC I believe
fixed your triggerAction()
and other stuff

function onCreated() {
//1=blocking, 32=width in pixel, 32=height in pixel
setshape(1, 32, 32);
}

function onActionDestroyBlock() {
this.destroy();
}

//#CLIENTSIDE
function onCreated() {
setshape(1, 32, 32);
this.setImg("block.png");
onTimeOut();
}


function onTimeOut(){
//if player is within the square, trigger the actions
if (player.x in |this.x, this.x + 2| && player.y in |this.y, this.y + 2|) {
player.chat = "HIT";
//stop the script and call the 'onDestroyObject()' function
return onDestroyObject();
}
//keep running the script as the player hasnt been inside the block range yet
setTimer(0.05);
}

function onDestroyObject() {
//x, y, onAction...., parameters
triggerAction(this.x, this.y, "DestroyBlock", NULL);
}

i8bit
01-08-2014, 05:21 PM
[QUOTE=BlueMelon;1724944]Consider this example:


Thanks for the tip. Here's what I got

function onCreated() {
setshape(1,32,32);
setImg("block.png");
}

function onActionTest(){
destroy();
}

//#CLIENTSIDE
function onCreated(){
dontblock();
setTimer(1);
if (player.x in |this.x, this.x + 2| && player.y in |this.y, this.y + 2|) {
player.chat = "HIT";}
}


function onTimeOut(){
triggeraction(this.x,this.y,"Test");
}


PROBLEM:
It sometimes works, and sometimes doesn't. It seems to only destroy half the time... Anything I can modify to make it more reliable?

BlueMelon
01-08-2014, 06:30 PM
Take a look at what you posted, and what callimuc and I have posted.

You are checking the collision when the npc is created. Meaning it is comparing the coordinates once... this is not what you want.

callimuc
01-08-2014, 06:33 PM
PROBLEM:
It sometimes works, and sometimes doesn't. It seems to only destroy half the time... Anything I can modify to make it more reliable?

you need to put the players location check into the loop, check my example. else it will only work when the block is being placed but it never checks back again.

Torankusu
01-08-2014, 09:07 PM
What they said ^^
Plus, its also probably noteworthy to make sure you use setTimer (0.05 or however long...) in the onTimeout function to continue the loop -- you don't have it this way in your example.

Also, not necessary, but if you wanted to start the loop as immediately as it was created just call onTimeout (); in the on created function.

edit: sorry, missed it on my phone.
callimuc's example is spot on -- disregard my post if you follow his examples -- or just take them as an explanation for what he did. :]

i8bit
01-09-2014, 04:36 AM
I wanna thank everyone for all the advice! I have been really learning a lot!
So far I have it running correct with the tests I have put it through. But if I run in to any trouble I will be sure to add on to this thread with my questions.

i8bit
01-12-2014, 05:44 PM
I have everything working the way I want, but I am trying to do a check in the CLASS script to check if the baddie is a certain type.

for (temp.npc : findAreaNPCs(this.x, this.y, 4, 4)) {
if (temp.npc.baddietype == "normal"){
temp.npc.trigger("Hurt");
}
}

What is line 2 suppose to look like to check if the baddietype == "normal"??

cbk1994
01-12-2014, 07:27 PM
I have everything working the way I want, but I am trying to do a check in the CLASS script to check if the baddie is a certain type.

for (temp.npc : findAreaNPCs(this.x, this.y, 4, 4)) {
if (temp.npc.baddietype == "normal"){
temp.npc.trigger("Hurt");
}
}

What is line 2 suppose to look like to check if the baddietype == "normal"??

It's fine if you're setting this.baddietype = "normal"; serverside in all the baddie NPCs you want to get hurt.

i8bit
01-13-2014, 06:24 AM
It's fine if you're setting this.baddietype = "normal"; serverside in all the baddie NPCs you want to get hurt.

I am, but it's not triggering the function for some reason...

cbk1994
01-13-2014, 07:21 AM
I am, but it's not triggering the function for some reason...

Post the code that's handling the triggers.

Have you tried debugging to see where the problem is? Add echoes to the for loop to see what NPCs it's finding, and another inside the if-statement to see if the right NPCs are being considered baddies. If you haven't already, add echoes in the trigger receiving function to see if it's received at all. Add echoes before the for-loop to see if it's even being reached. When you do this it will become clear where the problem is (e.g. is the problem finding NPCs? is it identifying the right NPCs? is it triggering the NPCs?). You can't have too many echoes when debugging.

i8bit
01-13-2014, 09:18 PM
Post the code that's handling the triggers.

Have you tried debugging to see where the problem is? Add echoes to the for loop to see what NPCs it's finding, and another inside the if-statement to see if the right NPCs are being considered baddies. If you haven't already, add echoes in the trigger receiving function to see if it's received at all. Add echoes before the for-loop to see if it's even being reached. When you do this it will become clear where the problem is (e.g. is the problem finding NPCs? is it identifying the right NPCs? is it triggering the NPCs?). You can't have too many echoes when debugging.

It was a dumb problem I overlooked. In the baddie's CLASS, I had
baddietype = "normal";
where I needed:
this.baddietype = "normal";

i8bit
01-14-2014, 02:57 AM
So after a lot of testing and what not, I have come to realize that my damage block system works, but not at the fast pace I am looking for.

Furthermore, I am going to switch to run my damage system all via CLIENTSIDE.

How would I trigger a clientside function in a weapon of another player?

//#CLIENTSIDE
function onKeyPressed(code, key){
if (key == "s"){
if (player.x in |this.x-2, this.x + 1| && player.y in |this.y-1, this.y + 3|) {
//Trigger Clientside function in other player's "System/Health" weapon
}
}
}


Can anyone fill in that line for me please? :)
It's also something I'd like to learn for the future.

Also, I'm not sure if my check is right to find players in front. If incorrect, please assist me there too.

100Zero100
01-14-2014, 04:49 AM
So after a lot of testing and what not, I have come to realize that my damage block system works, but not at the fast pace I am looking for.

Furthermore, I am going to switch to run my damage system all via CLIENTSIDE.

How would I trigger a clientside function in a weapon of another player?

//#CLIENTSIDE
function onKeyPressed(code, key){
if (key == "s"){
if (player.x in |this.x-2, this.x + 1| && player.y in |this.y-1, this.y + 3|) {
//Trigger Clientside function in other player's "System/Health" weapon
}
}
}


Can anyone fill in that line for me please? :)
It's also something I'd like to learn for the future.

Also, I'm not sure if my check is right to find players in front. If incorrect, please assist me there too.

If you want to find a player *in front* you would use the vecx() and vecy() functions. vecx(player.dir) returns -1 for left, +1 for right, and 0 for up/down. Thus, doing player.x + vecx(player.dir) will aim 1 in front of the player either left or right, while doing player.y + vecy(player.dir) will aim 1 in front of player either up or down.

I am pretty sure Graal also allows you to triggerAction straight onto another player, something like

triggerAction(player.x + vecx(..), player.y + vecy(..), "hit", "etc");

Assuming that's deprecated (I think it might be, or might require you to set a server option to allow it), then the first is the way to go.

In either scenario, once you get to the serverside with the player already planned, just triggerClient onto that player.

Something like this:

(in weapon NPC):
function onActionServerside(act, acc) {
if (act == "hit") {
temp.pl = findplayer(acc);
temp.dist = ((player.x - pl.x) ^ 2 + (player.y - pl.y) ^ 2) ^ 0.5;
if (dist < 4) { // This check is just for general security, to make sure a lagger isn't just conquering people, or a hacker isn't hitting people from 500 miles away
pl.triggerClient("weapon", this.name, "hit", player.account);
}
}
}
//#CLIENTSIDE
function onKeyPressed(keycode, key) {
if (key == "s") { (you should consider using "if (keydown(5)) {" or whatever instead, in case the player wishes to change their 'default' sword key
// Some algorithmic stuff here to detect the hitbox that I'm not going to include, assume they are "pl"
if (other player in hitbox -- this is the stuff we've spoken about elsewhere) {
triggerServer("weapon", this.name, "hit", pl.account);
}
}
}
function onActionServerside(temp.act, temp.info) {
if (act == "hit") {
// Hit the player here
// "info" in this case would be the account of the player who landed the hit.
}
}


As for your "damage block" system that you complained isn't "fast paced" enough -- if you want fast pace, you need clientside hit detection. Make the "hurt" attack a clientsided function. If you're using graal default, that's "hitplayer()" -- if you aren't using Graal default, code in your own custom knockback. You can do something like finding out the angle between your player and the block, and then hitting your player back away from them using cos/sin. Set a temporary flag to give them immunity to re-hit (like the 'blinks' in default graal), and have the heart-removal happen clientside (even a clientr. flag can be set on the clientside, it just won't truly save until it's set on the serverside), and then trigger the heart update to the serverside. The server gets the info late -- but the client gets it immediately, and that keeps it feeling crisp.

For making the NPCs hit baddies, there are two main ways to do this:

1. Have the block (or baddy) repeatedly loop through npcs[] (or better, a smaller level.list of the relevant NPCs) and compare their x/y to the others' x/y to see if they are making contact.

2. Have the block (or baddy) doing testnpc() onto itself. If you setshape(1,1,1); right before you testnpc (and then re-setshape immediately after), the testnpc will miss the npc itself and allow it to detect another NPC under it. Then you can access that NPC by doing like npcs[testnpc()].

I'd go with #1 in this case. Don't loop over the entire npcs[], though. Make it so baddies save a level variable of their presence. So, for example, at the top of the baddies class:

(clientside)
function onCreated() {
level.baddies.add(this);
}

Now, when the block wants to test if a baddy is inside of its coordinates, rather than doing:

for (temp.npc: npcs) {
if (npc.isBaddie) {
// compare npc.x and npc.y to this.x and this.y
}
}

you would instead do:

for (temp.baddie: level.baddies) {
// compare baddie.x and baddie.y to this.x and this.y
}

That is easier to read, easier to access and manipulate, and more efficient because you are no longer iterating pointlessly over a bunch of unrelated NPCs that have nothing to do with baddies.

i8bit
01-15-2014, 04:34 PM
I took what was read above into consideration. Thank you. But I am running all pvp actions via Clientside.

With that said, I am having issues and can't find out why this won't work.

This is the script for the weapon "Combat"
//#CLIENTSIDE
function onKeyPressed(code, key){
if (key == "s"){
setAni("sword", NULL)'
if (player.x in |this.x-2, this.x + 1| && player.y in |this.y-1, this.y + 3|) {
triggerAction(player.x, player.y, "SYSTEM/damage", "Hurt");
}
}
}

And this is the weapon for my damage system:
//#CLIENTSIDE
function onHurt(){
setAni("hurt", NULL);
player.chat = "HURT!";
}


It's not triggering the function.

I know the x1,x2 and y1,y2 and a little outa wack. I'm just testing with the player.dir == 3

Torankusu
01-15-2014, 04:59 PM
I took what was read above into consideration. Thank you. But I am running all pvp actions via Clientside.

With that said, I am having issues and can't find out why this won't work.

This is the script for the weapon "Combat"
//#CLIENTSIDE
function onKeyPressed(code, key){
if (key == "s"){
setAni("sword", NULL)'
if (player.x in |this.x-2, this.x + 1| && player.y in |this.y-1, this.y + 3|) {
triggerAction(player.x, player.y, "SYSTEM/damage", "Hurt");
}
}
}

And this is the weapon for my damage system:
//#CLIENTSIDE
function onHurt(){
setAni("hurt", NULL);
player.chat = "HURT!";
}


It's not triggering the function.

I know the x1,x2 and y1,y2 and a little outa wack. I'm just testing with the player.dir == 3

The problem is largely going to be in your condition. Not to mention the snippet you gave us does not have a ; after setani().

Think about this.x and this.y also: this. Refers to the weapon as the object. Probably not something you are going to want to do in this instance..

And player.x and player.x refers specifically the top left of the player.

The way this is set up it almost seems as if you want to hurt yourself, which might be fine for testing, but you can put the same onHurt function in a class and join it to an npc that looks like a player character as well to test animations.

While you're still early in the script i'd read up on vecx and vecy because it will come in handy for determining a position in front of a player.

Here is a thread explaining it: http://forums.graalonline.com/forums/showthread.php?p=1649547#post1649547

Also, here is an old example using a mining system that might help you set up better testing procedures (the triggers and receiving of them can be applied to a combat system)
http://forums.graalonline.com/forums/showthread.php?t=68273