using System; using System.Data; using System.Text; using System.Threading; using System.Collections; using Holo.Virtual.Users; using Holo.Virtual.Rooms.Pathfinding; using Holo.Virtual.Rooms.Items; using Holo.Managers; using Ion.Storage; namespace Holo.Virtual.Rooms.Pets { /// /// Represents a computer controlled virtual user with an artifical intelligence (AI). The pet walks around in virtual rooms on specified coordinates, interacts with other virtual users and serves drinks and food. /// internal class virtualPet { #region Declares /// /// The database ID of the pet. /// public int ID; /// /// Halt the AI /// internal bool haltAI; /// /// The ID of the pet in the virtual room. /// internal int roomUID; /// /// The virtual room the pet roams in. /// internal virtualRoom Room; /// /// The name of the pet. /// internal string Name; /// /// The name of the pet. /// internal string Colour; /// /// The pet's figure string. /// internal string Figure { get { return Type.Substring(3, 1) + " " + Race + " " + Colour; } set { Figure = value; } } /// /// The pet's figure string. /// internal string Race; /// /// The pet's figure string. /// internal string Type; /// /// The X position of the pet in the virtual room. /// internal int X; /// /// The Y position of the pet in the virtual room. /// internal int Y; /// /// The height of the pet in the virtual room. /// internal double H; internal int roomID; // dont remove this u need it trust me haha internal int Hungry; // same for this.. internal floorItem Nest; /// /// The rotation of the pet's head in the virtual room. /// internal byte Z1; /// /// The rotation of the pet's body in the virtual room. /// internal byte Z2; /// /// Used for pathfinding. The X coordinate of the pet's target square in the virtual room. /// internal int goalX; /// /// Used for pathfinding. The Y coordinate of the pet's target square in the virtual room. /// internal int goalY; internal string Talk = "Ruff ruff Grrrrrrr"; int milliseconds; private delegate void statusVoid(string Key, string Value, int Length); /// /// Handles the random determining of actions. /// private Thread aiHandler; /// /// Contains the texts that the pet can 'say' on random. /// public static Hashtable _Pets = new Hashtable(); private string[] Sayings; /// /// Contains the texts that the pet can 'shout' on random. /// private string[] Shouts; /// /// Contains the coordinate's where the pet can walk to. Ignored if freeroam is enabled. /// private Coord[] Coords; /// /// Contains the chat triggers where the pet reacts on. /// private chatTrigger[] chatTriggers; #endregion #region Constructors/destructors /// /// Contains the pet's animation statuses. /// private Hashtable Statuses; /// /// Initializes a new virtualpet object, loading the walk squares, chat texts etc. /// /// The database ID of the pet. /// The ID that identifies this pet in room. /// The virtualRoom object where the pet is in. internal virtualPet(int petID, int roomUID, virtualRoom Room, int mX, int mY) { this.roomUID = roomUID; this.Room = Room; this.ID = petID; DataRow dRow; using (DatabaseClient dbClient = Eucalypt.dbManager.GetClient()) { dRow = dbClient.getRow("SELECT ownerid,name,type,colour,race,x,y,z FROM pets WHERE id = '" + petID + "'"); dbClient.runQuery("UPDATE pets SET roomid = '" + Room.roomID + "' WHERE id = '" + ID + "'"); } this.Name = Convert.ToString(dRow["name"]); this.Type = Convert.ToString(dRow["type"]); this.Race = Convert.ToString(dRow["race"]); this.Colour = Convert.ToString(dRow["colour"]); if (mX == 0 && mY == 0) { this.X = Convert.ToInt32(dRow["x"]); this.Y = Convert.ToInt32(dRow["y"]); this.Z1 = Convert.ToByte(dRow["z"]); } else { this.X = mX; this.Y = mY; this.Z1 = 1; } this.Z2 = Z1; this.goalX = -1; DataColumn dCol; using (DatabaseClient dbClient = Eucalypt.dbManager.GetClient()) { dCol = dbClient.getColumn("SELECT text FROM pets_texts WHERE animal = '" + Type + "' AND type = 'say'"); } this.Sayings = (string[])dataHandling.dColToArray(dCol); using (DatabaseClient dbClient = Eucalypt.dbManager.GetClient()) { dCol = dbClient.getColumn("SELECT text FROM pets_texts WHERE animal = '" + Type + "' AND type = 'shout'"); } this.Shouts = (string[])dataHandling.dColToArray(dCol); DataTable dTable; int i = 0; using (DatabaseClient dbClient = Eucalypt.dbManager.GetClient()) { dTable = dbClient.getTable("SELECT x,y FROM roompets_coords WHERE id = '" + petID + "'"); } Coords = new Coord[dTable.Rows.Count]; i = 0; foreach (DataRow dbRow in dTable.Rows) { Coords[i] = new Coord(Convert.ToInt32(dbRow["x"]), Convert.ToInt32(dbRow["y"])); i++; } Statuses = new Hashtable(); aiHandler = new Thread(new ThreadStart(AI)); aiHandler.Priority = ThreadPriority.BelowNormal; aiHandler.Start(); } /// /// Safely shuts this virtualPet down and tidies up all resources. /// internal void Kill() { using (DatabaseClient dbClient = (Eucalypt.dbManager.GetClient())) { dbClient.runQuery("UPDATE pets SET x = '" + X + "', y = '" + Y + "', z = '" + Z1 + "' WHERE id = '" + ID + "'"); } try { aiHandler.Abort(); } catch { } aiHandler = null; Room = null; Statuses = null; Coords = null; Sayings = null; Shouts = null; chatTriggers = null; } #endregion #region pet properties /// /// The details string of the pet, containing room identifier ID, name, motto, figure etc. /// internal string detailsString { get { string s = "i:" + roomUID + Convert.ToChar(13) + "n:" + roomUID + Convert.ToChar(4) + Name + Convert.ToChar(13) + "f:" + Figure + Convert.ToChar(13) + "l:" + X + " " + Y + " " + H + Convert.ToChar(13); s += "c:" + Convert.ToChar(13); return s; } } public static virtualPet getPet(int ID) { try { return (virtualPet)_Pets[ID]; } catch { return null; } } /// /// The status string of the pet, containing positions, movements, statuses (animations) etc. /// internal string statusString { get { string s = roomUID + " " + X + "," + Y + "," + H.ToString().Replace(",", ".") + "," + Z1 + "," + Z2 + "/"; foreach (string Key in Statuses.Keys) { s += Key; string Value = (string)Statuses[Key]; if (Value != "") s += " " + Value; s += "/"; } return s; } } #endregion #region Actions /// /// Invoked by a virtualRoomUser. There is checked if this pet reacts on a certain chat message, if so, then replies/orders etc are processed. /// /// The virtualRoomUser object that interacts with this pet by saying a message. /// The message that the virtualRoomUser said to this pet. internal void Interact(virtualRoomUser roomUser, string Message) { Message = Message.ToLower(); string[] messageWords = Message.Split(' '); if (messageWords[0] == this.Name) { switch (messageWords[1]) { #region Pet Commands #region attack case "attack": { //Room.sendShout(this, "wait, " + roomUser + ", You haven't coded that yet!"); break; } #endregion #region dead case "dead": { Command("ded", 20000); break; } #endregion #region jump case "jump": { Command("jmp"); break; } #endregion #region sit case "sit": { Command("sit"); break; } #endregion #region lay case "lay": { Command("lay"); break; } #endregion #region come case "come": { Coord Next = new Coord(); int[] Borders = Room.getMapBorders(); Next = new Coord(roomUser.X - 1, roomUser.Y); goalX = Next.X; goalY = Next.Y; break; } #endregion #region beg case "beg": { Command("beg"); break; } #endregion #region eat case "eat": { Command("eat"); break; } #endregion #region sleep case "sleep": { Command("ded", "ZzzZZZzzzzZzz"); break; } #endregion #region nest case "nest": { ToNest(); break; } #endregion #region NEED TESTING case "play": { Command("ply"); break; } case "drink": { Command("drk"); break; } #endregion #endregion default: string[] Speech = { "Grrrr", "*Sniffs*", "*Tracks smell*", "*Bite*", "Ruff!" }; Random RandomSpeech = new Random(); Room.sendShout(this, Speech[RandomSpeech.Next(0, Speech.Length - 1)]); break; } } } #region ToNest private void ToNest() { haltAI = true; milliseconds = 20000; Coord PetNest = new Coord(); int[] Borders = Room.getMapBorders(); PetNest = new Coord(Nest.X, Nest.Y); goalX = PetNest.X; goalY = PetNest.Y; //if (X == Nest.X && Y == Nest.Y) //haltAI = true; } #endregion #region Command private void Command(string Command) { this.haltAI = true; milliseconds = 5000; RemovePetStatus(); handleStatus(Command, "", 5000); Refresh(); } private void Command(string Command, int MilliSeconds) { this.haltAI = true; milliseconds = MilliSeconds; RemovePetStatus(); handleStatus(Command, "", MilliSeconds); Refresh(); } private void Command(string Command, string Message) { this.haltAI = true; RemovePetStatus(); handleStatus(Command, "", 5000); Refresh(); if (Message != "") Room.sendShout(this, Message); } #endregion #region RemovePetStatus public void RemovePetStatus() { // Remove Status Statuses.Remove("sit"); Statuses.Remove("lay"); Statuses.Remove("ded"); Statuses.Remove("jmp"); Statuses.Remove("eat"); Statuses.Remove("beg"); //Statuses.Remove("snf"); //Statuses.Remove("eat"); //Not tested - Kryptos //Statuses.Remove("drk"); //Not tested - Kryptos //Statuses.Remove("flw"); //Not tested - Kryptos //Statuses.Remove("fwl"); //Not tested Kryptos //Statuses.Remove("ply"); //Not tested - Kryptos } #endregion /// /// Rotates the pet to a certain X and Y coordinate and refreshes it in the room. If the pet is sitting, then rotating will be ignored. /// /// The X coordinate to face. /// The Y coordinate to face. internal void Rotate(int toX, int toY) { Rotate(Rotation.Calculate(X, Y, toX, toY)); } /// /// Sets a new rotation for the pet and refreshes it in the room. If the pet is sitting, then rotating will be ignored. /// /// The new rotation to use. internal void Rotate(byte R) { if (R != Z1 && Statuses.ContainsKey("sit") == false) { Z1 = R; Z2 = R; Refresh(); } } /// /// Returns a Coord object with the X and Y of the walkcoord that is as closest to the given position. /// /// The X position. /// The Y position. internal Coord getClosestWalkCoordTo(int X, int Y) { int minDistance = 6; Coord Closest = new Coord(-1, 0); foreach (Coord Coord in Coords) { int curDistance = Math.Abs(X - Coord.X) + Math.Abs(Y - Coord.Y); if (curDistance < minDistance) { minDistance = curDistance; Closest = Coord; } } return Closest; } #endregion #region Status management /// /// Adds a status key and a value to the pet's statuses. If the status is already inside, then the previous one will be removed. /// /// The key of the status. /// The value of the status. internal void addStatus(string Key, string Value) { if (Statuses.ContainsKey(Key)) Statuses.Remove(Key); Statuses.Add(Key, Value); } /// /// Removes a certain status from the pet's statuses. /// /// The key of the status to remove. internal void removeStatus(string Key) { try { if (Statuses.ContainsKey(Key)) Statuses.Remove(Key); } catch { } } /// /// Returns a bool that indicates if the pet has a certain status at the moment. /// /// The key of the status to check. internal bool containsStatus(string Key) { return Statuses.ContainsKey(Key); } /// /// Refreshes the status of the pet in the virtual room. /// internal void Refresh() { Room.Refresh(this); } /// /// Adds a status to the pet, keeps it for a specified amount of time [in ms] and removes the status. Refreshes at add and remove. /// /// The key of the status, eg, 'sit'. /// The value of the status, eg, '1.0'. /// The amount of milliseconsd to keep the status before removing it again. internal void handleStatus(string Key, string Value, int Length) { if (Statuses.ContainsKey(Key)) Statuses.Remove(Key); new statusVoid(HANDLESTATUS).BeginInvoke(Key, Value, Length, null, null); } private void HANDLESTATUS(string Key, string Value, int Length) { try { Statuses.Add(Key, Value); Refresh(); Thread.Sleep(Length); Statuses.Remove(Key); Refresh(); } catch { } } #endregion #region Misc /// /// Ran on a thread. Handles the pet's artifical intelligence, by interacting with users and using random values etc. /// private void AI() { int lastMessageID = -1; Random RND = new Random(roomUID * DateTime.Now.Millisecond); //try { while (true) { if (haltAI) { Thread.Sleep(milliseconds); haltAI = false; } int ACTION = RND.Next(0, 7); switch (ACTION) { case 1: // Move { RemovePetStatus(); Coord Next = new Coord(); int[] Borders = Room.getMapBorders(); Next = new Coord(RND.Next(0, Borders[0]), RND.Next(0, Borders[1])); if (Next.X == X && Next.Y == Y) // Coord didn't changed { Z1 = (byte)RND.Next(0, 10); Z2 = Z1; Refresh(); } else { goalX = Next.X; goalY = Next.Y; } break; } case 2: // Rotate { byte R = (byte)RND.Next(0, 10); while (R == Z2) R = (byte)RND.Next(0, 10); Rotate(R); break; } case 3: // Shout { if (Shouts.Length > 0) { int messageID = RND.Next(0, Shouts.Length); if (Shouts.Length > 1) // More than one shout assigned { while (messageID == lastMessageID) // Avoid shouting the same sentence two times in a row messageID = RND.Next(0, Shouts.Length); lastMessageID = messageID; } Room.sendShout(this, stringManager.filterSwearwords(Shouts[messageID])); } break; } case 4: // Say { if (Sayings.Length > 0) { int messageID = RND.Next(0, Sayings.Length); if (Sayings.Length > 1) // More than one saying assigned { while (messageID == lastMessageID) // Avoid saying the same sentence two times in a row messageID = RND.Next(0, Sayings.Length); lastMessageID = messageID; } Room.sendShout(this, stringManager.filterSwearwords(Sayings[messageID])); } break; } case 5: // Status { if (RND.Next(0, 2) == 0) { RemovePetStatus(); handleStatus("jmp", "", 4000); } else { RemovePetStatus(); addStatus("ded", ""); addStatus("eat", ""); addStatus("drk", ""); addStatus("lay", ""); addStatus("sit", ""); addStatus("ply", ""); addStatus("flw", ""); addStatus("fwl", ""); Refresh(); } break; } case 6: // Pet Stats { break; } } Thread.Sleep(3000); Out.WriteTrace("pet AI loop"); } } //catch { aiHandler.Abort(); } } #endregion #region Private objects /// /// Represents a trigger that can be invoked by a chat message. Results in a reply and/or an order confirmation. /// private class chatTrigger { /// /// A System.String array with words that invoke this trigger. /// private string[] Words; /// /// A System.String array with replies that are used when this trigger is invoked. /// private string[] Replies; internal chatTrigger(string[] Words, string[] Replies) { this.Words = Words; this.Replies = Replies; } /// /// Returns a boolean that indicates if this trigger replies on a certain word. /// /// The word to check. internal bool containsWord(string Word) { if (Word.Substring(Word.Length - 1, 1) == "?") Word = Word.Substring(0, Word.Length - 1); for (int i = 0; i < Words.Length; i++) if (Words[i] == Word) return true; return false; } /// /// Returns a random reply from the replies array. /// internal string Reply { get { return Replies[new Random(DateTime.Now.Millisecond).Next(0, Replies.Length)]; } } } #endregion } }