[LazyRelease] Log python npc scripts from TQ!

03/28/2011 19:44 pro4never#1
Ok so I posted about this ages ago but realized after being msg'd on youtube that I've never actually posted the code to do this...

As many of you know, I never bothered adding npc scripts for well... any npcs in the hellmouth source...


THIS WILL ONLY LOG NPC SPAWN LOCATIONS AND THE FIRST PAGE OF TEXT/OPTIONS WHEN YOU TALK TO THEM!!!


I've also been trying to encourage people to use the damn external python scripts but so far they haven't taken much of an interest... well here's something that may change that for some people.


What do I need?

This is for use with my proxy release which can be found [Only registered and activated users can see links. Click Here To Register...]

Set up should be rather simple but I'll go over it very quickly here...

-Edit conquer loader to use the ports in the program.cs (namely 5001 or 5002 for login port 5000 for game port + a local hamachi ip. DO NOT USE 127.0.0.1 OR YOU WILL CRASH THE CLIENT)

Port 5001 = new server groups (generally left side of groups)
Port 5002 = old server groups (generally right side... IE: wild kingdom and such)

Now, you'll want to edit your settings.txt file in the proxy bin/debug folder to be using the correct settings.


THIS WILL CAUSE THE DATABASE TO BE USED SO PLEASE ENSURE YOU'VE RUN THE DATABASE BACKUP (make sure innodb is enabled... not sure if i'm using it in this db though) AND THE MYSQL SETTINGS ARE CORRECT IN THE SETTINGS.TXT FILE



Now... open up the proxy source code and follow my lead.

Not strongly needed but I added it for the hell of it and so I could see what I was missing...

Program.cs

anywhere near the top (since ppl suck lets just say right above 'DynaMaps')

Code:
public static List<uint> npcsInDatabase = new List<uint>();
Right below " Wha.Start();" place

Code:
                Database.GetNpcs();
Database.cs

Code:
 public static void AddNpc(Npc S)
        {
            MySqlCommand cmd = new MySqlCommand(MySqlCommandType.INSERT);
            cmd.Insert("npcs")
                .Insert("UID", S.UID)
                .Insert("ID", S.ID)
                .Insert("X", S.X)
                .Insert("Y", S.Y)
                .Insert("Map", S.Map)
                .Insert("Mesh", S.Mesh)
                .Insert("Flag", S.Flag);
                cmd.Execute();
        }
        public static void GetNpcs()
        {
            MySqlCommand cmd = new MySqlCommand(MySqlCommandType.SELECT);
            cmd.Select("npcs");
            MySqlReader r = new MySqlReader(cmd);
            while (r.Read())
            {
                Program.npcsInDatabase.Add(r.ReadUInt32("UID"));
            }
            Console.WriteLine("loaded " + Program.npcsInDatabase.Count + " npcs from database");
        }
        public static void AddSOB(Sob S)
        {
            MySqlCommand cmd = new MySqlCommand(MySqlCommandType.INSERT);
            cmd.Insert("Sobs")
                .Insert("UID", S.UID)
                .Insert("X", S.X)
                .Insert("Y", S.Y)
                .Insert("Map", S.Map)
                .Insert("Mesh", S.Type)
                .Insert("Flag", S.Flag)
                .Insert("MaxHp", S.Hp);
                cmd.Execute();
        }
Now in Client/Client.cs

above public class Client place

Code:
 public class LogNPC
    {
        public ushort Face, NPCID = 0;
        public List<String> Text = new List<String>();
        public Dictionary<byte, string> Links = new Dictionary<byte, string>();
    }
Inside the Client class (so lets just say right below sniffing)

Code:
public LogNPC RecordingNpc = null


Now in Packets/Handler.cs where Client packets are being handled
(public static void HandleClient(byte[] Data, Client Client)) place

Code:
 #region NPC REQUEST
                    case 2031:
                        {
                            if (Client.RecordingNpc != null)
                            {
                                Client.RecordingNpc = null;
                                return;
                            }
                            Client.RecordingNpc = new LogNPC();
                            Client.RecordingNpc.NPCID = (ushort)ReadUInt32(Data, 4);
                            break;
                        }
                    #endregion
obviously this goes in the main switch statement.


Now in the handle server side ( public static void HandleServer(byte[] Data, Client Client, bool Modify)) place

Code:
 #region SOB LOGGER
                        case 1109:
                            {
                                Sob S = new Sob();
                                S.UID = ReadUInt32(Data, 4);
                                S.Type = ReadUInt16(Data, 24);
                                S.Hp = ReadUInt32(Data, 12);
                                S.Flag = ReadUInt16(Data, 26);
                                S.X = ReadUInt16(Data, 20);
                                S.Y = ReadUInt16(Data, 22);
                                S.Map = Client.Map;
                                Database.AddSOB(S);
                                break;
                            }
                        #endregion
                        #region NPC LOGGER
                        case 2032:
                            if (Client.RecordingNpc != null)
                            {
                                Client.ToClientQueue.Enqueue(Data);
                                send = false;
                                switch (Data[11])
                                {
                                    case 1:
                                        Client.RecordingNpc.Text.Add(ReadString(Data, 14, Data[13]).Replace('~', ' '));
                                        break;
                                    case 2:
                                        Client.RecordingNpc.Links.Add(Data[10], ReadString(Data, 14, Data[13]).Replace('~', ' '));
                                        break;
                                    case 4://face
                                        Client.RecordingNpc.Face = ReadUInt16(Data, 8);
                                        break;
                                    case 100:
                                        Handler.DumpNpc(Client);
                                        Client.RecordingNpc.NPCID = 0;
                                        Client.RecordingNpc = null;
                                        break;
                                }
                            }
                            break;
                        #endregion
#region SpawnNpc
                        case 2030:
                            {
                                Npc N = new Npc();
                                N.UID = ReadUInt32(Data, 4);
                                N.ID = ReadUInt32(Data, 8);
                                N.X = ReadUInt16(Data, 12);
                                N.Y = ReadUInt16(Data, 14);
                                N.Mesh = ReadUInt16(Data, 16);
                                N.Flag = Data[18];
                                N.Map = Client.Map;
                                if (!Program.npcsInDatabase.Contains(N.UID))
                                { Program.npcsInDatabase.Add(N.UID); Console.WriteLine("Adding new NPC ID: " + N.UID); Database.AddNpc(N); }

                                Location L = new Location();
                                L.X = ReadUInt16(Data, 12);
                                L.Y = ReadUInt16(Data, 14);
                                L.Map = Client.Map;
                                if (!Program.NpcLocations.Contains(L))
                                    Program.NpcLocations.Add(L);
                                if (Client.Alone)
                                    send = false;
                                break;
                            }
                        #endregion

What I did was to dump the npc I made a new .cs file (doesn't matter where) and put this

Code:
 public partial class Handler
    {
        public static void DumpNpc(Client C)
        {
            try
            {
                if (C.RecordingNpc == null)
                    return;
                if (File.Exists(Application.StartupPath + @"\npcs\" + C.RecordingNpc.NPCID + ".npc"))
                    File.Delete(Application.StartupPath + @"\npcs\" + C.RecordingNpc.NPCID + ".npc");
                StreamWriter SW = new StreamWriter(File.Create(Application.StartupPath + @"\npcs\" + C.RecordingNpc.NPCID + ".npc"));
                SW.WriteLine("def npc(Client, Option):");
                SW.WriteLine("    if(Option == 0):");
                foreach (String Text in C.RecordingNpc.Text)
                    SW.WriteLine("        Text(\"" + Text + "\")");
                foreach (KeyValuePair<byte, string> Option in C.RecordingNpc.Links)
                    SW.WriteLine("        Link(\"" + Option.Value + "\", " + Option.Key + ")");
                SW.WriteLine("        Face(" + C.RecordingNpc.Face + ")");
                SW.WriteLine("    return 0");
                SW.Close();
                SW.Flush();
                C.RecordingNpc = null;
            }
            catch { }
        }
    }
<<EDIT>>

You will also need the sob/npc classes (yes, I'm well aware I should have been using structs all through this but bite me)


Code:
public class Sob
    {
        public uint Type, UID, Hp;
        public ushort Flag, Map, X, Y;
    }
    public class Npc
    {
        public uint UID,ID;
        public byte Flag;
        public ushort Mesh, Map, X, Y;
    }
And you should be good to go.


Attached is the database I was logging the files to.

NOTE: you must place a folder called npcs in your debug folder for the scripts to write properly.


NOTEX2: Tq has changed the npc uids for most npcs. I'd suggest logging them all from scratch (make a new npc db + logging their scripts by talking to them) and use that.


NOTEX3: The way tq uses their npcs there is no simple way to log more than the first page of dialogue. This is ONLY to make the server look more complete and save you some typing. They will not 'do' anything nor will they have any dialogue past the first page.
03/28/2011 19:56 sitdownson#2
Thanks alot for putting it up,
great release.
03/28/2011 19:59 F i n c h i#3
Thanks Chris,
Gonna try it :3
03/28/2011 20:57 { Angelius }#4
Dang .

i was thinking . Hell its gonna take forever to code the npcs .
but not anymore tho !

thats gonna save alot of time :xD
03/28/2011 21:53 sitdownson#5
Im having 1 problem, I made the class file
and pasted:

public partial class Handler
{
public static void DumpNpc(Client C)
{
try
{
if (C.RecordingNpc == null)
return;
if (File.Exists(Application.StartupPath + @"\npcs\" + C.RecordingNpc.NPCID + ".npc"))
File.Delete(Application.StartupPath + @"\npcs\" + C.RecordingNpc.NPCID + ".npc");
StreamWriter SW = new StreamWriter(File.Create(Application.StartupPath + @"\npcs\" + C.RecordingNpc.NPCID + ".npc"));
SW.WriteLine("def npc(Client, Option):");
SW.WriteLine(" if(Option == 0):");
foreach (String Text in C.RecordingNpc.Text)
SW.WriteLine(" Text(\"" + Text + "\")");
foreach (KeyValuePair<byte, string> Option in C.RecordingNpc.Links)
SW.WriteLine(" Link(\"" + Option.Value + "\", " + Option.Key + ")");
SW.WriteLine(" Face(" + C.RecordingNpc.Face + ")");
SW.WriteLine(" return 0");
SW.Close();
SW.Flush();
C.RecordingNpc = null;
}
catch { }
}
}

into it, but somehow I keep getting this error:

Error 1 The type or namespace name 'Client' could not be found (are you missing a using directive or an assembly reference?) C:\Users\Marvin.Marvin-PC\Desktop\Alchemy proxy \AlchemyProxy\AlchemyProxy\AlchemyProxy.cs 3 32 AlchemyProxy

Probaly a ''easy'' fix but I cant seem to fix it.
03/28/2011 22:19 pro4never#6
sounds to me like the .cs you added isn't even part of the original solution :O...

Make sure you're adding the new cs file to the alchemyproxy project and not the overall solution or w/e.


IE:

Code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Windows.Forms;

namespace AlchemyProxy
{
    public partial class Handler
    {
        public static void DumpNpc(Client C)
        {
            try
            {
                if (C.RecordingNpc == null)
                    return;
                if (File.Exists(Application.StartupPath + @"\npcs\" + C.RecordingNpc.NPCID + ".npc"))
                    File.Delete(Application.StartupPath + @"\npcs\" + C.RecordingNpc.NPCID + ".npc");
                StreamWriter SW = new StreamWriter(File.Create(Application.StartupPath + @"\npcs\" + C.RecordingNpc.NPCID + ".npc"));
                SW.WriteLine("def npc(Client, Option):");
                SW.WriteLine("    if(Option == 0):");
                foreach (String Text in C.RecordingNpc.Text)
                    SW.WriteLine("        Text(\"" + Text + "\")");
                foreach (KeyValuePair<byte, string> Option in C.RecordingNpc.Links)
                    SW.WriteLine("        Link(\"" + Option.Value + "\", " + Option.Key + ")");
                SW.WriteLine("        Face(" + C.RecordingNpc.Face + ")");
                SW.WriteLine("    return 0");
                SW.Close();
                SW.Flush();
                C.RecordingNpc = null;
                C.RecordingNpc.NPCID = 0;
            }
            catch { }
        }
    }
}
is the entire .cs file for me
03/28/2011 22:25 sitdownson#7
Quote:
Originally Posted by pro4never View Post
sounds to me like the .cs you added isn't even part of the original solution :O...

Make sure you're adding the new cs file to the alchemyproxy project and not the overall solution or w/e.


IE:

Code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Windows.Forms;

namespace AlchemyProxy
{
    public partial class Handler
    {
        public static void DumpNpc(Client C)
        {
            try
            {
                if (C.RecordingNpc == null)
                    return;
                if (File.Exists(Application.StartupPath + @"\npcs\" + C.RecordingNpc.NPCID + ".npc"))
                    File.Delete(Application.StartupPath + @"\npcs\" + C.RecordingNpc.NPCID + ".npc");
                StreamWriter SW = new StreamWriter(File.Create(Application.StartupPath + @"\npcs\" + C.RecordingNpc.NPCID + ".npc"));
                SW.WriteLine("def npc(Client, Option):");
                SW.WriteLine("    if(Option == 0):");
                foreach (String Text in C.RecordingNpc.Text)
                    SW.WriteLine("        Text(\"" + Text + "\")");
                foreach (KeyValuePair<byte, string> Option in C.RecordingNpc.Links)
                    SW.WriteLine("        Link(\"" + Option.Value + "\", " + Option.Key + ")");
                SW.WriteLine("        Face(" + C.RecordingNpc.Face + ")");
                SW.WriteLine("    return 0");
                SW.Close();
                SW.Flush();
                C.RecordingNpc = null;
                C.RecordingNpc.NPCID = 0;
            }
            catch { }
        }
    }
}
is the entire .cs file for me
Lol my bad i failed so bad,
noticed i deleted this for some reason:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Windows.Forms;

namespace AlchemyProxy

>< thanks anyway!
03/29/2011 02:13 Yaksha#8
Thank you works like charm, this makes life easier :).
03/29/2011 05:40 BioHazarxPaul#9
Quote:
Originally Posted by { Angelius } View Post
Dang .

i was thinking . Hell its gonna take forever to code the npcs .
but not anymore tho !

thats gonna save alot of time :xD
Its great having all the npcs setup, now you get to do the fun put and write/fix 200+ npcs xD
03/29/2011 06:04 pro4never#10
Yah still lots of work getting them fully functional but it gives you a nice template to work with and saves a ton of typing.


IE: this is what we're using essentially for our new server npcs. Should have all the main npcs working before closed beta even really starts lol
03/29/2011 07:29 Santa#11
I would just like to put an emphasis on this.

Quote:
NOTEX3: The way tq uses their npcs there is no simple way to log more than the first page of dialogue. This is ONLY to make the server look more complete and save you some typing. They will not 'do' anything nor will they have any dialogue past the first page.
03/29/2011 10:30 Pro4Never2#12
i haven't understood anything, what does code do?
03/29/2011 13:18 chickmagnet#13
Quote:
Originally Posted by Pro4Never2 View Post
i haven't understood anything, what does code do?
read post above urs and really sink it in
03/29/2011 17:53 pro4never#14
Quote:
Originally Posted by Pro4Never2 View Post
i haven't understood anything, what does code do?
It makes it so that you can use my proxy to build an npc/sob database (where what npcs are ingame) based on the official tq servers and when you talk to them it will build the npc scripts for the npcs you talk to.

IE: it fills in most of the basic text for you making your npc scripting that much easier.


That's why the new hellmouth server has all the npc locations/text that real conquer does.


[Only registered and activated users can see links. Click Here To Register...]
03/29/2011 19:40 sitdownson#15
Newbie question..
Im confused with the ports/IP in the program.cs
anyone mind explain/help me?..