• Unfortunately, we have experienced significant hard drive damage that requires urgent maintenance and rebuilding. The forum will be a state of read only until we install our new drives and rebuild all the configurations needed. Please follow our Facebook page for updates, we will be back up shortly! (The forum could go offline at any given time due to the nature of the failed drives whilst awaiting the upgrades.) When you see an Incapsula error, you know we are in the process of migration.

One-Time use NPCs

Newbie Spellweaver
Joined
Feb 23, 2012
Messages
31
Reaction score
18
I just coded this thing to challenge myself, as I am a new(er) coder. It might be of some use to somebody. I would also like feedback from the community. I realize that there might be some better ways to code this, but this is how I did it. If there is a better way, I would definitely like to hear it. No flaming please <3 My first release.

In MapleCharacter.java, add
Code:
private static int[] usedNpc = new int[] {0};
private boolean changed_usednpc;
and
Code:
public final boolean getUsedNpc(int npc) {
        for (int i = 0; i < usedNpc.length; i++) {
            if (usedNpc[i] == npc)
                return true;
        } return false;
    }
    
    public final void setUsedNpc(int npc) {
        if (usedNpc[0] == 0) {
            MapleCharacter.usedNpc[0] = npc;
            this.changed_usednpc = true;
        } else {
            int[] rice = MapleCharacter.usedNpc;
            usedNpc = new int[rice.length+1];
            MapleCharacter.usedNpc[rice.length] = npc;
            this.changed_usednpc = true;
        }
    }
and
Code:
ps = con.prepareStatement("SELECT npc FROM usednpc WHERE charid = ?");
                ps.setInt(1, charid);
                rs = ps.executeQuery();
                int n = 0;
                while (rs.next()) {
                    usedNpc[n] = rs.getInt("npc");
                    n++;
                }
                rs.close();
                ps.close();
and
Code:
if (changed_usednpc) {
                deleteWhereCharacterId(con, "DELETE FROM usednpc WHERE charid = ?");
                ps = con.prepareStatement("INSERT INTO usednpc (charid, npc) VALUES (?, ?)");
                ps.setInt(1, id);
                for (int i = 0; i < usedNpc.length; i++) {
                    if (usedNpc[i] > 0) {
                        ps.setInt(2, usedNpc[i]);
                        ps.execute();
                    }
                }
                ps.close();
            }

In NPCConversationManager.java, add
Code:
public void setUsedNpc(int npc) {
        c.getPlayer().setUsedNpc(npc);
    }
    
    public boolean getUsedNpc(int npc) {
        if (c.getPlayer().getUsedNpc(npc)) {
            return true;
        }
        return false;
    }

In MySQL, execute this:
Code:
ADD TABLE IF NOT EXISTS usednpc (charid int(11), npc int(11));

Here is a sample code for my Wild Hunter skill fixer:
Code:
var status;
var npc;
var successful = false;

function start() {
    status = -1;
    action(1,0,0);
}

function action(mode, type, selection) {
    npc = 9900003;
    successful = cm.getUsedNpc(npc);
    if (mode != 1) {
        cm.dispose();
        return;
    } else
        status++;
    
    if (status == 0) {
        if (cm.getJob() != 3312) {
            cm.sendOk("I am of no use to you.");
            cm.dispose();
            return;
        } else
            cm.sendYesNo("Would you like to fix your 4th job skills?\r\n#bOnly#k if their master level is 0.");
    } else if (status == 1) {
        if (successful)
            cm.sendOk("You've already used this npc!");
        else {
            cm.giveSkill(33120000, 0, 10);
            cm.giveSkill(33121001, 0, 10);
            cm.giveSkill(33121009, 0, 10);
            cm.sendOk("Your 4th job skills' master levels are now 10.");
            cm.setUsedNpc(npc);
        }
        cm.dispose();
        return;
    }
}
You have to take out the cm.giveSkill() since you have to get your own giveSkill method.
Intended for TetraSEA/Lithium
If you don't know where to put them, just take some key words from my code and ctrl+f.
 
Last edited:
Experienced Elementalist
Joined
Mar 13, 2010
Messages
291
Reaction score
47
nice very nicerelease.
Well, now I should ask, If players usedthis npc and they cc/relog/dc/crash etc. the skill is gone again :O?
Wellthat happens by my on my server
 
Newbie Spellweaver
Joined
Feb 23, 2012
Messages
31
Reaction score
18
Nah. Only if the server crashes will it not save.
 
Custom Title Activated
Loyal Member
Joined
Aug 21, 2009
Messages
1,149
Reaction score
598
Not in the mood to take you down, but this is pretty much wrong.

1. As you set the usedNpc as static attribute, this will affect everyone in the server. This means that there will not be an accurate list of the npc's that somebody has used.

2. I will like to take an approach of this block of code in specific:

PHP:
    int[] rice = MapleCharacter.usedNpc;
    usedNpc = new int[rice.length+1];
    MapleCharacter.usedNpc[rice.length] = npc;
    this.changed_usednpc = true;

As far as I understand, this block is to add a new npc into the array, expanding the array to avoid an IndexOutOfBoundsException.

Well, if I'm right, then, let's examine the code:

#1 - int[] rice = MapleCharacter.usedNpc;

You're creating a new int array which is equals to the usedNpc array.

#2 - usedNpc = new int[rice.length+1];

You're wiping the usedNpc because you're creating a new int array with the length of the variable 'rice' plus 1.

#3 - MapleCharacter.usedNpc[rice.length] = npc;

You're setting the last index of the array 'usedNpc' to the npc that we just used.

#4 - this.changed_usednpc = true;

You're setting 'changed_usednpc' to true. So in this MapleCharacter instance 'changed_usednpc' is true.
(I wonder why)

So, let's make this clear, usedNpc will has all values to 0's except, the last one, which will be the last npc that you clicked on. Besides, this is a static attribute, so this will be the same for everyone.

3. The load from database part, I will assume it is this one:

PHP:
    ps = con.prepareStatement("SELECT npc FROM usednpc WHERE charid = ?");
                ps.setInt(1, charid);
                rs = ps.executeQuery();
                int n = 0;
                while (rs.next()) {
                    usedNpc[n] = rs.getInt("npc");
                    n++;
                }
                rs.close();
                ps.close();

To make it quick, you're assuming that 'usedNpc' array length is the same as the resultset given, but there is no sign of you setting the length of the array to 'n' anywhere except that I'm just missing it. So you will, in fact, get an ArrayOutOfBoundsException unless the following cases occurs:

1. There is nothing in the database about that charid.
2. There is only one npc that comes out from that resultset.
3. There was a previous character which sets the npcUsed length to a bigger or equals size of 'n' in the resultset given.

4. The save part, I will assume it's this one:

PHP:
    if (changed_usednpc) {
                deleteWhereCharacterId(con, "DELETE FROM usednpc WHERE charid = ?");
                ps = con.prepareStatement("INSERT INTO usednpc (charid, npc) VALUES (?, ?)");
                ps.setInt(1, id);
                for (int i = 0; i < usedNpc.length; i++) {
                    if (usedNpc[i] > 0) {
                        ps.setInt(2, usedNpc[i]);
                        ps.execute();
                    }
                }
                ps.close();
            }

I just want to know in here, why you remove everything, and then you just add it back. What's the point?

This is all as for now, since I don't thinks I should go on because I gave you enough reasons to know that you must of change some structure there. Or at least, is what is thinks.

Have a nice day :)
 
Newbie Spellweaver
Joined
Feb 23, 2012
Messages
31
Reaction score
18
1: I set it to static because when I try to load the npcs, it says a non-static variable cannot be referenced from a static context. I didn't know how else to fix it.
2: What's wrong with the setUsedNpc method? I don't see how it will make them all 0 except for the last clicked npc.
3: How can I check how many npcs that the char has used with SQL?
4: I make it delete everything because I don't know how to make know which array value of usedNpc was changed/added

Help is much appreciated, thanks.
 
Custom Title Activated
Loyal Member
Joined
Aug 21, 2009
Messages
1,149
Reaction score
598
1: I set it to static because when I try to load the npcs, it says a non-static variable cannot be referenced from a static context. I didn't know how else to fix it.
2: What's wrong with the setUsedNpc method? I don't see how it will make them all 0 except for the last clicked npc.
3: How can I check how many npcs that the char has used with SQL?
4: I make it delete everything because I don't know how to make know which array value of usedNpc was changed/added

Help is much appreciated, thanks.

You're welcome :)

Okay, as to answer to each of your enumerated list, goes as follow:

1. Right, in most sources as far as I recall the LoadCharacter method is static, however, it does create a new MapleCharacter object as far as I remember, so you use it to set the values to its respective attributes. The LoadCharacter also returns this new object that is being made with the information that is being loaded from the database.

2. The method 'setUsedNpc' will make them all zero's because you're creating a new int array, so, by default, all the values inside the native int array are all zero's. However, in the line before the last one I quoted you set the last index of the new 'usedNpc' to the NPC you just clicked. So, in fact, all the previous values are 0's and the last one will be the last NPC clicked.

3. As you're using native array, the way you should do this is by settings the length of the array before giving values into it. How?

The best way that pops out of my mind is ditching the native array and use a collection (ArrayList or LinkedList). Otherwise, I can suggest you others works-around that I can think of, but are not as optimized as the usage of a collection in my opinion.

Suggestion #1:

Change the query to obtain the total of rows, because, as you should know, ResultSet doesn't have anyway to returns the amount of rows.

PHP:
ps = con.prepareStatement("SELECT (select count(*) from usednpc WHERE charid = " + charid + ") count, npc FROM usednpc WHERE charid = ?");
                ps.setInt(1, charid);
                rs = ps.executeQuery();
                int n = 0;
                usedNpc = new int[rs.getInt("count")];
                while (rs.next()) {
                    usedNpc[n++] = rs.getInt("npc");
                }
                rs.close();
                ps.close();

Suggestion #2:

Create a new native int array as temporal to keep the information.

PHP:
ps = con.prepareStatement("SELECT npc FROM usednpc WHERE charid = ?");
                ps.setInt(1, charid);
                rs = ps.executeQuery();
                int n = 0;
                int[] temporal = new int[5000];
                while (rs.next()) {
                    temporal[n++] = rs.getInt("npc");
                }
                usedNpc = new int[n];
                for (int i = 0; i < n; i++) {
                    usedNpc[i] = temporal[i];
                }
                rs.close();
                ps.close();

4. How to know the changes? It's simple. However, in your case, you supposedly will not have to remove any, because you will on just keep clicking NPC's. It's totally incremental.

PHP:
    StringBuilder sb = new StringBuilder();
    for (int all : usedNpc) {
        sb.append(all + ", ");
    }
    sb.deleteCharAt(sb.length() - 2);
    ps = con.prepareStatement("SELECT npc FROM usednpc un WHERE EXISTS (SELECT * FROM usednpc unp WHERE up.npc = unp.npc AND npc IN (" + sb.toString() + ") AND up.charid = " + charid + ");");
    int[] toAdd = new int[usedNpc.length];
    rs = ps.executeQuery();
    int i = 0;
    sb = new StringBuilder();
    result:
    while (rs.next()) {
        int value = rs.getInt("npc");
        for (int all : usedNpc) {
            if (all == value) {
                continue result;
            }
        }
        sb.append("(" + charid + ", " + all + "), ");
    }
    if (sb.length() > 2) {
        sb.deleteCharAt(sb.length() - 2);
    }
    ps = con.prepareStatement("INSERT INTO usednpc (charid, npc) VALUES " + sb.toString()).execute();
    ps.close();

Or even better:

PHP:
    StringBuilder sb = new StringBuilder();
    for (int all : usedNpc) {
        sb.append(all + ", ");
    }
    sb.deleteCharAt(sb.length() - 2);
    ps = con.prepareStatement("SELECT unp.npc FROM usednpc un LEFT JOIN usednpc unp ON un.npc = unp.npc AND unp.npc IN (" + sb.toString() + ") AND unp.charid = ? GROUP BY unp.npc;");
    ps.setInt(1, charid);
    int[] toRemove = new int[usedNpc.length];
    rs = ps.executeQuery();
    int i = 0;
    while (rs.next()) {
        Integer value = rs.getInt("npc");
        if (value != null) {
            toRemove[i++] = value;
        }
    }
    sb = new StringBuilder();
    check:
    for (int all : usedNpc) {
        for (int x : toRemove) {
            if (all == x) {
                continue check;
            }
         }
         sb.append("(" + charid + ", " + all + "), ");
    }
    if (sb.length() > 2) {
        sb.deleteCharAt(sb.length() - 2);
    }
    ps = con.prepareStatement("INSERT INTO usednpc (charid, npc) VALUES " + sb.toString()).execute();
    ps.close();

:)

I did all the code in the Quick Reply RichText, so, if there is any mistake, let me know.
 
Last edited:
Newbie Spellweaver
Joined
Feb 23, 2012
Messages
31
Reaction score
18
You're welcome :)

Okay, as to answer to each of your enumerated list, goes as follow:

1. Right, in most sources as far as I recall the LoadCharacter method is static, however, it does create a new MapleCharacter object as far as I remember, so you use it to set the values to its respective attributes. The LoadCharacter also returns this new object that is being made with the information that is being loaded from the database.
Since I'm so new to Java, I really have no clue what that is. I haven't taken a Java class yet (I intend to in the fall semester), I've taken a beginner's C++ class, though.
Maybe I could add you on Skype and you could help me with this a bit? I'm really trying to learn this stuff, not simply copy + paste as all the other people do in this

2. The method 'setUsedNpc' will make them all zero's because you're creating a new int array, so, by default, all the values inside the native int array are all zero's. However, in the line before the last one I quoted you set the last index of the new 'usedNpc' to the NPC you just clicked. So, in fact, all the previous values are 0's and the last one will be the last NPC clicked.
So I can just use usedNpc and step through each value from the rice array and then make the last value of usedNpc the value of the npc, right?

3. As you're using native array, the way you should do this is by settings the length of the array before giving values into it. How?

The best way that pops out of my mind is ditching the native array and use a collection (ArrayList or LinkedList). Otherwise, I can suggest you others works-around that I can think of, but are not as optimized as the usage of a collection in my opinion.

Suggestion #1:

Change the query to obtain the total of rows, because, as you should know, ResultSet doesn't have anyway to returns the amount of rows.

PHP:
ps = con.prepareStatement("SELECT (select count(*) from usednpc WHERE charid = " + charid + ") count, npc FROM usednpc WHERE charid = ?");
                ps.setInt(1, charid);
                rs = ps.executeQuery();
                int n = 0;
                usedNpc = new int[rs.getInt("count")];
                while (rs.next()) {
                    usedNpc[n++] = rs.getInt("npc");
                }
                rs.close();
                ps.close();

Suggestion #2:

Create a new native int array as temporal to keep the information.

PHP:
ps = con.prepareStatement("SELECT npc FROM usednpc WHERE charid = ?");
                ps.setInt(1, charid);
                rs = ps.executeQuery();
                int n = 0;
                int[] temporal = new int[5000];
                while (rs.next()) {
                    temporal[n++] = rs.getInt("npc");
                }
                usedNpc = new int[n];
                for (int i = 0; i < n; i++) {
                    usedNpc[i] = temporal[i];
                }
                rs.close();
                ps.close();
That makes a lot of sense. If you're open to adding me on Skype, maybe you can help me understand/learn ArrayList/LinkedList?

4. How to know the changes? It's simple. However, in your case, you supposedly will not have to remove any, because you will on just keep clicking NPC's. It's totally incremental.

PHP:
    StringBuilder sb = new StringBuilder();
    for (int all : usedNpc) {
        sb.append(all + ", ");
    }
    sb.deleteCharAt(sb.length() - 2);
    ps = con.prepareStatement("SELECT npc FROM usednpc un WHERE EXISTS (SELECT * FROM usednpc unp WHERE up.npc = unp.npc AND npc IN (" + sb.toString() + ") AND up.charid = " + charid + ");");
    int[] toAdd = new int[usedNpc.length];
    rs = ps.executeQuery();
    int i = 0;
    result:
    while (rs.next()) {
        int value = rs.getInt("npc");
        for (int all : usedNpc) {
            if (all == value) {
                continue result;
            }
        }
        toAdd[i++] = value;
    }
    sb = new StringBuilder();
    for (int all : toAdd) {
        sb.append("(" + charid + ", " + all + "), ");
    }
    sb.deleteCharAt(sb.length() - 2);
    ps = con.prepareStatement("INSERT INTO usednpc (charid, npc) VALUES " + sb.toString()).execute();
    ps.close();
I'm really terrible with SQL..I pretty much don't have any experience with SQL query so there was no way I could've made sense of that.
Hopefully you can explain some of the things you used in this code to me.

Thanks again
 
Custom Title Activated
Loyal Member
Joined
Aug 21, 2009
Messages
1,149
Reaction score
598
I edited the code in the reply #4.

Once again, everything I have written has been from the Quick Reply RichText. So if there is any mistype or something, let me know.

Regarding to chat in Skype. I'm sorry to tell you that I do not wish to do this. However, I can assist you in this very thread with no problem as far as I can do it. Anyone else can help tho... Do not intent into "stealing" your thread.

So I can just use usedNpc and step through each value from the rice array and then make the last value of usedNpc the value of the npc, right?

You have to set rise as a new array of the same length of npcUsed plus one. And then iterate through npcUsed to set all values to rice and then add the last value to rice and finally set npcUsed equals to rice.
 
Newbie Spellweaver
Joined
Feb 23, 2012
Messages
31
Reaction score
18
I edited the code in the reply #4.

Once again, everything I have written has been from the Quick Reply RichText. So if there is any mistype or something, let me know.

Regarding to chat in Skype. I'm sorry to tell you that I do not wish to do this. However, I can assist you in this very thread with no problem as far as I can do it. Anyone else can help tho... Do not intent into "stealing" your thread.



You have to set rise as a new array of the same length of npcUsed plus one. And then iterate through npcUsed to set all values to rice and then add the last value to rice and finally set npcUsed equals to rice.

Aw, okay. Thanks a lot.
Do you think you could explain how I can make the usedNpc a non-static variable, at least?
 
Last edited:
Custom Title Activated
Loyal Member
Joined
Aug 21, 2009
Messages
1,149
Reaction score
598
Do you think you could explain how I can make the usedNpc a non-static variable, at least?

Told you already. Set it in the MapleCharacter instance created inside that method, usually named as "ret".
 
Newbie Spellweaver
Joined
Feb 23, 2012
Messages
31
Reaction score
18
Oh, haha. Sorry, thanks for the hundredth time. I fixed it.
 
Custom Title Activated
Loyal Member
Joined
Nov 14, 2008
Messages
1,025
Reaction score
641
this would be so much simpler with an arraylist or something
 
Newbie Spellweaver
Joined
Feb 23, 2012
Messages
31
Reaction score
18
I'm kinda new to Java, so I don't know how to use them. Which is why I asked him to help teach me, but you probably read it.
 
Newbie Spellweaver
Joined
Feb 9, 2009
Messages
76
Reaction score
11
when i add these to maplecharacter.java i get errors.
Code:
ps = con.prepareStatement("SELECT npc FROM usednpc WHERE charid = ?");
                ps.setInt(1, charid);
                rs = ps.executeQuery();
                int n = 0;
                while (rs.next()) {
                    usedNpc[n] = rs.getInt("npc");
                    n++;
                }
                rs.close();
                ps.close();

and
Code:
if (changed_usednpc) {
                deleteWhereCharacterId(con, "DELETE FROM usednpc WHERE charid = ?");
                ps = con.prepareStatement("INSERT INTO usednpc (charid, npc) VALUES (?, ?)");
                ps.setInt(1, id);
                for (int i = 0; i < usedNpc.length; i++) {
                    if (usedNpc[i] > 0) {
                        ps.setInt(2, usedNpc[i]);
                        ps.execute();
                    }
                }
                ps.close();
            }

any solution??
 
Back
Top